From c93664780a9192831f105242a347f80da2c438b0 Mon Sep 17 00:00:00 2001 From: "aalfiann@gmail.com" Date: Fri, 16 Dec 2016 17:29:18 +0700 Subject: [PATCH] first commit --- database/islim.sql | 57 + license.md | 9 + readme.md | 207 +++ src/classes/Custom.php | 118 ++ src/composer.json | 19 + src/composer.lock | 540 ++++++ src/config.php | 37 + src/logs/app.log | 1 + src/models/Starter.php | 47 + src/public/.htaccess | 6 + src/public/default/css/bootstrap.min.css | 7 + src/public/default/css/starter-template.css | 7 + src/public/default/img/bootstrap.ico | Bin 0 -> 5430 bytes src/public/default/js/bootstrap.min.js | 7 + src/public/default/js/holder.min.js | 13 + .../js/ie10-viewport-bug-workaround.js | 24 + src/public/index.php | 116 ++ src/routers/api.router.php | 21 + src/routers/handler.router.php | 12 + src/routers/hello.router.php | 10 + src/routers/index.router.php | 17 + src/routers/loop.router.php | 15 + src/templates/default/frontend/404.html | 43 + src/templates/default/frontend/index.html | 43 + .../default/frontend/layout.global.css.html | 5 + .../default/frontend/layout.global.js.html | 10 + .../default/frontend/layout.global.meta.html | 3 + src/templates/default/frontend/loop.html | 67 + src/templates/default/frontend/user.html | 41 + src/templates/defaultPHP/frontend/404.html | 43 + src/templates/defaultPHP/frontend/index.html | 43 + .../frontend/layout.global.css.html | 5 + .../defaultPHP/frontend/layout.global.js.html | 10 + .../frontend/layout.global.meta.html | 3 + src/templates/defaultPHP/frontend/loop.html | 67 + src/templates/defaultPHP/frontend/user.html | 41 + src/vendor/autoload.php | 7 + src/vendor/composer/ClassLoader.php | 415 +++++ src/vendor/composer/LICENSE | 21 + src/vendor/composer/autoload_classmap.php | 9 + src/vendor/composer/autoload_files.php | 10 + src/vendor/composer/autoload_namespaces.php | 11 + src/vendor/composer/autoload_psr4.php | 16 + src/vendor/composer/autoload_real.php | 70 + src/vendor/composer/autoload_static.php | 96 + src/vendor/composer/installed.json | 543 ++++++ .../container-interop/.gitignore | 3 + .../container-interop/LICENSE | 20 + .../container-interop/README.md | 85 + .../container-interop/composer.json | 11 + .../docs/ContainerInterface-meta.md | 114 ++ .../docs/ContainerInterface.md | 153 ++ .../docs/Delegate-lookup-meta.md | 259 +++ .../container-interop/docs/Delegate-lookup.md | 60 + .../docs/images/interoperating_containers.png | Bin 0 -> 35971 bytes .../docs/images/priority.png | Bin 0 -> 22949 bytes .../docs/images/side_by_side_containers.png | Bin 0 -> 22519 bytes .../Interop/Container/ContainerInterface.php | 37 + .../Exception/ContainerException.php | 13 + .../Container/Exception/NotFoundException.php | 13 + src/vendor/monolog/monolog/.php_cs | 59 + src/vendor/monolog/monolog/CHANGELOG.mdown | 328 ++++ src/vendor/monolog/monolog/LICENSE | 19 + src/vendor/monolog/monolog/README.mdown | 95 + src/vendor/monolog/monolog/composer.json | 66 + src/vendor/monolog/monolog/doc/01-usage.md | 231 +++ .../doc/02-handlers-formatters-processors.md | 157 ++ .../monolog/monolog/doc/03-utilities.md | 13 + .../monolog/monolog/doc/04-extending.md | 76 + src/vendor/monolog/monolog/doc/sockets.md | 39 + src/vendor/monolog/monolog/phpunit.xml.dist | 19 + .../monolog/src/Monolog/ErrorHandler.php | 227 +++ .../Monolog/Formatter/ChromePHPFormatter.php | 78 + .../Monolog/Formatter/ElasticaFormatter.php | 89 + .../Monolog/Formatter/FlowdockFormatter.php | 116 ++ .../Monolog/Formatter/FluentdFormatter.php | 85 + .../Monolog/Formatter/FormatterInterface.php | 36 + .../Formatter/GelfMessageFormatter.php | 137 ++ .../src/Monolog/Formatter/HtmlFormatter.php | 141 ++ .../src/Monolog/Formatter/JsonFormatter.php | 206 +++ .../src/Monolog/Formatter/LineFormatter.php | 175 ++ .../src/Monolog/Formatter/LogglyFormatter.php | 47 + .../Monolog/Formatter/LogstashFormatter.php | 166 ++ .../Monolog/Formatter/MongoDBFormatter.php | 105 ++ .../Monolog/Formatter/NormalizerFormatter.php | 297 ++++ .../src/Monolog/Formatter/ScalarFormatter.php | 48 + .../Monolog/Formatter/WildfireFormatter.php | 113 ++ .../src/Monolog/Handler/AbstractHandler.php | 186 ++ .../Handler/AbstractProcessingHandler.php | 66 + .../Monolog/Handler/AbstractSyslogHandler.php | 101 ++ .../src/Monolog/Handler/AmqpHandler.php | 148 ++ .../Monolog/Handler/BrowserConsoleHandler.php | 230 +++ .../src/Monolog/Handler/BufferHandler.php | 117 ++ .../src/Monolog/Handler/ChromePHPHandler.php | 211 +++ .../src/Monolog/Handler/CouchDBHandler.php | 72 + .../src/Monolog/Handler/CubeHandler.php | 151 ++ .../monolog/src/Monolog/Handler/Curl/Util.php | 57 + .../Monolog/Handler/DeduplicationHandler.php | 169 ++ .../Handler/DoctrineCouchDBHandler.php | 45 + .../src/Monolog/Handler/DynamoDbHandler.php | 107 ++ .../Monolog/Handler/ElasticSearchHandler.php | 128 ++ .../src/Monolog/Handler/ErrorLogHandler.php | 82 + .../src/Monolog/Handler/FilterHandler.php | 140 ++ .../ActivationStrategyInterface.php | 28 + .../ChannelLevelActivationStrategy.php | 59 + .../ErrorLevelActivationStrategy.php | 34 + .../Monolog/Handler/FingersCrossedHandler.php | 163 ++ .../src/Monolog/Handler/FirePHPHandler.php | 195 +++ .../src/Monolog/Handler/FleepHookHandler.php | 126 ++ .../src/Monolog/Handler/FlowdockHandler.php | 127 ++ .../src/Monolog/Handler/GelfHandler.php | 73 + .../src/Monolog/Handler/GroupHandler.php | 104 ++ .../src/Monolog/Handler/HandlerInterface.php | 90 + .../src/Monolog/Handler/HandlerWrapper.php | 106 ++ .../src/Monolog/Handler/HipChatHandler.php | 350 ++++ .../src/Monolog/Handler/IFTTTHandler.php | 69 + .../src/Monolog/Handler/LogEntriesHandler.php | 55 + .../src/Monolog/Handler/LogglyHandler.php | 102 ++ .../src/Monolog/Handler/MailHandler.php | 67 + .../src/Monolog/Handler/MandrillHandler.php | 68 + .../Handler/MissingExtensionException.php | 21 + .../src/Monolog/Handler/MongoDBHandler.php | 59 + .../Monolog/Handler/NativeMailerHandler.php | 185 ++ .../src/Monolog/Handler/NewRelicHandler.php | 202 +++ .../src/Monolog/Handler/NullHandler.php | 45 + .../src/Monolog/Handler/PHPConsoleHandler.php | 242 +++ .../src/Monolog/Handler/PsrHandler.php | 56 + .../src/Monolog/Handler/PushoverHandler.php | 185 ++ .../src/Monolog/Handler/RavenHandler.php | 231 +++ .../src/Monolog/Handler/RedisHandler.php | 97 ++ .../src/Monolog/Handler/RollbarHandler.php | 131 ++ .../Monolog/Handler/RotatingFileHandler.php | 178 ++ .../src/Monolog/Handler/SamplingHandler.php | 82 + .../src/Monolog/Handler/Slack/SlackRecord.php | 248 +++ .../src/Monolog/Handler/SlackHandler.php | 202 +++ .../Monolog/Handler/SlackWebhookHandler.php | 108 ++ .../src/Monolog/Handler/SlackbotHandler.php | 80 + .../src/Monolog/Handler/SocketHandler.php | 346 ++++ .../src/Monolog/Handler/StreamHandler.php | 176 ++ .../Monolog/Handler/SwiftMailerHandler.php | 94 + .../src/Monolog/Handler/SyslogHandler.php | 67 + .../Monolog/Handler/SyslogUdp/UdpSocket.php | 56 + .../src/Monolog/Handler/SyslogUdpHandler.php | 82 + .../src/Monolog/Handler/TestHandler.php | 154 ++ .../Handler/WhatFailureGroupHandler.php | 61 + .../Monolog/Handler/ZendMonitorHandler.php | 95 + .../monolog/monolog/src/Monolog/Logger.php | 700 ++++++++ .../src/Monolog/Processor/GitProcessor.php | 64 + .../Processor/IntrospectionProcessor.php | 107 ++ .../Processor/MemoryPeakUsageProcessor.php | 35 + .../src/Monolog/Processor/MemoryProcessor.php | 63 + .../Processor/MemoryUsageProcessor.php | 35 + .../Monolog/Processor/MercurialProcessor.php | 63 + .../Monolog/Processor/ProcessIdProcessor.php | 31 + .../Processor/PsrLogMessageProcessor.php | 48 + .../src/Monolog/Processor/TagProcessor.php | 44 + .../src/Monolog/Processor/UidProcessor.php | 46 + .../src/Monolog/Processor/WebProcessor.php | 113 ++ .../monolog/monolog/src/Monolog/Registry.php | 134 ++ .../tests/Monolog/ErrorHandlerTest.php | 31 + .../Formatter/ChromePHPFormatterTest.php | 158 ++ .../Formatter/ElasticaFormatterTest.php | 79 + .../Formatter/FlowdockFormatterTest.php | 55 + .../Formatter/FluentdFormatterTest.php | 62 + .../Formatter/GelfMessageFormatterTest.php | 234 +++ .../Monolog/Formatter/JsonFormatterTest.php | 183 ++ .../Monolog/Formatter/LineFormatterTest.php | 222 +++ .../Monolog/Formatter/LogglyFormatterTest.php | 40 + .../Formatter/LogstashFormatterTest.php | 333 ++++ .../Formatter/MongoDBFormatterTest.php | 262 +++ .../Formatter/NormalizerFormatterTest.php | 423 +++++ .../Monolog/Formatter/ScalarFormatterTest.php | 110 ++ .../Formatter/WildfireFormatterTest.php | 142 ++ .../Monolog/Handler/AbstractHandlerTest.php | 115 ++ .../Handler/AbstractProcessingHandlerTest.php | 80 + .../tests/Monolog/Handler/AmqpHandlerTest.php | 136 ++ .../Handler/BrowserConsoleHandlerTest.php | 130 ++ .../Monolog/Handler/BufferHandlerTest.php | 158 ++ .../Monolog/Handler/ChromePHPHandlerTest.php | 141 ++ .../Monolog/Handler/CouchDBHandlerTest.php | 31 + .../Handler/DeduplicationHandlerTest.php | 165 ++ .../Handler/DoctrineCouchDBHandlerTest.php | 52 + .../Monolog/Handler/DynamoDbHandlerTest.php | 75 + .../Handler/ElasticSearchHandlerTest.php | 239 +++ .../Monolog/Handler/ErrorLogHandlerTest.php | 66 + .../Monolog/Handler/FilterHandlerTest.php | 170 ++ .../Handler/FingersCrossedHandlerTest.php | 279 +++ .../Monolog/Handler/FirePHPHandlerTest.php | 96 + .../tests/Monolog/Handler/Fixtures/.gitkeep | 0 .../Monolog/Handler/FleepHookHandlerTest.php | 85 + .../Monolog/Handler/FlowdockHandlerTest.php | 88 + .../Monolog/Handler/GelfHandlerLegacyTest.php | 95 + .../tests/Monolog/Handler/GelfHandlerTest.php | 117 ++ .../Handler/GelfMockMessagePublisher.php | 25 + .../Monolog/Handler/GroupHandlerTest.php | 112 ++ .../Monolog/Handler/HandlerWrapperTest.php | 130 ++ .../Monolog/Handler/HipChatHandlerTest.php | 279 +++ .../Monolog/Handler/LogEntriesHandlerTest.php | 84 + .../tests/Monolog/Handler/MailHandlerTest.php | 75 + .../tests/Monolog/Handler/MockRavenClient.php | 27 + .../Monolog/Handler/MongoDBHandlerTest.php | 65 + .../Handler/NativeMailerHandlerTest.php | 111 ++ .../Monolog/Handler/NewRelicHandlerTest.php | 200 +++ .../tests/Monolog/Handler/NullHandlerTest.php | 33 + .../Monolog/Handler/PHPConsoleHandlerTest.php | 273 +++ .../tests/Monolog/Handler/PsrHandlerTest.php | 50 + .../Monolog/Handler/PushoverHandlerTest.php | 141 ++ .../Monolog/Handler/RavenHandlerTest.php | 253 +++ .../Monolog/Handler/RedisHandlerTest.php | 127 ++ .../Handler/RotatingFileHandlerTest.php | 211 +++ .../Monolog/Handler/SamplingHandlerTest.php | 33 + .../Monolog/Handler/Slack/SlackRecordTest.php | 341 ++++ .../Monolog/Handler/SlackHandlerTest.php | 155 ++ .../Handler/SlackWebhookHandlerTest.php | 106 ++ .../Monolog/Handler/SlackbotHandlerTest.php | 47 + .../Monolog/Handler/SocketHandlerTest.php | 309 ++++ .../Monolog/Handler/StreamHandlerTest.php | 184 ++ .../Handler/SwiftMailerHandlerTest.php | 113 ++ .../Monolog/Handler/SyslogHandlerTest.php | 44 + .../Monolog/Handler/SyslogUdpHandlerTest.php | 65 + .../tests/Monolog/Handler/TestHandlerTest.php | 70 + .../tests/Monolog/Handler/UdpSocketTest.php | 64 + .../Handler/WhatFailureGroupHandlerTest.php | 121 ++ .../Handler/ZendMonitorHandlerTest.php | 69 + .../monolog/tests/Monolog/LoggerTest.php | 548 ++++++ .../Monolog/Processor/GitProcessorTest.php | 29 + .../Processor/IntrospectionProcessorTest.php | 123 ++ .../MemoryPeakUsageProcessorTest.php | 42 + .../Processor/MemoryUsageProcessorTest.php | 42 + .../Processor/MercurialProcessorTest.php | 41 + .../Processor/ProcessIdProcessorTest.php | 30 + .../Processor/PsrLogMessageProcessorTest.php | 43 + .../Monolog/Processor/TagProcessorTest.php | 49 + .../Monolog/Processor/UidProcessorTest.php | 33 + .../Monolog/Processor/WebProcessorTest.php | 113 ++ .../tests/Monolog/PsrLogCompatTest.php | 47 + .../monolog/tests/Monolog/RegistryTest.php | 153 ++ .../monolog/tests/Monolog/TestCase.php | 58 + src/vendor/nikic/fast-route/.hhconfig | 1 + src/vendor/nikic/fast-route/.travis.yml | 12 + src/vendor/nikic/fast-route/FastRoute.hhi | 126 ++ src/vendor/nikic/fast-route/LICENSE | 31 + src/vendor/nikic/fast-route/README.md | 273 +++ src/vendor/nikic/fast-route/composer.json | 21 + src/vendor/nikic/fast-route/phpunit.xml | 24 + .../fast-route/src/BadRouteException.php | 6 + .../nikic/fast-route/src/DataGenerator.php | 25 + .../src/DataGenerator/CharCountBased.php | 28 + .../src/DataGenerator/GroupCountBased.php | 28 + .../src/DataGenerator/GroupPosBased.php | 25 + .../src/DataGenerator/MarkBased.php | 25 + .../src/DataGenerator/RegexBasedAbstract.php | 144 ++ .../nikic/fast-route/src/Dispatcher.php | 25 + .../src/Dispatcher/CharCountBased.php | 28 + .../src/Dispatcher/GroupCountBased.php | 28 + .../src/Dispatcher/GroupPosBased.php | 30 + .../fast-route/src/Dispatcher/MarkBased.php | 28 + .../src/Dispatcher/RegexBasedAbstract.php | 80 + src/vendor/nikic/fast-route/src/Route.php | 38 + .../nikic/fast-route/src/RouteCollector.php | 46 + .../nikic/fast-route/src/RouteParser.php | 36 + .../nikic/fast-route/src/RouteParser/Std.php | 81 + src/vendor/nikic/fast-route/src/bootstrap.php | 12 + src/vendor/nikic/fast-route/src/functions.php | 70 + .../test/Dispatcher/CharCountBasedTest.php | 13 + .../test/Dispatcher/DispatcherTest.php | 561 ++++++ .../test/Dispatcher/GroupCountBasedTest.php | 13 + .../test/Dispatcher/GroupPosBasedTest.php | 13 + .../test/Dispatcher/MarkBasedTest.php | 20 + .../HackTypechecker/HackTypecheckerTest.php | 39 + .../HackTypechecker/fixtures/all_options.php | 29 + .../fixtures/empty_options.php | 11 + .../HackTypechecker/fixtures/no_options.php | 11 + .../fast-route/test/RouteParser/StdTest.php | 147 ++ .../nikic/fast-route/test/bootstrap.php | 11 + src/vendor/pimple/pimple/.gitignore | 3 + src/vendor/pimple/pimple/.travis.yml | 32 + src/vendor/pimple/pimple/CHANGELOG | 35 + src/vendor/pimple/pimple/LICENSE | 19 + src/vendor/pimple/pimple/README.rst | 201 +++ src/vendor/pimple/pimple/composer.json | 25 + .../pimple/pimple/ext/pimple/.gitignore | 30 + src/vendor/pimple/pimple/ext/pimple/README.md | 12 + src/vendor/pimple/pimple/ext/pimple/config.m4 | 63 + .../pimple/pimple/ext/pimple/config.w32 | 13 + .../pimple/pimple/ext/pimple/php_pimple.h | 121 ++ src/vendor/pimple/pimple/ext/pimple/pimple.c | 922 ++++++++++ .../pimple/pimple/ext/pimple/pimple_compat.h | 81 + .../pimple/pimple/ext/pimple/tests/001.phpt | 45 + .../pimple/pimple/ext/pimple/tests/002.phpt | 15 + .../pimple/pimple/ext/pimple/tests/003.phpt | 16 + .../pimple/pimple/ext/pimple/tests/004.phpt | 30 + .../pimple/pimple/ext/pimple/tests/005.phpt | 27 + .../pimple/pimple/ext/pimple/tests/006.phpt | 51 + .../pimple/pimple/ext/pimple/tests/007.phpt | 22 + .../pimple/pimple/ext/pimple/tests/008.phpt | 29 + .../pimple/pimple/ext/pimple/tests/009.phpt | 13 + .../pimple/pimple/ext/pimple/tests/010.phpt | 45 + .../pimple/pimple/ext/pimple/tests/011.phpt | 19 + .../pimple/pimple/ext/pimple/tests/012.phpt | 28 + .../pimple/pimple/ext/pimple/tests/013.phpt | 33 + .../pimple/pimple/ext/pimple/tests/014.phpt | 30 + .../pimple/pimple/ext/pimple/tests/015.phpt | 17 + .../pimple/pimple/ext/pimple/tests/016.phpt | 24 + .../pimple/pimple/ext/pimple/tests/017.phpt | 17 + .../pimple/pimple/ext/pimple/tests/017_1.phpt | 17 + .../pimple/pimple/ext/pimple/tests/018.phpt | 23 + .../pimple/pimple/ext/pimple/tests/019.phpt | 18 + .../pimple/pimple/ext/pimple/tests/bench.phpb | 51 + .../pimple/ext/pimple/tests/bench_shared.phpb | 25 + src/vendor/pimple/pimple/phpunit.xml.dist | 14 + .../pimple/pimple/src/Pimple/Container.php | 282 +++ .../src/Pimple/ServiceProviderInterface.php | 46 + .../src/Pimple/Tests/Fixtures/Invokable.php | 38 + .../Pimple/Tests/Fixtures/NonInvokable.php | 34 + .../Tests/Fixtures/PimpleServiceProvider.php | 54 + .../src/Pimple/Tests/Fixtures/Service.php | 35 + .../PimpleServiceProviderInterfaceTest.php | 76 + .../pimple/src/Pimple/Tests/PimpleTest.php | 440 +++++ src/vendor/psr/http-message/CHANGELOG.md | 36 + src/vendor/psr/http-message/LICENSE | 19 + src/vendor/psr/http-message/README.md | 13 + src/vendor/psr/http-message/composer.json | 26 + .../psr/http-message/src/MessageInterface.php | 187 ++ .../psr/http-message/src/RequestInterface.php | 129 ++ .../http-message/src/ResponseInterface.php | 68 + .../src/ServerRequestInterface.php | 261 +++ .../psr/http-message/src/StreamInterface.php | 158 ++ .../src/UploadedFileInterface.php | 123 ++ .../psr/http-message/src/UriInterface.php | 323 ++++ src/vendor/psr/log/.gitignore | 1 + src/vendor/psr/log/LICENSE | 19 + src/vendor/psr/log/Psr/Log/AbstractLogger.php | 128 ++ .../log/Psr/Log/InvalidArgumentException.php | 7 + src/vendor/psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 18 + .../psr/log/Psr/Log/LoggerAwareTrait.php | 26 + .../psr/log/Psr/Log/LoggerInterface.php | 123 ++ src/vendor/psr/log/Psr/Log/LoggerTrait.php | 140 ++ src/vendor/psr/log/Psr/Log/NullLogger.php | 28 + .../log/Psr/Log/Test/LoggerInterfaceTest.php | 140 ++ src/vendor/psr/log/README.md | 45 + src/vendor/psr/log/composer.json | 26 + src/vendor/slim/php-view/.gitignore | 6 + src/vendor/slim/php-view/.travis.yml | 11 + src/vendor/slim/php-view/LICENSE.md | 22 + src/vendor/slim/php-view/README.md | 87 + src/vendor/slim/php-view/composer.json | 25 + src/vendor/slim/php-view/phpunit.xml.dist | 25 + src/vendor/slim/php-view/src/PhpRenderer.php | 190 ++ .../slim/php-view/tests/PhpRendererTest.php | 115 ++ src/vendor/slim/php-view/tests/bootstrap.php | 9 + .../slim/php-view/tests/testException.php | 9 + .../slim/php-view/tests/testTemplate.php | 8 + src/vendor/slim/slim/LICENSE.md | 19 + src/vendor/slim/slim/Slim/App.php | 639 +++++++ .../slim/slim/Slim/CallableResolver.php | 90 + .../slim/Slim/CallableResolverAwareTrait.php | 47 + src/vendor/slim/slim/Slim/Collection.php | 202 +++ src/vendor/slim/slim/Slim/Container.php | 179 ++ .../slim/Slim/DefaultServicesProvider.php | 209 +++ .../slim/slim/Slim/DeferredCallable.php | 45 + .../Slim/Exception/ContainerException.php | 20 + .../ContainerValueNotFoundException.php | 20 + .../Exception/MethodNotAllowedException.php | 45 + .../slim/Slim/Exception/NotFoundException.php | 14 + .../slim/Slim/Exception/SlimException.php | 69 + .../slim/slim/Slim/Handlers/AbstractError.php | 99 ++ .../slim/Slim/Handlers/AbstractHandler.php | 59 + src/vendor/slim/slim/Slim/Handlers/Error.php | 206 +++ .../slim/slim/Slim/Handlers/NotAllowed.php | 147 ++ .../slim/slim/Slim/Handlers/NotFound.php | 126 ++ .../slim/slim/Slim/Handlers/PhpError.php | 205 +++ .../Handlers/Strategies/RequestResponse.php | 43 + .../Strategies/RequestResponseArgs.php | 42 + src/vendor/slim/slim/Slim/Http/Body.php | 22 + src/vendor/slim/slim/Slim/Http/Cookies.php | 195 +++ .../slim/slim/Slim/Http/Environment.php | 52 + src/vendor/slim/slim/Slim/Http/Headers.php | 222 +++ src/vendor/slim/slim/Slim/Http/Message.php | 304 ++++ src/vendor/slim/slim/Slim/Http/Request.php | 1190 +++++++++++++ .../slim/slim/Slim/Http/RequestBody.php | 27 + src/vendor/slim/slim/Slim/Http/Response.php | 473 +++++ src/vendor/slim/slim/Slim/Http/Stream.php | 450 +++++ .../slim/slim/Slim/Http/UploadedFile.php | 327 ++++ src/vendor/slim/slim/Slim/Http/Uri.php | 824 +++++++++ .../Interfaces/CallableResolverInterface.php | 27 + .../Slim/Interfaces/CollectionInterface.php | 32 + .../Slim/Interfaces/Http/CookiesInterface.php | 23 + .../Interfaces/Http/EnvironmentInterface.php | 20 + .../Slim/Interfaces/Http/HeadersInterface.php | 24 + .../InvocationStrategyInterface.php | 35 + .../Slim/Interfaces/RouteGroupInterface.php | 46 + .../slim/Slim/Interfaces/RouteInterface.php | 129 ++ .../slim/Slim/Interfaces/RouterInterface.php | 107 ++ .../slim/slim/Slim/MiddlewareAwareTrait.php | 120 ++ src/vendor/slim/slim/Slim/Routable.php | 106 ++ src/vendor/slim/slim/Slim/Route.php | 381 ++++ src/vendor/slim/slim/Slim/RouteGroup.php | 47 + src/vendor/slim/slim/Slim/Router.php | 455 +++++ src/vendor/slim/slim/composer.json | 57 + src/vendor/slim/twig-view/.gitignore | 2 + src/vendor/slim/twig-view/.travis.yml | 11 + src/vendor/slim/twig-view/CONTRIBUTING.md | 19 + src/vendor/slim/twig-view/LICENSE.md | 22 + src/vendor/slim/twig-view/README.md | 83 + src/vendor/slim/twig-view/composer.json | 28 + src/vendor/slim/twig-view/phpunit.xml.dist | 24 + src/vendor/slim/twig-view/src/Twig.php | 231 +++ .../slim/twig-view/src/TwigExtension.php | 67 + src/vendor/slim/twig-view/tests/TwigTest.php | 161 ++ .../tests/another/another_example.html | 1 + .../multi/directory/template/example.html | 1 + .../twig-view/tests/templates/example.html | 1 + src/vendor/twig/twig/.editorconfig | 18 + src/vendor/twig/twig/.gitignore | 5 + src/vendor/twig/twig/.travis.yml | 44 + src/vendor/twig/twig/CHANGELOG | 913 ++++++++++ src/vendor/twig/twig/LICENSE | 31 + src/vendor/twig/twig/README.rst | 15 + src/vendor/twig/twig/composer.json | 46 + src/vendor/twig/twig/doc/advanced.rst | 957 ++++++++++ src/vendor/twig/twig/doc/advanced_legacy.rst | 885 ++++++++++ src/vendor/twig/twig/doc/api.rst | 590 +++++++ src/vendor/twig/twig/doc/coding_standards.rst | 101 ++ src/vendor/twig/twig/doc/deprecated.rst | 214 +++ src/vendor/twig/twig/doc/filters/abs.rst | 18 + src/vendor/twig/twig/doc/filters/batch.rst | 51 + .../twig/twig/doc/filters/capitalize.rst | 11 + .../twig/doc/filters/convert_encoding.rst | 28 + src/vendor/twig/twig/doc/filters/date.rst | 100 ++ .../twig/twig/doc/filters/date_modify.rst | 23 + src/vendor/twig/twig/doc/filters/default.rst | 33 + src/vendor/twig/twig/doc/filters/escape.rst | 119 ++ src/vendor/twig/twig/doc/filters/first.rst | 25 + src/vendor/twig/twig/doc/filters/format.rst | 16 + src/vendor/twig/twig/doc/filters/index.rst | 37 + src/vendor/twig/twig/doc/filters/join.rst | 23 + .../twig/twig/doc/filters/json_encode.rst | 21 + src/vendor/twig/twig/doc/filters/keys.rst | 11 + src/vendor/twig/twig/doc/filters/last.rst | 25 + src/vendor/twig/twig/doc/filters/length.rst | 11 + src/vendor/twig/twig/doc/filters/lower.rst | 10 + src/vendor/twig/twig/doc/filters/merge.rst | 48 + src/vendor/twig/twig/doc/filters/nl2br.rst | 22 + .../twig/twig/doc/filters/number_format.rst | 48 + src/vendor/twig/twig/doc/filters/raw.rst | 36 + src/vendor/twig/twig/doc/filters/replace.rst | 19 + src/vendor/twig/twig/doc/filters/reverse.rst | 47 + src/vendor/twig/twig/doc/filters/round.rst | 37 + src/vendor/twig/twig/doc/filters/slice.rst | 71 + src/vendor/twig/twig/doc/filters/sort.rst | 18 + src/vendor/twig/twig/doc/filters/split.rst | 53 + .../twig/twig/doc/filters/striptags.rst | 15 + src/vendor/twig/twig/doc/filters/title.rst | 11 + src/vendor/twig/twig/doc/filters/trim.rst | 29 + src/vendor/twig/twig/doc/filters/upper.rst | 10 + .../twig/twig/doc/filters/url_encode.rst | 34 + .../twig/twig/doc/functions/attribute.rst | 26 + src/vendor/twig/twig/doc/functions/block.rst | 41 + .../twig/twig/doc/functions/constant.rst | 29 + src/vendor/twig/twig/doc/functions/cycle.rst | 28 + src/vendor/twig/twig/doc/functions/date.rst | 55 + src/vendor/twig/twig/doc/functions/dump.rst | 69 + .../twig/twig/doc/functions/include.rst | 84 + src/vendor/twig/twig/doc/functions/index.rst | 20 + src/vendor/twig/twig/doc/functions/max.rst | 20 + src/vendor/twig/twig/doc/functions/min.rst | 20 + src/vendor/twig/twig/doc/functions/parent.rst | 20 + src/vendor/twig/twig/doc/functions/random.rst | 29 + src/vendor/twig/twig/doc/functions/range.rst | 45 + src/vendor/twig/twig/doc/functions/source.rst | 32 + .../doc/functions/template_from_string.rst | 32 + src/vendor/twig/twig/doc/index.rst | 19 + src/vendor/twig/twig/doc/installation.rst | 116 ++ src/vendor/twig/twig/doc/internals.rst | 142 ++ src/vendor/twig/twig/doc/intro.rst | 85 + src/vendor/twig/twig/doc/recipes.rst | 568 ++++++ src/vendor/twig/twig/doc/tags/autoescape.rst | 81 + src/vendor/twig/twig/doc/tags/block.rst | 11 + src/vendor/twig/twig/doc/tags/do.rst | 12 + src/vendor/twig/twig/doc/tags/embed.rst | 178 ++ src/vendor/twig/twig/doc/tags/extends.rst | 272 +++ src/vendor/twig/twig/doc/tags/filter.rst | 21 + src/vendor/twig/twig/doc/tags/flush.rst | 17 + src/vendor/twig/twig/doc/tags/for.rst | 172 ++ src/vendor/twig/twig/doc/tags/from.rst | 8 + src/vendor/twig/twig/doc/tags/if.rst | 76 + src/vendor/twig/twig/doc/tags/import.rst | 57 + src/vendor/twig/twig/doc/tags/include.rst | 90 + src/vendor/twig/twig/doc/tags/index.rst | 25 + src/vendor/twig/twig/doc/tags/macro.rst | 103 ++ src/vendor/twig/twig/doc/tags/sandbox.rst | 30 + src/vendor/twig/twig/doc/tags/set.rst | 78 + src/vendor/twig/twig/doc/tags/spaceless.rst | 37 + src/vendor/twig/twig/doc/tags/use.rst | 124 ++ src/vendor/twig/twig/doc/tags/verbatim.rst | 24 + src/vendor/twig/twig/doc/tags/with.rst | 44 + src/vendor/twig/twig/doc/templates.rst | 908 ++++++++++ src/vendor/twig/twig/doc/tests/constant.rst | 22 + src/vendor/twig/twig/doc/tests/defined.rst | 30 + .../twig/twig/doc/tests/divisibleby.rst | 14 + src/vendor/twig/twig/doc/tests/empty.rst | 11 + src/vendor/twig/twig/doc/tests/even.rst | 10 + src/vendor/twig/twig/doc/tests/index.rst | 15 + src/vendor/twig/twig/doc/tests/iterable.rst | 19 + src/vendor/twig/twig/doc/tests/null.rst | 12 + src/vendor/twig/twig/doc/tests/odd.rst | 10 + src/vendor/twig/twig/doc/tests/sameas.rst | 14 + src/vendor/twig/twig/ext/twig/.gitignore | 30 + src/vendor/twig/twig/ext/twig/config.m4 | 8 + src/vendor/twig/twig/ext/twig/config.w32 | 8 + src/vendor/twig/twig/ext/twig/php_twig.h | 35 + src/vendor/twig/twig/ext/twig/twig.c | 1127 ++++++++++++ src/vendor/twig/twig/lib/Twig/Autoloader.php | 54 + .../twig/twig/lib/Twig/BaseNodeVisitor.php | 56 + .../twig/twig/lib/Twig/Cache/Filesystem.php | 98 ++ src/vendor/twig/twig/lib/Twig/Cache/Null.php | 50 + .../twig/twig/lib/Twig/CacheInterface.php | 56 + src/vendor/twig/twig/lib/Twig/Compiler.php | 281 +++ .../twig/twig/lib/Twig/CompilerInterface.php | 34 + src/vendor/twig/twig/lib/Twig/Environment.php | 1552 +++++++++++++++++ src/vendor/twig/twig/lib/Twig/Error.php | 365 ++++ .../twig/twig/lib/Twig/Error/Loader.php | 38 + .../twig/twig/lib/Twig/Error/Runtime.php | 20 + .../twig/twig/lib/Twig/Error/Syntax.php | 53 + .../twig/lib/Twig/ExistsLoaderInterface.php | 29 + .../twig/twig/lib/Twig/ExpressionParser.php | 739 ++++++++ src/vendor/twig/twig/lib/Twig/Extension.php | 89 + .../twig/twig/lib/Twig/Extension/Core.php | 1519 ++++++++++++++++ .../twig/twig/lib/Twig/Extension/Debug.php | 65 + .../twig/twig/lib/Twig/Extension/Escaper.php | 110 ++ .../lib/Twig/Extension/GlobalsInterface.php | 22 + .../Twig/Extension/InitRuntimeInterface.php | 22 + .../twig/lib/Twig/Extension/Optimizer.php | 33 + .../twig/twig/lib/Twig/Extension/Profiler.php | 46 + .../twig/twig/lib/Twig/Extension/Sandbox.php | 101 ++ .../twig/twig/lib/Twig/Extension/Staging.php | 94 + .../twig/lib/Twig/Extension/StringLoader.php | 45 + .../twig/twig/lib/Twig/ExtensionInterface.php | 87 + .../Twig/FileExtensionEscapingStrategy.php | 58 + src/vendor/twig/twig/lib/Twig/Filter.php | 84 + .../twig/twig/lib/Twig/Filter/Function.php | 40 + .../twig/twig/lib/Twig/Filter/Method.php | 42 + src/vendor/twig/twig/lib/Twig/Filter/Node.php | 42 + .../twig/lib/Twig/FilterCallableInterface.php | 24 + .../twig/twig/lib/Twig/FilterInterface.php | 43 + src/vendor/twig/twig/lib/Twig/Function.php | 74 + .../twig/twig/lib/Twig/Function/Function.php | 41 + .../twig/twig/lib/Twig/Function/Method.php | 43 + .../twig/twig/lib/Twig/Function/Node.php | 42 + .../lib/Twig/FunctionCallableInterface.php | 24 + .../twig/twig/lib/Twig/FunctionInterface.php | 40 + src/vendor/twig/twig/lib/Twig/Lexer.php | 425 +++++ .../twig/twig/lib/Twig/LexerInterface.php | 32 + .../twig/twig/lib/Twig/Loader/Array.php | 110 ++ .../twig/twig/lib/Twig/Loader/Chain.php | 164 ++ .../twig/twig/lib/Twig/Loader/Filesystem.php | 303 ++++ .../twig/twig/lib/Twig/Loader/String.php | 73 + .../twig/twig/lib/Twig/LoaderInterface.php | 55 + src/vendor/twig/twig/lib/Twig/Markup.php | 37 + src/vendor/twig/twig/lib/Twig/Node.php | 253 +++ .../twig/twig/lib/Twig/Node/AutoEscape.php | 34 + src/vendor/twig/twig/lib/Twig/Node/Block.php | 39 + .../twig/lib/Twig/Node/BlockReference.php | 32 + src/vendor/twig/twig/lib/Twig/Node/Body.php | 19 + .../twig/twig/lib/Twig/Node/CheckSecurity.php | 78 + src/vendor/twig/twig/lib/Twig/Node/Do.php | 33 + src/vendor/twig/twig/lib/Twig/Node/Embed.php | 44 + .../twig/twig/lib/Twig/Node/Expression.php | 20 + .../twig/lib/Twig/Node/Expression/Array.php | 81 + .../lib/Twig/Node/Expression/AssignName.php | 23 + .../twig/lib/Twig/Node/Expression/Binary.php | 35 + .../lib/Twig/Node/Expression/Binary/Add.php | 18 + .../lib/Twig/Node/Expression/Binary/And.php | 18 + .../Node/Expression/Binary/BitwiseAnd.php | 18 + .../Twig/Node/Expression/Binary/BitwiseOr.php | 18 + .../Node/Expression/Binary/BitwiseXor.php | 18 + .../Twig/Node/Expression/Binary/Concat.php | 18 + .../lib/Twig/Node/Expression/Binary/Div.php | 18 + .../Twig/Node/Expression/Binary/EndsWith.php | 30 + .../lib/Twig/Node/Expression/Binary/Equal.php | 17 + .../Twig/Node/Expression/Binary/FloorDiv.php | 24 + .../Twig/Node/Expression/Binary/Greater.php | 17 + .../Node/Expression/Binary/GreaterEqual.php | 17 + .../lib/Twig/Node/Expression/Binary/In.php | 28 + .../lib/Twig/Node/Expression/Binary/Less.php | 17 + .../Twig/Node/Expression/Binary/LessEqual.php | 17 + .../Twig/Node/Expression/Binary/Matches.php | 28 + .../lib/Twig/Node/Expression/Binary/Mod.php | 18 + .../lib/Twig/Node/Expression/Binary/Mul.php | 18 + .../Twig/Node/Expression/Binary/NotEqual.php | 17 + .../lib/Twig/Node/Expression/Binary/NotIn.php | 28 + .../lib/Twig/Node/Expression/Binary/Or.php | 18 + .../lib/Twig/Node/Expression/Binary/Power.php | 32 + .../lib/Twig/Node/Expression/Binary/Range.php | 28 + .../Node/Expression/Binary/StartsWith.php | 30 + .../lib/Twig/Node/Expression/Binary/Sub.php | 18 + .../Twig/Node/Expression/BlockReference.php | 91 + .../twig/lib/Twig/Node/Expression/Call.php | 289 +++ .../lib/Twig/Node/Expression/Conditional.php | 31 + .../lib/Twig/Node/Expression/Constant.php | 23 + .../Node/Expression/ExtensionReference.php | 32 + .../twig/lib/Twig/Node/Expression/Filter.php | 39 + .../Twig/Node/Expression/Filter/Default.php | 43 + .../lib/Twig/Node/Expression/Function.php | 43 + .../twig/lib/Twig/Node/Expression/GetAttr.php | 68 + .../lib/Twig/Node/Expression/MethodCall.php | 41 + .../twig/lib/Twig/Node/Expression/Name.php | 100 ++ .../lib/Twig/Node/Expression/NullCoalesce.php | 46 + .../twig/lib/Twig/Node/Expression/Parent.php | 42 + .../lib/Twig/Node/Expression/TempName.php | 26 + .../twig/lib/Twig/Node/Expression/Test.php | 40 + .../Twig/Node/Expression/Test/Constant.php | 46 + .../lib/Twig/Node/Expression/Test/Defined.php | 59 + .../Twig/Node/Expression/Test/Divisibleby.php | 33 + .../lib/Twig/Node/Expression/Test/Even.php | 32 + .../lib/Twig/Node/Expression/Test/Null.php | 31 + .../lib/Twig/Node/Expression/Test/Odd.php | 32 + .../lib/Twig/Node/Expression/Test/Sameas.php | 29 + .../twig/lib/Twig/Node/Expression/Unary.php | 27 + .../lib/Twig/Node/Expression/Unary/Neg.php | 18 + .../lib/Twig/Node/Expression/Unary/Not.php | 18 + .../lib/Twig/Node/Expression/Unary/Pos.php | 18 + src/vendor/twig/twig/lib/Twig/Node/Flush.php | 31 + src/vendor/twig/twig/lib/Twig/Node/For.php | 111 ++ .../twig/twig/lib/Twig/Node/ForLoop.php | 50 + src/vendor/twig/twig/lib/Twig/Node/If.php | 66 + src/vendor/twig/twig/lib/Twig/Node/Import.php | 49 + .../twig/twig/lib/Twig/Node/Include.php | 88 + src/vendor/twig/twig/lib/Twig/Node/Macro.php | 123 ++ src/vendor/twig/twig/lib/Twig/Node/Module.php | 459 +++++ src/vendor/twig/twig/lib/Twig/Node/Print.php | 34 + .../twig/twig/lib/Twig/Node/Sandbox.php | 42 + .../twig/lib/Twig/Node/SandboxedPrint.php | 49 + src/vendor/twig/twig/lib/Twig/Node/Set.php | 96 + .../twig/twig/lib/Twig/Node/SetTemp.php | 35 + .../twig/twig/lib/Twig/Node/Spaceless.php | 35 + src/vendor/twig/twig/lib/Twig/Node/Text.php | 34 + src/vendor/twig/twig/lib/Twig/Node/With.php | 62 + .../twig/twig/lib/Twig/NodeInterface.php | 32 + .../twig/lib/Twig/NodeOutputInterface.php | 19 + .../twig/twig/lib/Twig/NodeTraverser.php | 82 + .../twig/lib/Twig/NodeVisitor/Escaper.php | 161 ++ .../twig/lib/Twig/NodeVisitor/Optimizer.php | 260 +++ .../lib/Twig/NodeVisitor/SafeAnalysis.php | 157 ++ .../twig/lib/Twig/NodeVisitor/Sandbox.php | 84 + .../twig/lib/Twig/NodeVisitorInterface.php | 41 + src/vendor/twig/twig/lib/Twig/Parser.php | 409 +++++ .../twig/twig/lib/Twig/ParserInterface.php | 29 + .../lib/Twig/Profiler/Dumper/Blackfire.php | 70 + .../twig/lib/Twig/Profiler/Dumper/Html.php | 45 + .../twig/lib/Twig/Profiler/Dumper/Text.php | 70 + .../lib/Twig/Profiler/Node/EnterProfile.php | 40 + .../lib/Twig/Profiler/Node/LeaveProfile.php | 34 + .../Twig/Profiler/NodeVisitor/Profiler.php | 74 + .../twig/twig/lib/Twig/Profiler/Profile.php | 162 ++ .../twig/lib/Twig/RuntimeLoaderInterface.php | 27 + .../twig/lib/Twig/Sandbox/SecurityError.php | 19 + .../Sandbox/SecurityNotAllowedFilterError.php | 31 + .../SecurityNotAllowedFunctionError.php | 31 + .../Sandbox/SecurityNotAllowedMethodError.php | 38 + .../SecurityNotAllowedPropertyError.php | 38 + .../Sandbox/SecurityNotAllowedTagError.php | 31 + .../twig/lib/Twig/Sandbox/SecurityPolicy.php | 123 ++ .../Twig/Sandbox/SecurityPolicyInterface.php | 24 + .../twig/twig/lib/Twig/SimpleFilter.php | 117 ++ .../twig/twig/lib/Twig/SimpleFunction.php | 107 ++ src/vendor/twig/twig/lib/Twig/SimpleTest.php | 69 + src/vendor/twig/twig/lib/Twig/Source.php | 51 + .../lib/Twig/SourceContextLoaderInterface.php | 31 + src/vendor/twig/twig/lib/Twig/Template.php | 707 ++++++++ .../twig/twig/lib/Twig/TemplateInterface.php | 48 + .../twig/twig/lib/Twig/TemplateWrapper.php | 131 ++ src/vendor/twig/twig/lib/Twig/Test.php | 37 + .../twig/twig/lib/Twig/Test/Function.php | 38 + .../lib/Twig/Test/IntegrationTestCase.php | 228 +++ src/vendor/twig/twig/lib/Twig/Test/Method.php | 40 + src/vendor/twig/twig/lib/Twig/Test/Node.php | 40 + .../twig/twig/lib/Twig/Test/NodeTestCase.php | 68 + .../twig/lib/Twig/TestCallableInterface.php | 22 + .../twig/twig/lib/Twig/TestInterface.php | 27 + src/vendor/twig/twig/lib/Twig/Token.php | 205 +++ src/vendor/twig/twig/lib/Twig/TokenParser.php | 31 + .../twig/lib/Twig/TokenParser/AutoEscape.php | 81 + .../twig/twig/lib/Twig/TokenParser/Block.php | 71 + .../twig/twig/lib/Twig/TokenParser/Do.php | 32 + .../twig/twig/lib/Twig/TokenParser/Embed.php | 65 + .../twig/lib/Twig/TokenParser/Extends.php | 44 + .../twig/twig/lib/Twig/TokenParser/Filter.php | 51 + .../twig/twig/lib/Twig/TokenParser/Flush.php | 32 + .../twig/twig/lib/Twig/TokenParser/For.php | 125 ++ .../twig/twig/lib/Twig/TokenParser/From.php | 64 + .../twig/twig/lib/Twig/TokenParser/If.php | 84 + .../twig/twig/lib/Twig/TokenParser/Import.php | 39 + .../twig/lib/Twig/TokenParser/Include.php | 65 + .../twig/twig/lib/Twig/TokenParser/Macro.php | 58 + .../twig/lib/Twig/TokenParser/Sandbox.php | 59 + .../twig/twig/lib/Twig/TokenParser/Set.php | 73 + .../twig/lib/Twig/TokenParser/Spaceless.php | 49 + .../twig/twig/lib/Twig/TokenParser/Use.php | 66 + .../twig/twig/lib/Twig/TokenParser/With.php | 50 + .../twig/twig/lib/Twig/TokenParserBroker.php | 120 ++ .../lib/Twig/TokenParserBrokerInterface.php | 44 + .../twig/lib/Twig/TokenParserInterface.php | 39 + src/vendor/twig/twig/lib/Twig/TokenStream.php | 194 +++ .../lib/Twig/Util/DeprecationCollector.php | 84 + .../lib/Twig/Util/TemplateDirIterator.php | 26 + src/vendor/twig/twig/phpunit.xml.dist | 33 + .../twig/test/Twig/Tests/AutoloaderTest.php | 24 + .../test/Twig/Tests/Cache/FilesystemTest.php | 193 ++ .../twig/test/Twig/Tests/CompilerTest.php | 33 + .../test/Twig/Tests/CustomExtensionTest.php | 88 + .../twig/test/Twig/Tests/EnvironmentTest.php | 652 +++++++ .../twig/twig/test/Twig/Tests/ErrorTest.php | 211 +++ .../test/Twig/Tests/ExpressionParserTest.php | 373 ++++ .../test/Twig/Tests/Extension/CoreTest.php | 158 ++ .../test/Twig/Tests/Extension/SandboxTest.php | 304 ++++ .../twig/test/Twig/Tests/FileCachingTest.php | 63 + .../FileExtensionEscapingStrategyTest.php | 51 + .../twig/test/Twig/Tests/FilesystemHelper.php | 30 + .../Twig/Tests/Fixtures/autoescape/block.test | 21 + .../Twig/Tests/Fixtures/autoescape/name.test | 18 + .../test/Twig/Tests/Fixtures/errors/base.html | 1 + .../Twig/Tests/Fixtures/errors/index.html | 7 + .../child_contents_outside_blocks.test | 15 + ...ltiline_array_with_undefined_variable.test | 18 + ...e_array_with_undefined_variable_again.test | 18 + ...line_function_with_undefined_variable.test | 12 + ...tiline_function_with_unknown_argument.test | 9 + ...multiline_tag_with_undefined_variable.test | 12 + .../syntax_error_in_reused_template.test | 10 + .../Fixtures/exceptions/unclosed_tag.test | 20 + .../Fixtures/exceptions/undefined_parent.test | 8 + .../undefined_template_in_child_template.test | 15 + .../Fixtures/exceptions/undefined_trait.test | 9 + .../Tests/Fixtures/expressions/_self.test | 8 + .../Tests/Fixtures/expressions/array.test | 61 + .../Fixtures/expressions/array_call.test | 14 + .../Tests/Fixtures/expressions/binary.test | 46 + .../Tests/Fixtures/expressions/bitwise.test | 14 + .../Fixtures/expressions/comparison.test | 14 + .../Fixtures/expressions/divisibleby.test | 17 + .../Tests/Fixtures/expressions/dotdot.test | 20 + .../Tests/Fixtures/expressions/ends_with.test | 26 + .../Tests/Fixtures/expressions/grouping.test | 8 + .../Tests/Fixtures/expressions/literals.test | 22 + .../Fixtures/expressions/magic_call.test | 27 + .../Tests/Fixtures/expressions/matches.test | 12 + .../Fixtures/expressions/method_call.test | 28 + .../expressions/negative_numbers.test | 18 + .../expressions/operators_as_variables.test | 16 + .../Tests/Fixtures/expressions/postfix.test | 22 + .../Tests/Fixtures/expressions/power.test | 20 + .../Tests/Fixtures/expressions/sameas.test | 21 + .../Fixtures/expressions/starts_with.test | 27 + .../Tests/Fixtures/expressions/strings.test | 10 + .../expressions/ternary_operator.test | 18 + .../expressions/ternary_operator_noelse.test | 10 + .../expressions/ternary_operator_nothen.test | 10 + .../two_word_operators_as_variables.test | 8 + .../Tests/Fixtures/expressions/unary.test | 12 + .../expressions/unary_macro_arguments.test | 22 + .../expressions/unary_precedence.test | 14 + .../test/Twig/Tests/Fixtures/filters/abs.test | 30 + .../Twig/Tests/Fixtures/filters/batch.test | 31 + .../Tests/Fixtures/filters/batch_float.test | 29 + .../filters/batch_with_empty_fill.test | 37 + .../filters/batch_with_exact_elements.test | 33 + .../Fixtures/filters/batch_with_fill.test | 37 + .../Fixtures/filters/batch_with_keys.test | 10 + .../filters/batch_with_zero_elements.test | 10 + .../Fixtures/filters/convert_encoding.test | 10 + .../Twig/Tests/Fixtures/filters/date.test | 90 + .../Fixtures/filters/date_default_format.test | 14 + .../filters/date_default_format_interval.test | 16 + .../Fixtures/filters/date_immutable.test | 37 + .../Tests/Fixtures/filters/date_interval.test | 19 + .../Tests/Fixtures/filters/date_modify.test | 14 + .../Fixtures/filters/date_namedargs.test | 13 + .../Twig/Tests/Fixtures/filters/default.test | 150 ++ .../Fixtures/filters/dynamic_filter.test | 10 + .../Twig/Tests/Fixtures/filters/escape.test | 8 + .../Fixtures/filters/escape_html_attr.test | 8 + .../filters/escape_non_supported_charset.test | 8 + .../Twig/Tests/Fixtures/filters/first.test | 17 + .../Tests/Fixtures/filters/force_escape.test | 18 + .../Twig/Tests/Fixtures/filters/format.test | 8 + .../Twig/Tests/Fixtures/filters/join.test | 12 + .../Tests/Fixtures/filters/json_encode.test | 12 + .../Twig/Tests/Fixtures/filters/last.test | 17 + .../Twig/Tests/Fixtures/filters/length.test | 14 + .../Tests/Fixtures/filters/length_utf8.test | 12 + .../Twig/Tests/Fixtures/filters/merge.test | 18 + .../Twig/Tests/Fixtures/filters/nl2br.test | 14 + .../Tests/Fixtures/filters/number_format.test | 18 + .../filters/number_format_default.test | 21 + .../Twig/Tests/Fixtures/filters/replace.test | 12 + .../Fixtures/filters/replace_invalid_arg.test | 8 + .../Twig/Tests/Fixtures/filters/reverse.test | 18 + .../Twig/Tests/Fixtures/filters/round.test | 22 + .../Twig/Tests/Fixtures/filters/slice.test | 54 + .../Twig/Tests/Fixtures/filters/sort.test | 12 + .../Tests/Fixtures/filters/special_chars.test | 8 + .../Twig/Tests/Fixtures/filters/split.test | 20 + .../Tests/Fixtures/filters/split_utf8.test | 24 + .../Tests/Fixtures/filters/static_calls.test | 10 + .../Twig/Tests/Fixtures/filters/trim.test | 12 + .../Tests/Fixtures/filters/urlencode.test | 16 + .../filters/urlencode_deprecated.test | 16 + .../Tests/Fixtures/functions/attribute.test | 18 + .../Twig/Tests/Fixtures/functions/block.test | 12 + .../functions/block_with_template.test | 22 + .../functions/block_without_name.test | 12 + .../Tests/Fixtures/functions/constant.test | 10 + .../Twig/Tests/Fixtures/functions/cycle.test | 16 + .../Twig/Tests/Fixtures/functions/date.test | 25 + .../Fixtures/functions/date_namedargs.test | 11 + .../Twig/Tests/Fixtures/functions/dump.test | 16 + .../Tests/Fixtures/functions/dump_array.test | 19 + .../Fixtures/functions/dynamic_function.test | 10 + .../functions/include/assignment.test | 13 + .../functions/include/autoescaping.test | 10 + .../Fixtures/functions/include/basic.test | 17 + .../functions/include/expression.test | 17 + .../functions/include/ignore_missing.test | 10 + .../Fixtures/functions/include/missing.test | 8 + .../functions/include/missing_nested.test | 16 + .../Fixtures/functions/include/sandbox.test | 13 + .../functions/include/sandbox_disabling.test | 16 + .../sandbox_disabling_ignore_missing.test | 13 + .../functions/include/template_instance.test | 10 + .../functions/include/templates_as_array.test | 12 + .../functions/include/with_context.test | 16 + .../functions/include/with_variables.test | 12 + .../Tests/Fixtures/functions/magic_call.test | 8 + .../Fixtures/functions/magic_call53.test | 12 + .../Twig/Tests/Fixtures/functions/max.test | 12 + .../Twig/Tests/Fixtures/functions/min.test | 12 + .../Twig/Tests/Fixtures/functions/range.test | 8 + .../recursive_block_with_inheritance.test | 21 + .../Twig/Tests/Fixtures/functions/source.test | 17 + .../Fixtures/functions/special_chars.test | 8 + .../Fixtures/functions/static_calls.test | 10 + .../functions/template_from_string.test | 15 + .../Tests/Fixtures/macros/default_values.test | 16 + .../Tests/Fixtures/macros/nested_calls.test | 18 + .../Fixtures/macros/reserved_variables.test | 14 + .../Twig/Tests/Fixtures/macros/simple.test | 22 + .../Twig/Tests/Fixtures/macros/varargs.test | 21 + .../Fixtures/macros/varargs_argument.test | 7 + .../Tests/Fixtures/macros/with_filters.test | 14 + .../regression/combined_debug_info.test | 15 + .../Fixtures/regression/empty_token.test | 8 + .../Tests/Fixtures/regression/issue_1143.test | 23 + .../Fixtures/regression/multi_word_tests.test | 10 + .../regression/simple_xml_element.test | 19 + .../regression/strings_like_numbers.test | 8 + .../Tests/Fixtures/tags/autoescape/basic.test | 26 + .../Fixtures/tags/autoescape/blocks.test | 12 + .../tags/autoescape/double_escaping.test | 10 + .../Fixtures/tags/autoescape/functions.test | 83 + .../Fixtures/tags/autoescape/literal.test | 45 + .../Fixtures/tags/autoescape/nested.test | 26 + .../Fixtures/tags/autoescape/objects.test | 26 + .../Tests/Fixtures/tags/autoescape/raw.test | 10 + .../tags/autoescape/strategy.legacy.test | 11 + .../Fixtures/tags/autoescape/strategy.test | 11 + .../Tests/Fixtures/tags/autoescape/type.test | 69 + .../tags/autoescape/with_filters.test | 131 ++ .../autoescape/with_filters_arguments.test | 23 + .../autoescape/with_pre_escape_filters.test | 68 + .../with_preserves_safety_filters.test | 50 + .../Twig/Tests/Fixtures/tags/block/basic.test | 11 + .../tags/block/block_unique_name.test | 11 + .../Fixtures/tags/block/special_chars.test | 10 + .../Twig/Tests/Fixtures/tags/embed/basic.test | 35 + .../tags/embed/complex_dynamic_parent.test | 35 + .../Fixtures/tags/embed/dynamic_parent.test | 35 + .../Tests/Fixtures/tags/embed/error_line.test | 16 + .../Tests/Fixtures/tags/embed/multiple.test | 50 + .../Tests/Fixtures/tags/embed/nested.test | 42 + .../Fixtures/tags/embed/with_extends.test | 60 + .../Tests/Fixtures/tags/filter/basic.test | 10 + .../Fixtures/tags/filter/json_encode.test | 8 + .../Tests/Fixtures/tags/filter/multiple.test | 10 + .../Tests/Fixtures/tags/filter/nested.test | 16 + .../Fixtures/tags/filter/with_for_tag.test | 13 + .../Fixtures/tags/filter/with_if_tag.test | 29 + .../Tests/Fixtures/tags/for/condition.test | 14 + .../Twig/Tests/Fixtures/tags/for/context.test | 18 + .../Twig/Tests/Fixtures/tags/for/else.test | 23 + .../Fixtures/tags/for/inner_variables.test | 17 + .../Twig/Tests/Fixtures/tags/for/keys.test | 11 + .../Fixtures/tags/for/keys_and_values.test | 11 + .../Tests/Fixtures/tags/for/loop_context.test | 19 + .../Fixtures/tags/for/loop_context_local.test | 10 + .../Fixtures/tags/for/loop_not_defined.test | 10 + .../tags/for/loop_not_defined_cond.test | 9 + .../Tests/Fixtures/tags/for/nested_else.test | 17 + .../Twig/Tests/Fixtures/tags/for/objects.test | 43 + .../Fixtures/tags/for/objects_countable.test | 47 + .../Tests/Fixtures/tags/for/recursive.test | 18 + .../Twig/Tests/Fixtures/tags/for/values.test | 11 + .../test/Twig/Tests/Fixtures/tags/from.test | 14 + .../Twig/Tests/Fixtures/tags/if/basic.test | 22 + .../Tests/Fixtures/tags/if/expression.test | 22 + .../Tests/Fixtures/tags/include/basic.test | 16 + .../Fixtures/tags/include/expression.test | 16 + .../Fixtures/tags/include/ignore_missing.test | 10 + .../Tests/Fixtures/tags/include/missing.test | 8 + .../Fixtures/tags/include/missing_nested.test | 16 + .../Tests/Fixtures/tags/include/only.test | 16 + .../tags/include/template_instance.test | 10 + .../tags/include/templates_as_array.test | 12 + .../Fixtures/tags/include/with_variables.test | 12 + .../Fixtures/tags/inheritance/basic.test | 14 + .../Fixtures/tags/inheritance/block_expr.test | 32 + .../tags/inheritance/block_expr2.test | 34 + .../tags/inheritance/conditional.test | 14 + .../Fixtures/tags/inheritance/dynamic.test | 14 + .../Fixtures/tags/inheritance/empty.test | 10 + .../tags/inheritance/extends_as_array.test | 12 + .../extends_as_array_with_empty_name.test | 12 + .../extends_as_array_with_null_name.test | 12 + .../Fixtures/tags/inheritance/multiple.test | 12 + .../tags/inheritance/multiple_dynamic.test | 22 + .../tags/inheritance/nested_blocks.test | 22 + .../nested_blocks_parent_only.test | 15 + .../tags/inheritance/nested_inheritance.test | 16 + .../Fixtures/tags/inheritance/parent.test | 12 + .../tags/inheritance/parent_change.test | 16 + .../tags/inheritance/parent_in_a_block.test | 8 + .../tags/inheritance/parent_isolation.test | 20 + .../tags/inheritance/parent_nested.test | 28 + .../inheritance/parent_without_extends.test | 8 + .../parent_without_extends_but_traits.test | 14 + .../tags/inheritance/template_instance.test | 14 + .../Tests/Fixtures/tags/inheritance/use.test | 44 + .../Twig/Tests/Fixtures/tags/macro/basic.test | 17 + .../Fixtures/tags/macro/endmacro_name.test | 16 + .../Tests/Fixtures/tags/macro/external.test | 17 + .../Twig/Tests/Fixtures/tags/macro/from.test | 18 + .../tags/macro/from_with_reserved_name.test | 9 + .../Tests/Fixtures/tags/macro/global.test | 14 + .../tags/macro/import_with_reserved_nam.test | 11 + .../Fixtures/tags/macro/reserved_name.test | 10 + .../Fixtures/tags/macro/self_import.test | 17 + .../Fixtures/tags/macro/special_chars.test | 14 + .../Fixtures/tags/macro/super_globals.test | 14 + .../Tests/Fixtures/tags/raw/basic.legacy.test | 10 + .../tags/raw/mixed_usage_with_raw.legacy.test | 10 + .../tags/raw/whitespace_control.legacy.test | 56 + .../Fixtures/tags/sandbox/not_valid1.test | 11 + .../Fixtures/tags/sandbox/not_valid2.test | 14 + .../Tests/Fixtures/tags/sandbox/simple.test | 22 + .../Twig/Tests/Fixtures/tags/set/basic.test | 20 + .../Fixtures/tags/set/capture-empty.test | 9 + .../Twig/Tests/Fixtures/tags/set/capture.test | 10 + .../Tests/Fixtures/tags/set/expression.test | 12 + .../Tests/Fixtures/tags/spaceless/simple.test | 12 + .../Tests/Fixtures/tags/special_chars.test | 8 + .../Twig/Tests/Fixtures/tags/trim_block.test | 74 + .../Twig/Tests/Fixtures/tags/use/aliases.test | 12 + .../Twig/Tests/Fixtures/tags/use/basic.test | 12 + .../Twig/Tests/Fixtures/tags/use/deep.test | 22 + .../Tests/Fixtures/tags/use/deep_empty.test | 10 + .../Tests/Fixtures/tags/use/inheritance.test | 25 + .../Tests/Fixtures/tags/use/inheritance2.test | 24 + .../Tests/Fixtures/tags/use/multiple.test | 21 + .../Fixtures/tags/use/multiple_aliases.test | 23 + .../Tests/Fixtures/tags/use/parent_block.test | 24 + .../Fixtures/tags/use/parent_block2.test | 24 + .../Fixtures/tags/use/parent_block3.test | 38 + .../Tests/Fixtures/tags/verbatim/basic.test | 10 + .../tags/verbatim/mixed_usage_with_raw.test | 10 + .../tags/verbatim/whitespace_control.test | 56 + .../Twig/Tests/Fixtures/tags/with/basic.test | 13 + .../Tests/Fixtures/tags/with/expression.test | 10 + .../Twig/Tests/Fixtures/tags/with/nested.test | 15 + .../Fixtures/tags/with/with_no_hash.test | 10 + .../Tests/Fixtures/tags/with/with_only.test | 10 + .../test/Twig/Tests/Fixtures/tests/array.test | 24 + .../Twig/Tests/Fixtures/tests/constant.test | 14 + .../Twig/Tests/Fixtures/tests/defined.test | 129 ++ .../Fixtures/tests/defined_for_attribute.test | 35 + .../Fixtures/tests/defined_for_blocks.test | 38 + .../defined_for_blocks_with_template.test | 17 + .../Fixtures/tests/defined_for_constants.test | 14 + .../test/Twig/Tests/Fixtures/tests/empty.test | 45 + .../test/Twig/Tests/Fixtures/tests/even.test | 14 + .../test/Twig/Tests/Fixtures/tests/in.test | 128 ++ .../Tests/Fixtures/tests/in_with_objects.test | 19 + .../Twig/Tests/Fixtures/tests/iterable.test | 19 + .../Tests/Fixtures/tests/null_coalesce.test | 30 + .../test/Twig/Tests/Fixtures/tests/odd.test | 10 + .../twig/test/Twig/Tests/IntegrationTest.php | 254 +++ .../autoescape/filename.legacy.test | 18 + .../functions/undefined_block.legacy.test | 12 + .../Tests/LegacyFixtures/test.legacy.test | 8 + .../test/Twig/Tests/LegacyIntegrationTest.php | 54 + .../twig/twig/test/Twig/Tests/LexerTest.php | 311 ++++ .../twig/test/Twig/Tests/Loader/ArrayTest.php | 111 ++ .../twig/test/Twig/Tests/Loader/ChainTest.php | 124 ++ .../test/Twig/Tests/Loader/FilesystemTest.php | 226 +++ .../array_inheritance_empty_parent.html.twig | 3 + ...y_inheritance_nonexistent_parent.html.twig | 3 + .../array_inheritance_null_parent.html.twig | 3 + .../array_inheritance_valid_parent.html.twig | 3 + .../Fixtures/inheritance/parent.html.twig | 1 + .../inheritance/spare_parent.html.twig | 1 + .../Tests/Loader/Fixtures/named/index.html | 1 + .../Loader/Fixtures/named_bis/index.html | 1 + .../Loader/Fixtures/named_final/index.html | 1 + .../Fixtures/named_quater/named_absolute.html | 1 + .../Loader/Fixtures/named_ter/index.html | 1 + .../Tests/Loader/Fixtures/normal/index.html | 1 + .../Loader/Fixtures/normal_bis/index.html | 1 + .../Loader/Fixtures/normal_final/index.html | 1 + .../Loader/Fixtures/normal_ter/index.html | 1 + .../Loader/Fixtures/phar/phar-sample.phar | Bin 0 -> 6786 bytes .../Fixtures/themes/theme1/blocks.html.twig | 3 + .../Fixtures/themes/theme2/blocks.html.twig | 3 + .../test/Twig/Tests/NativeExtensionTest.php | 36 + .../test/Twig/Tests/Node/AutoEscapeTest.php | 32 + .../Twig/Tests/Node/BlockReferenceTest.php | 31 + .../twig/test/Twig/Tests/Node/BlockTest.php | 39 + .../twig/twig/test/Twig/Tests/Node/DoTest.php | 32 + .../Twig/Tests/Node/Expression/ArrayTest.php | 37 + .../Tests/Node/Expression/AssignNameTest.php | 29 + .../Tests/Node/Expression/Binary/AddTest.php | 34 + .../Tests/Node/Expression/Binary/AndTest.php | 34 + .../Node/Expression/Binary/ConcatTest.php | 34 + .../Tests/Node/Expression/Binary/DivTest.php | 34 + .../Node/Expression/Binary/FloorDivTest.php | 34 + .../Tests/Node/Expression/Binary/ModTest.php | 34 + .../Tests/Node/Expression/Binary/MulTest.php | 34 + .../Tests/Node/Expression/Binary/OrTest.php | 34 + .../Tests/Node/Expression/Binary/SubTest.php | 34 + .../Twig/Tests/Node/Expression/CallTest.php | 147 ++ .../Tests/Node/Expression/ConditionalTest.php | 38 + .../Tests/Node/Expression/ConstantTest.php | 30 + .../Twig/Tests/Node/Expression/FilterTest.php | 154 ++ .../Tests/Node/Expression/FunctionTest.php | 110 ++ .../Tests/Node/Expression/GetAttrTest.php | 50 + .../Twig/Tests/Node/Expression/NameTest.php | 43 + .../Node/Expression/NullCoalesceTest.php | 31 + .../Node/Expression/PHP53/FilterInclude.php | 6 + .../Node/Expression/PHP53/FunctionInclude.php | 6 + .../Node/Expression/PHP53/TestInclude.php | 6 + .../Twig/Tests/Node/Expression/ParentTest.php | 28 + .../Twig/Tests/Node/Expression/TestTest.php | 82 + .../Tests/Node/Expression/Unary/NegTest.php | 32 + .../Tests/Node/Expression/Unary/NotTest.php | 31 + .../Tests/Node/Expression/Unary/PosTest.php | 31 + .../twig/test/Twig/Tests/Node/ForTest.php | 191 ++ .../twig/twig/test/Twig/Tests/Node/IfTest.php | 88 + .../twig/test/Twig/Tests/Node/ImportTest.php | 40 + .../twig/test/Twig/Tests/Node/IncludeTest.php | 83 + .../twig/test/Twig/Tests/Node/MacroTest.php | 74 + .../twig/test/Twig/Tests/Node/ModuleTest.php | 223 +++ .../twig/test/Twig/Tests/Node/PrintTest.php | 29 + .../twig/test/Twig/Tests/Node/SandboxTest.php | 44 + .../Twig/Tests/Node/SandboxedPrintTest.php | 33 + .../twig/test/Twig/Tests/Node/SetTest.php | 69 + .../test/Twig/Tests/Node/SpacelessTest.php | 37 + .../twig/test/Twig/Tests/Node/TextTest.php | 28 + .../Twig/Tests/NodeVisitor/OptimizerTest.php | 124 ++ .../twig/twig/test/Twig/Tests/ParserTest.php | 196 +++ .../Tests/Profiler/Dumper/AbstractTest.php | 101 ++ .../Tests/Profiler/Dumper/BlackfireTest.php | 32 + .../Twig/Tests/Profiler/Dumper/HtmlTest.php | 30 + .../Twig/Tests/Profiler/Dumper/TextTest.php | 30 + .../test/Twig/Tests/Profiler/ProfileTest.php | 100 ++ .../twig/test/Twig/Tests/TemplateTest.php | 796 +++++++++ .../test/Twig/Tests/TemplateWrapperTest.php | 64 + .../twig/test/Twig/Tests/TokenStreamTest.php | 82 + .../twig/test/Twig/Tests/escapingTest.php | 320 ++++ src/vendor/twig/twig/test/bootstrap.php | 13 + 1079 files changed, 82649 insertions(+) create mode 100644 database/islim.sql create mode 100644 license.md create mode 100644 readme.md create mode 100644 src/classes/Custom.php create mode 100644 src/composer.json create mode 100644 src/composer.lock create mode 100644 src/config.php create mode 100644 src/logs/app.log create mode 100644 src/models/Starter.php create mode 100644 src/public/.htaccess create mode 100644 src/public/default/css/bootstrap.min.css create mode 100644 src/public/default/css/starter-template.css create mode 100644 src/public/default/img/bootstrap.ico create mode 100644 src/public/default/js/bootstrap.min.js create mode 100644 src/public/default/js/holder.min.js create mode 100644 src/public/default/js/ie10-viewport-bug-workaround.js create mode 100644 src/public/index.php create mode 100644 src/routers/api.router.php create mode 100644 src/routers/handler.router.php create mode 100644 src/routers/hello.router.php create mode 100644 src/routers/index.router.php create mode 100644 src/routers/loop.router.php create mode 100644 src/templates/default/frontend/404.html create mode 100644 src/templates/default/frontend/index.html create mode 100644 src/templates/default/frontend/layout.global.css.html create mode 100644 src/templates/default/frontend/layout.global.js.html create mode 100644 src/templates/default/frontend/layout.global.meta.html create mode 100644 src/templates/default/frontend/loop.html create mode 100644 src/templates/default/frontend/user.html create mode 100644 src/templates/defaultPHP/frontend/404.html create mode 100644 src/templates/defaultPHP/frontend/index.html create mode 100644 src/templates/defaultPHP/frontend/layout.global.css.html create mode 100644 src/templates/defaultPHP/frontend/layout.global.js.html create mode 100644 src/templates/defaultPHP/frontend/layout.global.meta.html create mode 100644 src/templates/defaultPHP/frontend/loop.html create mode 100644 src/templates/defaultPHP/frontend/user.html create mode 100644 src/vendor/autoload.php create mode 100644 src/vendor/composer/ClassLoader.php create mode 100644 src/vendor/composer/LICENSE create mode 100644 src/vendor/composer/autoload_classmap.php create mode 100644 src/vendor/composer/autoload_files.php create mode 100644 src/vendor/composer/autoload_namespaces.php create mode 100644 src/vendor/composer/autoload_psr4.php create mode 100644 src/vendor/composer/autoload_real.php create mode 100644 src/vendor/composer/autoload_static.php create mode 100644 src/vendor/composer/installed.json create mode 100644 src/vendor/container-interop/container-interop/.gitignore create mode 100644 src/vendor/container-interop/container-interop/LICENSE create mode 100644 src/vendor/container-interop/container-interop/README.md create mode 100644 src/vendor/container-interop/container-interop/composer.json create mode 100644 src/vendor/container-interop/container-interop/docs/ContainerInterface-meta.md create mode 100644 src/vendor/container-interop/container-interop/docs/ContainerInterface.md create mode 100644 src/vendor/container-interop/container-interop/docs/Delegate-lookup-meta.md create mode 100644 src/vendor/container-interop/container-interop/docs/Delegate-lookup.md create mode 100644 src/vendor/container-interop/container-interop/docs/images/interoperating_containers.png create mode 100644 src/vendor/container-interop/container-interop/docs/images/priority.png create mode 100644 src/vendor/container-interop/container-interop/docs/images/side_by_side_containers.png create mode 100644 src/vendor/container-interop/container-interop/src/Interop/Container/ContainerInterface.php create mode 100644 src/vendor/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.php create mode 100644 src/vendor/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.php create mode 100644 src/vendor/monolog/monolog/.php_cs create mode 100644 src/vendor/monolog/monolog/CHANGELOG.mdown create mode 100644 src/vendor/monolog/monolog/LICENSE create mode 100644 src/vendor/monolog/monolog/README.mdown create mode 100644 src/vendor/monolog/monolog/composer.json create mode 100644 src/vendor/monolog/monolog/doc/01-usage.md create mode 100644 src/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md create mode 100644 src/vendor/monolog/monolog/doc/03-utilities.md create mode 100644 src/vendor/monolog/monolog/doc/04-extending.md create mode 100644 src/vendor/monolog/monolog/doc/sockets.md create mode 100644 src/vendor/monolog/monolog/phpunit.xml.dist create mode 100644 src/vendor/monolog/monolog/src/Monolog/ErrorHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Logger.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php create mode 100644 src/vendor/monolog/monolog/src/Monolog/Registry.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/LoggerTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/RegistryTest.php create mode 100644 src/vendor/monolog/monolog/tests/Monolog/TestCase.php create mode 100644 src/vendor/nikic/fast-route/.hhconfig create mode 100644 src/vendor/nikic/fast-route/.travis.yml create mode 100644 src/vendor/nikic/fast-route/FastRoute.hhi create mode 100644 src/vendor/nikic/fast-route/LICENSE create mode 100644 src/vendor/nikic/fast-route/README.md create mode 100644 src/vendor/nikic/fast-route/composer.json create mode 100644 src/vendor/nikic/fast-route/phpunit.xml create mode 100644 src/vendor/nikic/fast-route/src/BadRouteException.php create mode 100644 src/vendor/nikic/fast-route/src/DataGenerator.php create mode 100644 src/vendor/nikic/fast-route/src/DataGenerator/CharCountBased.php create mode 100644 src/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php create mode 100644 src/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php create mode 100644 src/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php create mode 100644 src/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php create mode 100644 src/vendor/nikic/fast-route/src/Dispatcher.php create mode 100644 src/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php create mode 100644 src/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php create mode 100644 src/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php create mode 100644 src/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php create mode 100644 src/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php create mode 100644 src/vendor/nikic/fast-route/src/Route.php create mode 100644 src/vendor/nikic/fast-route/src/RouteCollector.php create mode 100644 src/vendor/nikic/fast-route/src/RouteParser.php create mode 100644 src/vendor/nikic/fast-route/src/RouteParser/Std.php create mode 100644 src/vendor/nikic/fast-route/src/bootstrap.php create mode 100644 src/vendor/nikic/fast-route/src/functions.php create mode 100644 src/vendor/nikic/fast-route/test/Dispatcher/CharCountBasedTest.php create mode 100644 src/vendor/nikic/fast-route/test/Dispatcher/DispatcherTest.php create mode 100644 src/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php create mode 100644 src/vendor/nikic/fast-route/test/Dispatcher/GroupPosBasedTest.php create mode 100644 src/vendor/nikic/fast-route/test/Dispatcher/MarkBasedTest.php create mode 100644 src/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php create mode 100644 src/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php create mode 100644 src/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php create mode 100644 src/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php create mode 100644 src/vendor/nikic/fast-route/test/RouteParser/StdTest.php create mode 100644 src/vendor/nikic/fast-route/test/bootstrap.php create mode 100644 src/vendor/pimple/pimple/.gitignore create mode 100644 src/vendor/pimple/pimple/.travis.yml create mode 100644 src/vendor/pimple/pimple/CHANGELOG create mode 100644 src/vendor/pimple/pimple/LICENSE create mode 100644 src/vendor/pimple/pimple/README.rst create mode 100644 src/vendor/pimple/pimple/composer.json create mode 100644 src/vendor/pimple/pimple/ext/pimple/.gitignore create mode 100644 src/vendor/pimple/pimple/ext/pimple/README.md create mode 100644 src/vendor/pimple/pimple/ext/pimple/config.m4 create mode 100644 src/vendor/pimple/pimple/ext/pimple/config.w32 create mode 100644 src/vendor/pimple/pimple/ext/pimple/php_pimple.h create mode 100644 src/vendor/pimple/pimple/ext/pimple/pimple.c create mode 100644 src/vendor/pimple/pimple/ext/pimple/pimple_compat.h create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/001.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/002.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/003.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/004.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/005.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/006.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/007.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/008.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/009.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/010.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/011.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/012.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/013.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/014.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/015.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/016.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/017.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/018.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/019.phpt create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/bench.phpb create mode 100644 src/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb create mode 100644 src/vendor/pimple/pimple/phpunit.xml.dist create mode 100644 src/vendor/pimple/pimple/src/Pimple/Container.php create mode 100644 src/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php create mode 100644 src/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php create mode 100644 src/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php create mode 100644 src/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/PimpleServiceProvider.php create mode 100644 src/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php create mode 100644 src/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php create mode 100644 src/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php create mode 100644 src/vendor/psr/http-message/CHANGELOG.md create mode 100644 src/vendor/psr/http-message/LICENSE create mode 100644 src/vendor/psr/http-message/README.md create mode 100644 src/vendor/psr/http-message/composer.json create mode 100644 src/vendor/psr/http-message/src/MessageInterface.php create mode 100644 src/vendor/psr/http-message/src/RequestInterface.php create mode 100644 src/vendor/psr/http-message/src/ResponseInterface.php create mode 100644 src/vendor/psr/http-message/src/ServerRequestInterface.php create mode 100644 src/vendor/psr/http-message/src/StreamInterface.php create mode 100644 src/vendor/psr/http-message/src/UploadedFileInterface.php create mode 100644 src/vendor/psr/http-message/src/UriInterface.php create mode 100644 src/vendor/psr/log/.gitignore create mode 100644 src/vendor/psr/log/LICENSE create mode 100644 src/vendor/psr/log/Psr/Log/AbstractLogger.php create mode 100644 src/vendor/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 src/vendor/psr/log/Psr/Log/LogLevel.php create mode 100644 src/vendor/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 src/vendor/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 src/vendor/psr/log/Psr/Log/LoggerInterface.php create mode 100644 src/vendor/psr/log/Psr/Log/LoggerTrait.php create mode 100644 src/vendor/psr/log/Psr/Log/NullLogger.php create mode 100644 src/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php create mode 100644 src/vendor/psr/log/README.md create mode 100644 src/vendor/psr/log/composer.json create mode 100644 src/vendor/slim/php-view/.gitignore create mode 100644 src/vendor/slim/php-view/.travis.yml create mode 100644 src/vendor/slim/php-view/LICENSE.md create mode 100644 src/vendor/slim/php-view/README.md create mode 100644 src/vendor/slim/php-view/composer.json create mode 100644 src/vendor/slim/php-view/phpunit.xml.dist create mode 100644 src/vendor/slim/php-view/src/PhpRenderer.php create mode 100644 src/vendor/slim/php-view/tests/PhpRendererTest.php create mode 100644 src/vendor/slim/php-view/tests/bootstrap.php create mode 100644 src/vendor/slim/php-view/tests/testException.php create mode 100644 src/vendor/slim/php-view/tests/testTemplate.php create mode 100644 src/vendor/slim/slim/LICENSE.md create mode 100644 src/vendor/slim/slim/Slim/App.php create mode 100644 src/vendor/slim/slim/Slim/CallableResolver.php create mode 100644 src/vendor/slim/slim/Slim/CallableResolverAwareTrait.php create mode 100644 src/vendor/slim/slim/Slim/Collection.php create mode 100644 src/vendor/slim/slim/Slim/Container.php create mode 100644 src/vendor/slim/slim/Slim/DefaultServicesProvider.php create mode 100644 src/vendor/slim/slim/Slim/DeferredCallable.php create mode 100644 src/vendor/slim/slim/Slim/Exception/ContainerException.php create mode 100644 src/vendor/slim/slim/Slim/Exception/ContainerValueNotFoundException.php create mode 100644 src/vendor/slim/slim/Slim/Exception/MethodNotAllowedException.php create mode 100644 src/vendor/slim/slim/Slim/Exception/NotFoundException.php create mode 100644 src/vendor/slim/slim/Slim/Exception/SlimException.php create mode 100644 src/vendor/slim/slim/Slim/Handlers/AbstractError.php create mode 100644 src/vendor/slim/slim/Slim/Handlers/AbstractHandler.php create mode 100644 src/vendor/slim/slim/Slim/Handlers/Error.php create mode 100644 src/vendor/slim/slim/Slim/Handlers/NotAllowed.php create mode 100644 src/vendor/slim/slim/Slim/Handlers/NotFound.php create mode 100644 src/vendor/slim/slim/Slim/Handlers/PhpError.php create mode 100644 src/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php create mode 100644 src/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php create mode 100644 src/vendor/slim/slim/Slim/Http/Body.php create mode 100644 src/vendor/slim/slim/Slim/Http/Cookies.php create mode 100644 src/vendor/slim/slim/Slim/Http/Environment.php create mode 100644 src/vendor/slim/slim/Slim/Http/Headers.php create mode 100644 src/vendor/slim/slim/Slim/Http/Message.php create mode 100644 src/vendor/slim/slim/Slim/Http/Request.php create mode 100644 src/vendor/slim/slim/Slim/Http/RequestBody.php create mode 100644 src/vendor/slim/slim/Slim/Http/Response.php create mode 100644 src/vendor/slim/slim/Slim/Http/Stream.php create mode 100644 src/vendor/slim/slim/Slim/Http/UploadedFile.php create mode 100644 src/vendor/slim/slim/Slim/Http/Uri.php create mode 100644 src/vendor/slim/slim/Slim/Interfaces/CallableResolverInterface.php create mode 100644 src/vendor/slim/slim/Slim/Interfaces/CollectionInterface.php create mode 100644 src/vendor/slim/slim/Slim/Interfaces/Http/CookiesInterface.php create mode 100644 src/vendor/slim/slim/Slim/Interfaces/Http/EnvironmentInterface.php create mode 100644 src/vendor/slim/slim/Slim/Interfaces/Http/HeadersInterface.php create mode 100644 src/vendor/slim/slim/Slim/Interfaces/InvocationStrategyInterface.php create mode 100644 src/vendor/slim/slim/Slim/Interfaces/RouteGroupInterface.php create mode 100644 src/vendor/slim/slim/Slim/Interfaces/RouteInterface.php create mode 100644 src/vendor/slim/slim/Slim/Interfaces/RouterInterface.php create mode 100644 src/vendor/slim/slim/Slim/MiddlewareAwareTrait.php create mode 100644 src/vendor/slim/slim/Slim/Routable.php create mode 100644 src/vendor/slim/slim/Slim/Route.php create mode 100644 src/vendor/slim/slim/Slim/RouteGroup.php create mode 100644 src/vendor/slim/slim/Slim/Router.php create mode 100644 src/vendor/slim/slim/composer.json create mode 100644 src/vendor/slim/twig-view/.gitignore create mode 100644 src/vendor/slim/twig-view/.travis.yml create mode 100644 src/vendor/slim/twig-view/CONTRIBUTING.md create mode 100644 src/vendor/slim/twig-view/LICENSE.md create mode 100644 src/vendor/slim/twig-view/README.md create mode 100644 src/vendor/slim/twig-view/composer.json create mode 100644 src/vendor/slim/twig-view/phpunit.xml.dist create mode 100644 src/vendor/slim/twig-view/src/Twig.php create mode 100644 src/vendor/slim/twig-view/src/TwigExtension.php create mode 100644 src/vendor/slim/twig-view/tests/TwigTest.php create mode 100644 src/vendor/slim/twig-view/tests/another/another_example.html create mode 100644 src/vendor/slim/twig-view/tests/multi/directory/template/example.html create mode 100644 src/vendor/slim/twig-view/tests/templates/example.html create mode 100644 src/vendor/twig/twig/.editorconfig create mode 100644 src/vendor/twig/twig/.gitignore create mode 100644 src/vendor/twig/twig/.travis.yml create mode 100644 src/vendor/twig/twig/CHANGELOG create mode 100644 src/vendor/twig/twig/LICENSE create mode 100644 src/vendor/twig/twig/README.rst create mode 100644 src/vendor/twig/twig/composer.json create mode 100644 src/vendor/twig/twig/doc/advanced.rst create mode 100644 src/vendor/twig/twig/doc/advanced_legacy.rst create mode 100644 src/vendor/twig/twig/doc/api.rst create mode 100644 src/vendor/twig/twig/doc/coding_standards.rst create mode 100644 src/vendor/twig/twig/doc/deprecated.rst create mode 100644 src/vendor/twig/twig/doc/filters/abs.rst create mode 100644 src/vendor/twig/twig/doc/filters/batch.rst create mode 100644 src/vendor/twig/twig/doc/filters/capitalize.rst create mode 100644 src/vendor/twig/twig/doc/filters/convert_encoding.rst create mode 100644 src/vendor/twig/twig/doc/filters/date.rst create mode 100644 src/vendor/twig/twig/doc/filters/date_modify.rst create mode 100644 src/vendor/twig/twig/doc/filters/default.rst create mode 100644 src/vendor/twig/twig/doc/filters/escape.rst create mode 100644 src/vendor/twig/twig/doc/filters/first.rst create mode 100644 src/vendor/twig/twig/doc/filters/format.rst create mode 100644 src/vendor/twig/twig/doc/filters/index.rst create mode 100644 src/vendor/twig/twig/doc/filters/join.rst create mode 100644 src/vendor/twig/twig/doc/filters/json_encode.rst create mode 100644 src/vendor/twig/twig/doc/filters/keys.rst create mode 100644 src/vendor/twig/twig/doc/filters/last.rst create mode 100644 src/vendor/twig/twig/doc/filters/length.rst create mode 100644 src/vendor/twig/twig/doc/filters/lower.rst create mode 100644 src/vendor/twig/twig/doc/filters/merge.rst create mode 100644 src/vendor/twig/twig/doc/filters/nl2br.rst create mode 100644 src/vendor/twig/twig/doc/filters/number_format.rst create mode 100644 src/vendor/twig/twig/doc/filters/raw.rst create mode 100644 src/vendor/twig/twig/doc/filters/replace.rst create mode 100644 src/vendor/twig/twig/doc/filters/reverse.rst create mode 100644 src/vendor/twig/twig/doc/filters/round.rst create mode 100644 src/vendor/twig/twig/doc/filters/slice.rst create mode 100644 src/vendor/twig/twig/doc/filters/sort.rst create mode 100644 src/vendor/twig/twig/doc/filters/split.rst create mode 100644 src/vendor/twig/twig/doc/filters/striptags.rst create mode 100644 src/vendor/twig/twig/doc/filters/title.rst create mode 100644 src/vendor/twig/twig/doc/filters/trim.rst create mode 100644 src/vendor/twig/twig/doc/filters/upper.rst create mode 100644 src/vendor/twig/twig/doc/filters/url_encode.rst create mode 100644 src/vendor/twig/twig/doc/functions/attribute.rst create mode 100644 src/vendor/twig/twig/doc/functions/block.rst create mode 100644 src/vendor/twig/twig/doc/functions/constant.rst create mode 100644 src/vendor/twig/twig/doc/functions/cycle.rst create mode 100644 src/vendor/twig/twig/doc/functions/date.rst create mode 100644 src/vendor/twig/twig/doc/functions/dump.rst create mode 100644 src/vendor/twig/twig/doc/functions/include.rst create mode 100644 src/vendor/twig/twig/doc/functions/index.rst create mode 100644 src/vendor/twig/twig/doc/functions/max.rst create mode 100644 src/vendor/twig/twig/doc/functions/min.rst create mode 100644 src/vendor/twig/twig/doc/functions/parent.rst create mode 100644 src/vendor/twig/twig/doc/functions/random.rst create mode 100644 src/vendor/twig/twig/doc/functions/range.rst create mode 100644 src/vendor/twig/twig/doc/functions/source.rst create mode 100644 src/vendor/twig/twig/doc/functions/template_from_string.rst create mode 100644 src/vendor/twig/twig/doc/index.rst create mode 100644 src/vendor/twig/twig/doc/installation.rst create mode 100644 src/vendor/twig/twig/doc/internals.rst create mode 100644 src/vendor/twig/twig/doc/intro.rst create mode 100644 src/vendor/twig/twig/doc/recipes.rst create mode 100644 src/vendor/twig/twig/doc/tags/autoescape.rst create mode 100644 src/vendor/twig/twig/doc/tags/block.rst create mode 100644 src/vendor/twig/twig/doc/tags/do.rst create mode 100644 src/vendor/twig/twig/doc/tags/embed.rst create mode 100644 src/vendor/twig/twig/doc/tags/extends.rst create mode 100644 src/vendor/twig/twig/doc/tags/filter.rst create mode 100644 src/vendor/twig/twig/doc/tags/flush.rst create mode 100644 src/vendor/twig/twig/doc/tags/for.rst create mode 100644 src/vendor/twig/twig/doc/tags/from.rst create mode 100644 src/vendor/twig/twig/doc/tags/if.rst create mode 100644 src/vendor/twig/twig/doc/tags/import.rst create mode 100644 src/vendor/twig/twig/doc/tags/include.rst create mode 100644 src/vendor/twig/twig/doc/tags/index.rst create mode 100644 src/vendor/twig/twig/doc/tags/macro.rst create mode 100644 src/vendor/twig/twig/doc/tags/sandbox.rst create mode 100644 src/vendor/twig/twig/doc/tags/set.rst create mode 100644 src/vendor/twig/twig/doc/tags/spaceless.rst create mode 100644 src/vendor/twig/twig/doc/tags/use.rst create mode 100644 src/vendor/twig/twig/doc/tags/verbatim.rst create mode 100644 src/vendor/twig/twig/doc/tags/with.rst create mode 100644 src/vendor/twig/twig/doc/templates.rst create mode 100644 src/vendor/twig/twig/doc/tests/constant.rst create mode 100644 src/vendor/twig/twig/doc/tests/defined.rst create mode 100644 src/vendor/twig/twig/doc/tests/divisibleby.rst create mode 100644 src/vendor/twig/twig/doc/tests/empty.rst create mode 100644 src/vendor/twig/twig/doc/tests/even.rst create mode 100644 src/vendor/twig/twig/doc/tests/index.rst create mode 100644 src/vendor/twig/twig/doc/tests/iterable.rst create mode 100644 src/vendor/twig/twig/doc/tests/null.rst create mode 100644 src/vendor/twig/twig/doc/tests/odd.rst create mode 100644 src/vendor/twig/twig/doc/tests/sameas.rst create mode 100644 src/vendor/twig/twig/ext/twig/.gitignore create mode 100644 src/vendor/twig/twig/ext/twig/config.m4 create mode 100644 src/vendor/twig/twig/ext/twig/config.w32 create mode 100644 src/vendor/twig/twig/ext/twig/php_twig.h create mode 100644 src/vendor/twig/twig/ext/twig/twig.c create mode 100644 src/vendor/twig/twig/lib/Twig/Autoloader.php create mode 100644 src/vendor/twig/twig/lib/Twig/BaseNodeVisitor.php create mode 100644 src/vendor/twig/twig/lib/Twig/Cache/Filesystem.php create mode 100644 src/vendor/twig/twig/lib/Twig/Cache/Null.php create mode 100644 src/vendor/twig/twig/lib/Twig/CacheInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Compiler.php create mode 100644 src/vendor/twig/twig/lib/Twig/CompilerInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Environment.php create mode 100644 src/vendor/twig/twig/lib/Twig/Error.php create mode 100644 src/vendor/twig/twig/lib/Twig/Error/Loader.php create mode 100644 src/vendor/twig/twig/lib/Twig/Error/Runtime.php create mode 100644 src/vendor/twig/twig/lib/Twig/Error/Syntax.php create mode 100644 src/vendor/twig/twig/lib/Twig/ExistsLoaderInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/ExpressionParser.php create mode 100644 src/vendor/twig/twig/lib/Twig/Extension.php create mode 100644 src/vendor/twig/twig/lib/Twig/Extension/Core.php create mode 100644 src/vendor/twig/twig/lib/Twig/Extension/Debug.php create mode 100644 src/vendor/twig/twig/lib/Twig/Extension/Escaper.php create mode 100644 src/vendor/twig/twig/lib/Twig/Extension/GlobalsInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Extension/InitRuntimeInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Extension/Optimizer.php create mode 100644 src/vendor/twig/twig/lib/Twig/Extension/Profiler.php create mode 100644 src/vendor/twig/twig/lib/Twig/Extension/Sandbox.php create mode 100644 src/vendor/twig/twig/lib/Twig/Extension/Staging.php create mode 100644 src/vendor/twig/twig/lib/Twig/Extension/StringLoader.php create mode 100644 src/vendor/twig/twig/lib/Twig/ExtensionInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/FileExtensionEscapingStrategy.php create mode 100644 src/vendor/twig/twig/lib/Twig/Filter.php create mode 100644 src/vendor/twig/twig/lib/Twig/Filter/Function.php create mode 100644 src/vendor/twig/twig/lib/Twig/Filter/Method.php create mode 100644 src/vendor/twig/twig/lib/Twig/Filter/Node.php create mode 100644 src/vendor/twig/twig/lib/Twig/FilterCallableInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/FilterInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Function.php create mode 100644 src/vendor/twig/twig/lib/Twig/Function/Function.php create mode 100644 src/vendor/twig/twig/lib/Twig/Function/Method.php create mode 100644 src/vendor/twig/twig/lib/Twig/Function/Node.php create mode 100644 src/vendor/twig/twig/lib/Twig/FunctionCallableInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/FunctionInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Lexer.php create mode 100644 src/vendor/twig/twig/lib/Twig/LexerInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Loader/Array.php create mode 100644 src/vendor/twig/twig/lib/Twig/Loader/Chain.php create mode 100644 src/vendor/twig/twig/lib/Twig/Loader/Filesystem.php create mode 100644 src/vendor/twig/twig/lib/Twig/Loader/String.php create mode 100644 src/vendor/twig/twig/lib/Twig/LoaderInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Markup.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/AutoEscape.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Block.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/BlockReference.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Body.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/CheckSecurity.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Do.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Embed.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Array.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/AssignName.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Add.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/And.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Div.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/In.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Less.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/LessEqual.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Matches.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Or.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Power.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Range.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/BlockReference.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Call.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Conditional.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Constant.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/ExtensionReference.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Filter.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Filter/Default.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Function.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/GetAttr.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/MethodCall.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Name.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/NullCoalesce.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Parent.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/TempName.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Test.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Constant.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Defined.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Even.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Null.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Odd.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Sameas.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Unary.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Unary/Neg.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Unary/Not.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Expression/Unary/Pos.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Flush.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/For.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/ForLoop.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/If.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Import.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Include.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Macro.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Module.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Print.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Sandbox.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/SandboxedPrint.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Set.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/SetTemp.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Spaceless.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/Text.php create mode 100644 src/vendor/twig/twig/lib/Twig/Node/With.php create mode 100644 src/vendor/twig/twig/lib/Twig/NodeInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/NodeOutputInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/NodeTraverser.php create mode 100644 src/vendor/twig/twig/lib/Twig/NodeVisitor/Escaper.php create mode 100644 src/vendor/twig/twig/lib/Twig/NodeVisitor/Optimizer.php create mode 100644 src/vendor/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php create mode 100644 src/vendor/twig/twig/lib/Twig/NodeVisitor/Sandbox.php create mode 100644 src/vendor/twig/twig/lib/Twig/NodeVisitorInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Parser.php create mode 100644 src/vendor/twig/twig/lib/Twig/ParserInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php create mode 100644 src/vendor/twig/twig/lib/Twig/Profiler/Dumper/Html.php create mode 100644 src/vendor/twig/twig/lib/Twig/Profiler/Dumper/Text.php create mode 100644 src/vendor/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php create mode 100644 src/vendor/twig/twig/lib/Twig/Profiler/Node/LeaveProfile.php create mode 100644 src/vendor/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php create mode 100644 src/vendor/twig/twig/lib/Twig/Profiler/Profile.php create mode 100644 src/vendor/twig/twig/lib/Twig/RuntimeLoaderInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Sandbox/SecurityError.php create mode 100644 src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php create mode 100644 src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php create mode 100644 src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php create mode 100644 src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php create mode 100644 src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedTagError.php create mode 100644 src/vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php create mode 100644 src/vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicyInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/SimpleFilter.php create mode 100644 src/vendor/twig/twig/lib/Twig/SimpleFunction.php create mode 100644 src/vendor/twig/twig/lib/Twig/SimpleTest.php create mode 100644 src/vendor/twig/twig/lib/Twig/Source.php create mode 100644 src/vendor/twig/twig/lib/Twig/SourceContextLoaderInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Template.php create mode 100644 src/vendor/twig/twig/lib/Twig/TemplateInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/TemplateWrapper.php create mode 100644 src/vendor/twig/twig/lib/Twig/Test.php create mode 100644 src/vendor/twig/twig/lib/Twig/Test/Function.php create mode 100644 src/vendor/twig/twig/lib/Twig/Test/IntegrationTestCase.php create mode 100644 src/vendor/twig/twig/lib/Twig/Test/Method.php create mode 100644 src/vendor/twig/twig/lib/Twig/Test/Node.php create mode 100644 src/vendor/twig/twig/lib/Twig/Test/NodeTestCase.php create mode 100644 src/vendor/twig/twig/lib/Twig/TestCallableInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/TestInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/Token.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/AutoEscape.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Block.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Do.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Embed.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Extends.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Filter.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Flush.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/For.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/From.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/If.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Import.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Include.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Macro.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Sandbox.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Set.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Spaceless.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/Use.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParser/With.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParserBroker.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParserBrokerInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenParserInterface.php create mode 100644 src/vendor/twig/twig/lib/Twig/TokenStream.php create mode 100644 src/vendor/twig/twig/lib/Twig/Util/DeprecationCollector.php create mode 100644 src/vendor/twig/twig/lib/Twig/Util/TemplateDirIterator.php create mode 100644 src/vendor/twig/twig/phpunit.xml.dist create mode 100644 src/vendor/twig/twig/test/Twig/Tests/AutoloaderTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Cache/FilesystemTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/CompilerTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/CustomExtensionTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/EnvironmentTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/ErrorTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/ExpressionParserTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Extension/CoreTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Extension/SandboxTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/FileCachingTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/FileExtensionEscapingStrategyTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/FilesystemHelper.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/autoescape/block.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/autoescape/name.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/errors/base.html create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/errors/index.html create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/child_contents_outside_blocks.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_array_with_undefined_variable.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_array_with_undefined_variable_again.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_function_with_undefined_variable.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_function_with_unknown_argument.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_tag_with_undefined_variable.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/syntax_error_in_reused_template.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/unclosed_tag.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_parent.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_template_in_child_template.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_trait.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/_self.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/array.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/array_call.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/binary.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/bitwise.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/comparison.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/divisibleby.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/dotdot.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ends_with.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/grouping.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/literals.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/magic_call.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/matches.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/method_call.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/negative_numbers.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/operators_as_variables.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/postfix.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/power.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/sameas.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/starts_with.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/strings.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ternary_operator.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ternary_operator_noelse.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ternary_operator_nothen.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/two_word_operators_as_variables.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/unary.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/unary_macro_arguments.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/unary_precedence.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/abs.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_float.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_empty_fill.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_exact_elements.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_fill.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_keys.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_zero_elements.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/convert_encoding.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_default_format.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_default_format_interval.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_immutable.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_interval.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_modify.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_namedargs.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/default.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/dynamic_filter.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape_html_attr.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape_non_supported_charset.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/first.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/force_escape.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/format.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/join.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/json_encode.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/last.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/length.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/length_utf8.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/merge.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/nl2br.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/number_format.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/number_format_default.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/replace.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/replace_invalid_arg.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/reverse.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/round.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/slice.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/sort.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/special_chars.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/split.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/split_utf8.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/static_calls.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/trim.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/urlencode.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/urlencode_deprecated.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/attribute.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/block.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/block_with_template.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/block_without_name.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/constant.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/cycle.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/date.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/date_namedargs.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/dump.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/dump_array.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/dynamic_function.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/assignment.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/autoescaping.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/expression.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/ignore_missing.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/missing.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/missing_nested.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox_disabling.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/template_instance.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/templates_as_array.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/with_context.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/with_variables.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/magic_call.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/magic_call53.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/max.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/min.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/range.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/recursive_block_with_inheritance.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/source.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/special_chars.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/static_calls.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/template_from_string.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/default_values.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/nested_calls.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/reserved_variables.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/simple.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/varargs.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/varargs_argument.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/with_filters.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/combined_debug_info.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/empty_token.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/issue_1143.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/multi_word_tests.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/simple_xml_element.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/strings_like_numbers.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/blocks.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/double_escaping.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/functions.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/literal.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/nested.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/objects.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/raw.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/strategy.legacy.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/type.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_filters.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_filters_arguments.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_pre_escape_filters.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_preserves_safety_filters.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/block_unique_name.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/special_chars.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/complex_dynamic_parent.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/dynamic_parent.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/error_line.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/multiple.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/nested.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/with_extends.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/json_encode.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/multiple.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/nested.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/condition.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/context.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/else.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/inner_variables.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/keys.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/keys_and_values.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_context.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_context_local.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_not_defined.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_not_defined_cond.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/nested_else.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/objects.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/objects_countable.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/recursive.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/values.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/from.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/if/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/if/expression.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/expression.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/ignore_missing.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/missing.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/missing_nested.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/only.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/template_instance.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/templates_as_array.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/with_variables.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/block_expr.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/block_expr2.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/conditional.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/dynamic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/empty.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array_with_empty_name.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array_with_null_name.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/multiple.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/multiple_dynamic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_blocks.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_blocks_parent_only.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_inheritance.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_change.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_in_a_block.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_isolation.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_nested.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_without_extends.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_without_extends_but_traits.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/template_instance.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/use.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/endmacro_name.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/external.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_with_reserved_name.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/global.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_with_reserved_nam.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/reserved_name.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/self_import.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/special_chars.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/super_globals.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/raw/basic.legacy.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/raw/mixed_usage_with_raw.legacy.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/raw/whitespace_control.legacy.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/sandbox/not_valid1.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/sandbox/not_valid2.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/sandbox/simple.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/capture-empty.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/capture.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/expression.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/spaceless/simple.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/special_chars.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/trim_block.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/aliases.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/deep.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/deep_empty.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/inheritance.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/inheritance2.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/multiple.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/multiple_aliases.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block2.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block3.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/mixed_usage_with_raw.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/whitespace_control.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/basic.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/expression.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/nested.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/with_no_hash.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/with_only.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/array.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/constant.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_attribute.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_blocks.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_blocks_with_template.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_constants.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/empty.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/even.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/in.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/in_with_objects.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/iterable.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/null_coalesce.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/odd.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/IntegrationTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/LegacyFixtures/autoescape/filename.legacy.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/LegacyFixtures/functions/undefined_block.legacy.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/LegacyFixtures/test.legacy.test create mode 100644 src/vendor/twig/twig/test/Twig/Tests/LegacyIntegrationTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/LexerTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/ArrayTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/ChainTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/FilesystemTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_empty_parent.html.twig create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_nonexistent_parent.html.twig create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_null_parent.html.twig create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_valid_parent.html.twig create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/parent.html.twig create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/spare_parent.html.twig create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named/index.html create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_bis/index.html create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_final/index.html create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_ter/index.html create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal/index.html create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal_bis/index.html create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal_final/index.html create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal_ter/index.html create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/phar/phar-sample.phar create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/themes/theme1/blocks.html.twig create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/themes/theme2/blocks.html.twig create mode 100644 src/vendor/twig/twig/test/Twig/Tests/NativeExtensionTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/AutoEscapeTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/BlockReferenceTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/BlockTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/DoTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ArrayTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/AssignNameTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/AddTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/AndTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/ConcatTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/DivTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/ModTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/MulTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/OrTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/SubTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/CallTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ConditionalTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ConstantTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/FilterTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/FunctionTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/GetAttrTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/NameTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/NullCoalesceTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/PHP53/FilterInclude.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/PHP53/FunctionInclude.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/PHP53/TestInclude.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ParentTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/TestTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Unary/NegTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Unary/NotTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Unary/PosTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/ForTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/IfTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/ImportTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/IncludeTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/MacroTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/ModuleTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/PrintTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/SandboxTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/SandboxedPrintTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/SetTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/SpacelessTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Node/TextTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/NodeVisitor/OptimizerTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/ParserTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/AbstractTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/BlackfireTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/HtmlTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/TextTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/Profiler/ProfileTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/TemplateTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/TemplateWrapperTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/TokenStreamTest.php create mode 100644 src/vendor/twig/twig/test/Twig/Tests/escapingTest.php create mode 100644 src/vendor/twig/twig/test/bootstrap.php diff --git a/database/islim.sql b/database/islim.sql new file mode 100644 index 0000000..3804363 --- /dev/null +++ b/database/islim.sql @@ -0,0 +1,57 @@ +-- phpMyAdmin SQL Dump +-- version 4.2.11 +-- http://www.phpmyadmin.net +-- +-- Host: 127.0.0.1 +-- Generation Time: Dec 15, 2016 at 07:56 PM +-- Server version: 5.6.21 +-- PHP Version: 5.6.3 + +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: `islim` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `user` +-- + +CREATE TABLE IF NOT EXISTS `user` ( + `username` varchar(50) NOT NULL, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `status` varchar(50) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- +-- Dumping data for table `user` +-- + +INSERT INTO `user` (`username`, `created`, `status`) VALUES +('abdul', '2016-12-12 14:39:15', 'active'), +('alfian', '2016-12-14 14:39:27', 'suspended'), +('aziz', '2016-12-13 14:39:21', 'active'), +('muhammad', '2016-12-11 14:39:05', 'active'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `user` +-- +ALTER TABLE `user` + ADD PRIMARY KEY (`username`); + +/*!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 */; diff --git a/license.md b/license.md new file mode 100644 index 0000000..c73bc1c --- /dev/null +++ b/license.md @@ -0,0 +1,9 @@ + + +Copyright (c) 2016 M ABD AZIZ ALFIAN + +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/readme.md b/readme.md new file mode 100644 index 0000000..e231d3c --- /dev/null +++ b/readme.md @@ -0,0 +1,207 @@ +iSlim3-basic +======= +[![Travis branch](https://img.shields.io/travis/rust-lang/rust/master.svg)](https://github.com/aalfiann/iSlim3-basic) +[![Packagist](https://img.shields.io/packagist/l/doctrine/orm.svg)](https://github.com/aalfiann/iSlim3-basic/blob/master/license.md) + +I call this "iSlim" because I heart [Slim Framework](http://www.slimframework.com/).
+iSlim version 3 is the easiest and flexible way to create your PHP application using PSR 7 way,
+which is look alike MVC pattern and Bootstrap 4. + + +Getting Started +--------------- +1. Get or download the project +2. Install it using Composer + +Folder System +--------------- +* classes/ +* logs/ +* models/ +* public/ +* routers/ + * name.router.php (routes by functionalities) +* templates/ + +### classes/ + +Here is the place for your application classes + +### logs/ + +Here is the place your custom log. +You can add your custom log in your any container or router. + +Example adding custom log in a router post +```php +$app->post('/user/new', function (Request $request, Response $response) { + echo 'This is a POST route'; + $this->logger->addInfo("Response post is succesfully complete!!!"); +}); +``` + +### models/ + +Add the model classes here. +We are using PDO MySQL for the Database. + +Example of models class: + +Starter.php + +```php + +namespace models; + +class Starter { + + protected $db; + + function __construct($db=null) { + if (!empty($db)) + { + $this->db = $db; + } + } + + // Get all data from database mysql + public function getAll() { + $r = array(); + + $sql = "SELECT * FROM user a order by a.created;"; + $stmt = $this->db->prepare($sql); + + if ($stmt->execute()) { + if ($stmt->rowCount() > 0){ + $r = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + else{ + $r = 0; + } + } else { + $r = 0; + } + + return $r; + $stmt->Close(); + } + + public function setHello() { + return array( + 'hello' => "Hello World!!!", + 'description1' => "Use this document as a way to quickly start any new project.", + 'description2' => "All you get is this text and a mostly barebones HTML document.", + 'author' => "iSlim3 is forged by M ABD AZIZ ALFIAN" + ); + } +} +``` + +### public/ + +All the public files: +* Images, CSS and JS files which is inside a theme folder. +* index.php (this is required to run the core of Slim Framework) + +### routers/ + +All the files with the routes. Each file contents the routes of an specific functionality. +It is very important that the names of the files inside this folder follow this pattern: name.router.php + +Example of router file: + +index.router.php + +```php +use \Psr\Http\Message\ServerRequestInterface as Request; +use \Psr\Http\Message\ResponseInterface as Response; + + //Hello Word + $app->get('/', function (Request $request, Response $response) { + $oStuff = new models\Starter(); + $hello = $oStuff->setHello(); + + $response = $this->viewfrontend->render($response, "index.html", [ + "hello" => $hello['hello'], + "description1" => $hello['description1'], + "description2" => $hello['description2'], + "author" => $hello['author'], + "router" => $this->router]); + return $response; + })->setName("/"); + +//POST route +$app->post('/user/new', function (Request $request, Response $response) { + echo 'This is a POST route'; +}); + +// PUT route +$app->put('/user/update', function (Request $request, Response $response) { + echo 'This is a PUT route'; +}); + +// DELETE route +$app->delete('/user/delete', function (Request $request, Response $response) { + echo 'This is a DELETE route'; +}); +``` + +### templates/ + +There are already 2 default themes in iSlim3: +* default (using twig way) +* defaultPHP (using render PHP) + +iSlim3 have two type of templates: + +1. Render as PHP +2. Use Twig + +Default templates type is using twig, You can find change this configuration in config.php that placed in root folder. + +Example Config.php +```php +/** + * Configuration App + * + * @var $config['displayErrorDetails'] to display error details on slim + * @var $config['addContentLengthHeader'] to set the Content-Length header which makes Slim behave more predictably + * + */ +$config['displayErrorDetails'] = true; +$config['addContentLengthHeader'] = false; + +/** + * Configuration Templates + * + * @var $config['templateRender'] is how slim3 to render a template. There are two options 'twig' or 'php' + * @var $config['twigcache'] is cache options in twig only (won't work if you use it on php render) + * @var $config['theme'] is options to choose which one theme will be use. + * + * Note: if You choose theme defaultPHP, make sure you have set templateRender to 'php' + */ +$config['templateRender'] = 'twig'; +$config['twigcache'] = false; +$config['theme'] = 'default'; + +/** + * Configuration PDO MySQL Database + * + * @var $config['db']['host'] = where is database was hosted + * @var $config['db']['user'] = username database to login + * @var $config['db']['pass'] = pass database to login + * @var $config['db']['dbname'] = the database name + */ +$config['db']['host'] = 'localhost'; +$config['db']['user'] = 'root'; +$config['db']['pass'] = 'root'; +$config['db']['dbname'] = 'iSlim'; +``` + +How to Contribute +----------------- +### Pull Requests + +1. Fork the iSlim3 repository +2. Create a new branch for each feature or improvement +3. Send a pull request from each feature branch to the develop branch \ No newline at end of file diff --git a/src/classes/Custom.php b/src/classes/Custom.php new file mode 100644 index 0000000..4b2c827 --- /dev/null +++ b/src/classes/Custom.php @@ -0,0 +1,118 @@ + 1000) + { + $hasil = true; + } + return $hasil; + } + + /** + * Convert encoded json data to prettyPrint in old way + * This function is support for PHP below 5.4 as JSON_PRETTY_PRINT is not supported at all + * + * @param $json = input encoded data json here + */ + function prettyPrint( $json ) + { + $result = ''; + $level = 0; + $in_quotes = false; + $in_escape = false; + $ends_line_level = NULL; + $json_length = strlen( $json ); + + for( $i = 0; $i < $json_length; $i++ ) { + $char = $json[$i]; + $new_line_level = NULL; + $post = ""; + if( $ends_line_level !== NULL ) { + $new_line_level = $ends_line_level; + $ends_line_level = NULL; + } + if ( $in_escape ) { + $in_escape = false; + } else if( $char === '"' ) { + $in_quotes = !$in_quotes; + } else if( ! $in_quotes ) { + switch( $char ) { + case '}': case ']': + $level--; + $ends_line_level = NULL; + $new_line_level = $level; + break; + + case '{': case '[': + $level++; + case ',': + $ends_line_level = $level; + break; + + case ':': + $post = " "; + break; + + case " ": case "\t": case "\n": case "\r": + $char = ""; + $ends_line_level = $new_line_level; + $new_line_level = NULL; + break; + } + } else if ( $char === '\\' ) { + $in_escape = true; + } + if( $new_line_level !== NULL ) { + $result .= "\n".str_repeat( "\t", $new_line_level ); + } + $result .= $char.$post; + } + + return $result; + } + +} \ No newline at end of file diff --git a/src/composer.json b/src/composer.json new file mode 100644 index 0000000..84e9da9 --- /dev/null +++ b/src/composer.json @@ -0,0 +1,19 @@ +{ + "name":"aalfiann/iSlim3", + "type":"framework", + "description":"PSR + Twig for the PHP microframework Slim v3.x which look alike MVC", + "keywords":["microframework", "rest", "router", "mvc", "PSR"], + "homepage":"https://github.com/aalfiann/iSlim3", + "license":"MIT", + "authors":[ + { + "name":"aalfiann" + } + ], + "require": { + "slim/slim": "^3.6", + "monolog/monolog": "^1.22", + "slim/php-view": "^2.2", + "slim/twig-view": "^2.1" + } +} diff --git a/src/composer.lock b/src/composer.lock new file mode 100644 index 0000000..23adee3 --- /dev/null +++ b/src/composer.lock @@ -0,0 +1,540 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "c55c7c01db17a747a753ac84f80a1644", + "content-hash": "ee6bd1bbf6d3f7aea41674cf04621761", + "packages": [ + { + "name": "container-interop/container-interop", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/fc08354828f8fd3245f77a66b9e23a6bca48297e", + "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "time": "2014-12-30 15:22:37" + }, + { + "name": "monolog/monolog", + "version": "1.22.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "bad29cb8d18ab0315e6c477751418a82c850d558" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558", + "reference": "bad29cb8d18ab0315e6c477751418a82c850d558", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "~5.3" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2016-11-26 00:15:39" + }, + { + "name": "nikic/fast-route", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "8ea928195fa9b907f0d6e48312d323c1a13cc2af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/8ea928195fa9b907f0d6e48312d323c1a13cc2af", + "reference": "8ea928195fa9b907f0d6e48312d323c1a13cc2af", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "FastRoute\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "time": "2016-06-12 19:08:51" + }, + { + "name": "pimple/pimple", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2015-09-11 15:10:35" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06 14:39:51" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10 12:19:37" + }, + { + "name": "slim/php-view", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/PHP-View.git", + "reference": "122ed121a8d9cf91a94020814d2a3ee6c836754f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/PHP-View/zipball/122ed121a8d9cf91a94020814d2a3ee6c836754f", + "reference": "122ed121a8d9cf91a94020814d2a3ee6c836754f", + "shasum": "" + }, + "require": { + "psr/http-message": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8", + "slim/slim": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Views\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Glenn Eggleton", + "email": "geggleto@gmail.com" + } + ], + "description": "Render PHP view scripts into a PSR-7 Response object.", + "keywords": [ + "framework", + "php", + "phtml", + "renderer", + "slim", + "template", + "view" + ], + "time": "2016-10-11 07:43:08" + }, + { + "name": "slim/slim", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim.git", + "reference": "a685fe91a9435e1432e8eeb7cf516e2f5cee7f64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/a685fe91a9435e1432e8eeb7cf516e2f5cee7f64", + "reference": "a685fe91a9435e1432e8eeb7cf516e2f5cee7f64", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.1", + "nikic/fast-route": "^1.0", + "php": ">=5.5.0", + "pimple/pimple": "^3.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "squizlabs/php_codesniffer": "^2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + } + ], + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "http://slimframework.com", + "keywords": [ + "api", + "framework", + "micro", + "router" + ], + "time": "2016-11-20 20:48:49" + }, + { + "name": "slim/twig-view", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Twig-View.git", + "reference": "16fded26a44b8e8e0e041f1cff32afa21daeb284" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Twig-View/zipball/16fded26a44b8e8e0e041f1cff32afa21daeb284", + "reference": "16fded26a44b8e8e0e041f1cff32afa21daeb284", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "psr/http-message": "^1.0", + "twig/twig": "^1.18" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Views\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "description": "Slim Framework 3 view helper built on top of the Twig templating component", + "homepage": "http://slimframework.com", + "keywords": [ + "framework", + "slim", + "template", + "twig", + "view" + ], + "time": "2016-03-13 20:58:41" + }, + { + "name": "twig/twig", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "74f723e542368ca2080b252740be5f1113ebb898" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/74f723e542368ca2080b252740be5f1113ebb898", + "reference": "74f723e542368ca2080b252740be5f1113ebb898", + "shasum": "" + }, + "require": { + "php": ">=5.2.7" + }, + "require-dev": { + "symfony/debug": "~2.7", + "symfony/phpunit-bridge": "~3.2@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.29-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "http://twig.sensiolabs.org/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "http://twig.sensiolabs.org", + "keywords": [ + "templating" + ], + "time": "2016-12-13 17:28:18" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/src/config.php b/src/config.php new file mode 100644 index 0000000..b4bd381 --- /dev/null +++ b/src/config.php @@ -0,0 +1,37 @@ +db = $db; + } + } + + // Get all data from database mysql + public function getAll() { + $r = array(); + + $sql = "SELECT * FROM user a order by a.created;"; + $stmt = $this->db->prepare($sql); + + if ($stmt->execute()) { + if ($stmt->rowCount() > 0){ + $r = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + else{ + $r = 0; + } + } else { + $r = 0; + } + + return $r; + $stmt->Close(); + } + + public function setHello() { + return array( + 'hello' => "Hello World!!!", + 'description1' => "Use this document as a way to quickly start any new project.", + 'description2' => "All you get is this text and a mostly barebones HTML document.", + 'author' => "iSlim3 is forged by M ABD AZIZ ALFIAN" + ); + } +} \ No newline at end of file diff --git a/src/public/.htaccess b/src/public/.htaccess new file mode 100644 index 0000000..8481f26 --- /dev/null +++ b/src/public/.htaccess @@ -0,0 +1,6 @@ + + RewriteEngine on + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule . index.php [L] + \ No newline at end of file diff --git a/src/public/default/css/bootstrap.min.css b/src/public/default/css/bootstrap.min.css new file mode 100644 index 0000000..06cf582 --- /dev/null +++ b/src/public/default/css/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.0.0-alpha.5 (https://getbootstrap.com) + * Copyright 2011-2016 The Bootstrap Authors + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v4.2.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}[hidden],template{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}button,input,optgroup,select,textarea{font:inherit;margin:0}optgroup{font-weight:700}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}@media print{*,::after,::before,::first-letter,blockquote::first-line,div::first-line,li::first-line,p::first-line{text-shadow:none!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.tag{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}@-ms-viewport{width:device-width}html{font-size:16px;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;line-height:1.5;color:#373a3c;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #818a91}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}a{color:#0275d8;text-decoration:none}a:focus,a:hover{color:#014c8c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle}[role=button]{cursor:pointer}[role=button],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}table{border-collapse:collapse;background-color:transparent}caption{padding-top:.75rem;padding-bottom:.75rem;color:#818a91;text-align:left;caption-side:bottom}th{text-align:left}label{display:inline-block;margin-bottom:.5rem}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,select,textarea{line-height:inherit}input[type=checkbox]:disabled,input[type=radio]:disabled{cursor:not-allowed}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit}input[type=search]{-webkit-appearance:none}output{display:inline-block}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300}.display-2{font-size:5.5rem;font-weight:300}.display-3{font-size:4.5rem;font-weight:300}.display-4{font-size:3.5rem;font-weight:300}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:5px}.initialism{font-size:90%;text-transform:uppercase}.blockquote{padding:.5rem 1rem;margin-bottom:1rem;font-size:1.25rem;border-left:.25rem solid #eceeef}.blockquote-footer{display:block;font-size:80%;color:#818a91}.blockquote-footer::before{content:"\2014 \00A0"}.blockquote-reverse{padding-right:1rem;padding-left:0;text-align:right;border-right:.25rem solid #eceeef;border-left:0}.blockquote-reverse .blockquote-footer::before{content:""}.blockquote-reverse .blockquote-footer::after{content:"\00A0 \2014"}dl.row>dd+dt{clear:left}.carousel-inner>.carousel-item>a>img,.carousel-inner>.carousel-item>img,.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #ddd;border-radius:.25rem;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#818a91}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}code{padding:.2rem .4rem;font-size:90%;color:#bd4147;background-color:#f7f7f9;border-radius:.25rem}kbd{padding:.2rem .4rem;font-size:90%;color:#fff;background-color:#333;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;margin-top:0;margin-bottom:1rem;font-size:90%;color:#373a3c}pre code{padding:0;font-size:inherit;color:inherit;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px}.container::after{content:"";display:table;clear:both}@media (min-width:576px){.container{width:540px;max-width:100%}}@media (min-width:768px){.container{width:720px;max-width:100%}}@media (min-width:992px){.container{width:960px;max-width:100%}}@media (min-width:1200px){.container{width:1140px;max-width:100%}}.container-fluid{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px}.container-fluid::after{content:"";display:table;clear:both}.row{margin-right:-15px;margin-left:-15px}.row::after{content:"";display:table;clear:both}@media (min-width:576px){.row{margin-right:-15px;margin-left:-15px}}@media (min-width:768px){.row{margin-right:-15px;margin-left:-15px}}@media (min-width:992px){.row{margin-right:-15px;margin-left:-15px}}@media (min-width:1200px){.row{margin-right:-15px;margin-left:-15px}}.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xs,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}@media (min-width:576px){.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xs,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{padding-right:15px;padding-left:15px}}@media (min-width:768px){.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xs,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{padding-right:15px;padding-left:15px}}@media (min-width:992px){.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xs,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{padding-right:15px;padding-left:15px}}@media (min-width:1200px){.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xs,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{padding-right:15px;padding-left:15px}}.col-xs-1{float:left;width:8.333333%}.col-xs-2{float:left;width:16.666667%}.col-xs-3{float:left;width:25%}.col-xs-4{float:left;width:33.333333%}.col-xs-5{float:left;width:41.666667%}.col-xs-6{float:left;width:50%}.col-xs-7{float:left;width:58.333333%}.col-xs-8{float:left;width:66.666667%}.col-xs-9{float:left;width:75%}.col-xs-10{float:left;width:83.333333%}.col-xs-11{float:left;width:91.666667%}.col-xs-12{float:left;width:100%}.pull-xs-0{right:auto}.pull-xs-1{right:8.333333%}.pull-xs-2{right:16.666667%}.pull-xs-3{right:25%}.pull-xs-4{right:33.333333%}.pull-xs-5{right:41.666667%}.pull-xs-6{right:50%}.pull-xs-7{right:58.333333%}.pull-xs-8{right:66.666667%}.pull-xs-9{right:75%}.pull-xs-10{right:83.333333%}.pull-xs-11{right:91.666667%}.pull-xs-12{right:100%}.push-xs-0{left:auto}.push-xs-1{left:8.333333%}.push-xs-2{left:16.666667%}.push-xs-3{left:25%}.push-xs-4{left:33.333333%}.push-xs-5{left:41.666667%}.push-xs-6{left:50%}.push-xs-7{left:58.333333%}.push-xs-8{left:66.666667%}.push-xs-9{left:75%}.push-xs-10{left:83.333333%}.push-xs-11{left:91.666667%}.push-xs-12{left:100%}.offset-xs-1{margin-left:8.333333%}.offset-xs-2{margin-left:16.666667%}.offset-xs-3{margin-left:25%}.offset-xs-4{margin-left:33.333333%}.offset-xs-5{margin-left:41.666667%}.offset-xs-6{margin-left:50%}.offset-xs-7{margin-left:58.333333%}.offset-xs-8{margin-left:66.666667%}.offset-xs-9{margin-left:75%}.offset-xs-10{margin-left:83.333333%}.offset-xs-11{margin-left:91.666667%}@media (min-width:576px){.col-sm-1{float:left;width:8.333333%}.col-sm-2{float:left;width:16.666667%}.col-sm-3{float:left;width:25%}.col-sm-4{float:left;width:33.333333%}.col-sm-5{float:left;width:41.666667%}.col-sm-6{float:left;width:50%}.col-sm-7{float:left;width:58.333333%}.col-sm-8{float:left;width:66.666667%}.col-sm-9{float:left;width:75%}.col-sm-10{float:left;width:83.333333%}.col-sm-11{float:left;width:91.666667%}.col-sm-12{float:left;width:100%}.pull-sm-0{right:auto}.pull-sm-1{right:8.333333%}.pull-sm-2{right:16.666667%}.pull-sm-3{right:25%}.pull-sm-4{right:33.333333%}.pull-sm-5{right:41.666667%}.pull-sm-6{right:50%}.pull-sm-7{right:58.333333%}.pull-sm-8{right:66.666667%}.pull-sm-9{right:75%}.pull-sm-10{right:83.333333%}.pull-sm-11{right:91.666667%}.pull-sm-12{right:100%}.push-sm-0{left:auto}.push-sm-1{left:8.333333%}.push-sm-2{left:16.666667%}.push-sm-3{left:25%}.push-sm-4{left:33.333333%}.push-sm-5{left:41.666667%}.push-sm-6{left:50%}.push-sm-7{left:58.333333%}.push-sm-8{left:66.666667%}.push-sm-9{left:75%}.push-sm-10{left:83.333333%}.push-sm-11{left:91.666667%}.push-sm-12{left:100%}.offset-sm-0{margin-left:0%}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md-1{float:left;width:8.333333%}.col-md-2{float:left;width:16.666667%}.col-md-3{float:left;width:25%}.col-md-4{float:left;width:33.333333%}.col-md-5{float:left;width:41.666667%}.col-md-6{float:left;width:50%}.col-md-7{float:left;width:58.333333%}.col-md-8{float:left;width:66.666667%}.col-md-9{float:left;width:75%}.col-md-10{float:left;width:83.333333%}.col-md-11{float:left;width:91.666667%}.col-md-12{float:left;width:100%}.pull-md-0{right:auto}.pull-md-1{right:8.333333%}.pull-md-2{right:16.666667%}.pull-md-3{right:25%}.pull-md-4{right:33.333333%}.pull-md-5{right:41.666667%}.pull-md-6{right:50%}.pull-md-7{right:58.333333%}.pull-md-8{right:66.666667%}.pull-md-9{right:75%}.pull-md-10{right:83.333333%}.pull-md-11{right:91.666667%}.pull-md-12{right:100%}.push-md-0{left:auto}.push-md-1{left:8.333333%}.push-md-2{left:16.666667%}.push-md-3{left:25%}.push-md-4{left:33.333333%}.push-md-5{left:41.666667%}.push-md-6{left:50%}.push-md-7{left:58.333333%}.push-md-8{left:66.666667%}.push-md-9{left:75%}.push-md-10{left:83.333333%}.push-md-11{left:91.666667%}.push-md-12{left:100%}.offset-md-0{margin-left:0%}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg-1{float:left;width:8.333333%}.col-lg-2{float:left;width:16.666667%}.col-lg-3{float:left;width:25%}.col-lg-4{float:left;width:33.333333%}.col-lg-5{float:left;width:41.666667%}.col-lg-6{float:left;width:50%}.col-lg-7{float:left;width:58.333333%}.col-lg-8{float:left;width:66.666667%}.col-lg-9{float:left;width:75%}.col-lg-10{float:left;width:83.333333%}.col-lg-11{float:left;width:91.666667%}.col-lg-12{float:left;width:100%}.pull-lg-0{right:auto}.pull-lg-1{right:8.333333%}.pull-lg-2{right:16.666667%}.pull-lg-3{right:25%}.pull-lg-4{right:33.333333%}.pull-lg-5{right:41.666667%}.pull-lg-6{right:50%}.pull-lg-7{right:58.333333%}.pull-lg-8{right:66.666667%}.pull-lg-9{right:75%}.pull-lg-10{right:83.333333%}.pull-lg-11{right:91.666667%}.pull-lg-12{right:100%}.push-lg-0{left:auto}.push-lg-1{left:8.333333%}.push-lg-2{left:16.666667%}.push-lg-3{left:25%}.push-lg-4{left:33.333333%}.push-lg-5{left:41.666667%}.push-lg-6{left:50%}.push-lg-7{left:58.333333%}.push-lg-8{left:66.666667%}.push-lg-9{left:75%}.push-lg-10{left:83.333333%}.push-lg-11{left:91.666667%}.push-lg-12{left:100%}.offset-lg-0{margin-left:0%}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl-1{float:left;width:8.333333%}.col-xl-2{float:left;width:16.666667%}.col-xl-3{float:left;width:25%}.col-xl-4{float:left;width:33.333333%}.col-xl-5{float:left;width:41.666667%}.col-xl-6{float:left;width:50%}.col-xl-7{float:left;width:58.333333%}.col-xl-8{float:left;width:66.666667%}.col-xl-9{float:left;width:75%}.col-xl-10{float:left;width:83.333333%}.col-xl-11{float:left;width:91.666667%}.col-xl-12{float:left;width:100%}.pull-xl-0{right:auto}.pull-xl-1{right:8.333333%}.pull-xl-2{right:16.666667%}.pull-xl-3{right:25%}.pull-xl-4{right:33.333333%}.pull-xl-5{right:41.666667%}.pull-xl-6{right:50%}.pull-xl-7{right:58.333333%}.pull-xl-8{right:66.666667%}.pull-xl-9{right:75%}.pull-xl-10{right:83.333333%}.pull-xl-11{right:91.666667%}.pull-xl-12{right:100%}.push-xl-0{left:auto}.push-xl-1{left:8.333333%}.push-xl-2{left:16.666667%}.push-xl-3{left:25%}.push-xl-4{left:33.333333%}.push-xl-5{left:41.666667%}.push-xl-6{left:50%}.push-xl-7{left:58.333333%}.push-xl-8{left:66.666667%}.push-xl-9{left:75%}.push-xl-10{left:83.333333%}.push-xl-11{left:91.666667%}.push-xl-12{left:100%}.offset-xl-0{margin-left:0%}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;max-width:100%;margin-bottom:1rem}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #eceeef}.table thead th{vertical-align:bottom;border-bottom:2px solid #eceeef}.table tbody+tbody{border-top:2px solid #eceeef}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #eceeef}.table-bordered td,.table-bordered th{border:1px solid #eceeef}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:rgba(0,0,0,.075)}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table-success,.table-success>td,.table-success>th{background-color:#dff0d8}.table-hover .table-success:hover{background-color:#d0e9c6}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#d0e9c6}.table-info,.table-info>td,.table-info>th{background-color:#d9edf7}.table-hover .table-info:hover{background-color:#c4e3f3}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#c4e3f3}.table-warning,.table-warning>td,.table-warning>th{background-color:#fcf8e3}.table-hover .table-warning:hover{background-color:#faf2cc}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#faf2cc}.table-danger,.table-danger>td,.table-danger>th{background-color:#f2dede}.table-hover .table-danger:hover{background-color:#ebcccc}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#ebcccc}.thead-inverse th{color:#fff;background-color:#373a3c}.thead-default th{color:#55595c;background-color:#eceeef}.table-inverse{color:#eceeef;background-color:#373a3c}.table-inverse td,.table-inverse th,.table-inverse thead th{border-color:#55595c}.table-inverse.table-bordered{border:0}.table-responsive{display:block;width:100%;min-height:0%;overflow-x:auto}.table-reflow thead{float:left}.table-reflow tbody{display:block;white-space:nowrap}.table-reflow td,.table-reflow th{border-top:1px solid #eceeef;border-left:1px solid #eceeef}.table-reflow td:last-child,.table-reflow th:last-child{border-right:1px solid #eceeef}.table-reflow tbody:last-child tr:last-child td,.table-reflow tbody:last-child tr:last-child th,.table-reflow tfoot:last-child tr:last-child td,.table-reflow tfoot:last-child tr:last-child th,.table-reflow thead:last-child tr:last-child td,.table-reflow thead:last-child tr:last-child th{border-bottom:1px solid #eceeef}.table-reflow tr{float:left}.table-reflow tr td,.table-reflow tr th{display:block!important;border:1px solid #eceeef}.form-control{display:block;width:100%;padding:.5rem .75rem;font-size:1rem;line-height:1.25;color:#55595c;background-color:#fff;background-image:none;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#55595c;background-color:#fff;border-color:#66afe9;outline:0}.form-control::-webkit-input-placeholder{color:#999;opacity:1}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999;opacity:1}.form-control::placeholder{color:#999;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#eceeef;opacity:1}.form-control:disabled{cursor:not-allowed}select.form-control:not([size]):not([multiple]){height:calc(2.5rem - 2px)}select.form-control:focus::-ms-value{color:#55595c;background-color:#fff}.form-control-file,.form-control-range{display:block}.col-form-label{padding-top:.5rem;padding-bottom:.5rem;margin-bottom:0}.col-form-label-lg{padding-top:.75rem;padding-bottom:.75rem;font-size:1.25rem}.col-form-label-sm{padding-top:.25rem;padding-bottom:.25rem;font-size:.875rem}.col-form-legend{padding-top:.5rem;padding-bottom:.5rem;margin-bottom:0;font-size:1rem}.form-control-static{padding-top:.5rem;padding-bottom:.5rem;line-height:1.25;border:solid transparent;border-width:1px 0}.form-control-static.form-control-lg,.form-control-static.form-control-sm,.input-group-lg>.form-control-static.form-control,.input-group-lg>.form-control-static.input-group-addon,.input-group-lg>.input-group-btn>.form-control-static.btn,.input-group-sm>.form-control-static.form-control,.input-group-sm>.form-control-static.input-group-addon,.input-group-sm>.input-group-btn>.form-control-static.btn{padding-right:0;padding-left:0}.form-control-sm,.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-sm>.input-group-btn>select.btn:not([size]):not([multiple]),.input-group-sm>select.form-control:not([size]):not([multiple]),.input-group-sm>select.input-group-addon:not([size]):not([multiple]),select.form-control-sm:not([size]):not([multiple]){height:1.8125rem}.form-control-lg,.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{padding:.75rem 1.5rem;font-size:1.25rem;border-radius:.3rem}.input-group-lg>.input-group-btn>select.btn:not([size]):not([multiple]),.input-group-lg>select.form-control:not([size]):not([multiple]),.input-group-lg>select.input-group-addon:not([size]):not([multiple]),select.form-control-lg:not([size]):not([multiple]){height:3.166667rem}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-check{position:relative;display:block;margin-bottom:.75rem}.form-check+.form-check{margin-top:-.25rem}.form-check.disabled .form-check-label{color:#818a91;cursor:not-allowed}.form-check-label{padding-left:1.25rem;margin-bottom:0;cursor:pointer}.form-check-input{position:absolute;margin-top:.25rem;margin-left:-1.25rem}.form-check-input:only-child{position:static}.form-check-inline{position:relative;display:inline-block;padding-left:1.25rem;margin-bottom:0;vertical-align:middle;cursor:pointer}.form-check-inline+.form-check-inline{margin-left:.75rem}.form-check-inline.disabled{color:#818a91;cursor:not-allowed}.form-control-feedback{margin-top:.25rem}.form-control-danger,.form-control-success,.form-control-warning{padding-right:2.25rem;background-repeat:no-repeat;background-position:center right .625rem;-webkit-background-size:1.25rem 1.25rem;background-size:1.25rem 1.25rem}.has-success .custom-control,.has-success .form-check-inline,.has-success .form-check-label,.has-success .form-control-feedback,.has-success .form-control-label{color:#5cb85c}.has-success .form-control{border-color:#5cb85c}.has-success .form-control:focus{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #a3d7a3;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #a3d7a3}.has-success .input-group-addon{color:#5cb85c;border-color:#5cb85c;background-color:#eaf6ea}.has-success .form-control-success{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#5cb85c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E")}.has-warning .custom-control,.has-warning .form-check-inline,.has-warning .form-check-label,.has-warning .form-control-feedback,.has-warning .form-control-label{color:#f0ad4e}.has-warning .form-control{border-color:#f0ad4e}.has-warning .form-control:focus{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #f8d9ac;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #f8d9ac}.has-warning .input-group-addon{color:#f0ad4e;border-color:#f0ad4e;background-color:#fff}.has-warning .form-control-warning{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#f0ad4e' d='M4.4 5.324h-.8v-2.46h.8zm0 1.42h-.8V5.89h.8zM3.76.63L.04 7.075c-.115.2.016.425.26.426h7.397c.242 0 .372-.226.258-.426C6.726 4.924 5.47 2.79 4.253.63c-.113-.174-.39-.174-.494 0z'/%3E%3C/svg%3E")}.has-danger .custom-control,.has-danger .form-check-inline,.has-danger .form-check-label,.has-danger .form-control-feedback,.has-danger .form-control-label{color:#d9534f}.has-danger .form-control{border-color:#d9534f}.has-danger .form-control:focus{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #eba5a3;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #eba5a3}.has-danger .input-group-addon{color:#d9534f;border-color:#d9534f;background-color:#fdf7f7}.has-danger .form-control-danger{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#d9534f' viewBox='-2 -2 7 7'%3E%3Cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3E%3Ccircle r='.5'/%3E%3Ccircle cx='3' r='.5'/%3E%3Ccircle cy='3' r='.5'/%3E%3Ccircle cx='3' cy='3' r='.5'/%3E%3C/svg%3E")}@media (min-width:576px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;width:auto;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .form-control-label{margin-bottom:0;vertical-align:middle}.form-inline .form-check{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .form-check-label{padding-left:0}.form-inline .form-check-input{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.btn{display:inline-block;font-weight:400;line-height:1.25;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;padding:.5rem 1rem;font-size:1rem;border-radius:.25rem}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:focus,.btn:hover{text-decoration:none}.btn.focus{text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0}.btn.disabled,.btn:disabled{cursor:not-allowed;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#0275d8;border-color:#0275d8}.btn-primary:hover{color:#fff;background-color:#025aa5;border-color:#01549b}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#025aa5;border-color:#01549b}.btn-primary.active,.btn-primary:active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#025aa5;border-color:#01549b;background-image:none}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.btn-primary.dropdown-toggle.focus,.open>.btn-primary.dropdown-toggle:focus,.open>.btn-primary.dropdown-toggle:hover{color:#fff;background-color:#014682;border-color:#01315a}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary:disabled.focus,.btn-primary:disabled:focus{background-color:#0275d8;border-color:#0275d8}.btn-primary.disabled:hover,.btn-primary:disabled:hover{background-color:#0275d8;border-color:#0275d8}.btn-secondary{color:#373a3c;background-color:#fff;border-color:#ccc}.btn-secondary:hover{color:#373a3c;background-color:#e6e6e6;border-color:#adadad}.btn-secondary.focus,.btn-secondary:focus{color:#373a3c;background-color:#e6e6e6;border-color:#adadad}.btn-secondary.active,.btn-secondary:active,.open>.btn-secondary.dropdown-toggle{color:#373a3c;background-color:#e6e6e6;border-color:#adadad;background-image:none}.btn-secondary.active.focus,.btn-secondary.active:focus,.btn-secondary.active:hover,.btn-secondary:active.focus,.btn-secondary:active:focus,.btn-secondary:active:hover,.open>.btn-secondary.dropdown-toggle.focus,.open>.btn-secondary.dropdown-toggle:focus,.open>.btn-secondary.dropdown-toggle:hover{color:#373a3c;background-color:#d4d4d4;border-color:#8c8c8c}.btn-secondary.disabled.focus,.btn-secondary.disabled:focus,.btn-secondary:disabled.focus,.btn-secondary:disabled:focus{background-color:#fff;border-color:#ccc}.btn-secondary.disabled:hover,.btn-secondary:disabled:hover{background-color:#fff;border-color:#ccc}.btn-info{color:#fff;background-color:#5bc0de;border-color:#5bc0de}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#2aabd2}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#2aabd2}.btn-info.active,.btn-info:active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;border-color:#2aabd2;background-image:none}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.btn-info.dropdown-toggle.focus,.open>.btn-info.dropdown-toggle:focus,.open>.btn-info.dropdown-toggle:hover{color:#fff;background-color:#269abc;border-color:#1f7e9a}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info:disabled.focus,.btn-info:disabled:focus{background-color:#5bc0de;border-color:#5bc0de}.btn-info.disabled:hover,.btn-info:disabled:hover{background-color:#5bc0de;border-color:#5bc0de}.btn-success{color:#fff;background-color:#5cb85c;border-color:#5cb85c}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#419641}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#419641}.btn-success.active,.btn-success:active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#419641;background-image:none}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.btn-success.dropdown-toggle.focus,.open>.btn-success.dropdown-toggle:focus,.open>.btn-success.dropdown-toggle:hover{color:#fff;background-color:#398439;border-color:#2d672d}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success:disabled.focus,.btn-success:disabled:focus{background-color:#5cb85c;border-color:#5cb85c}.btn-success.disabled:hover,.btn-success:disabled:hover{background-color:#5cb85c;border-color:#5cb85c}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#f0ad4e}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#eb9316}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#eb9316}.btn-warning.active,.btn-warning:active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#eb9316;background-image:none}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.btn-warning.dropdown-toggle.focus,.open>.btn-warning.dropdown-toggle:focus,.open>.btn-warning.dropdown-toggle:hover{color:#fff;background-color:#d58512;border-color:#b06d0f}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning:disabled.focus,.btn-warning:disabled:focus{background-color:#f0ad4e;border-color:#f0ad4e}.btn-warning.disabled:hover,.btn-warning:disabled:hover{background-color:#f0ad4e;border-color:#f0ad4e}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#c12e2a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#c12e2a}.btn-danger.active,.btn-danger:active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;border-color:#c12e2a;background-image:none}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.btn-danger.dropdown-toggle.focus,.open>.btn-danger.dropdown-toggle:focus,.open>.btn-danger.dropdown-toggle:hover{color:#fff;background-color:#ac2925;border-color:#8b211e}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger:disabled.focus,.btn-danger:disabled:focus{background-color:#d9534f;border-color:#d9534f}.btn-danger.disabled:hover,.btn-danger:disabled:hover{background-color:#d9534f;border-color:#d9534f}.btn-outline-primary{color:#0275d8;background-image:none;background-color:transparent;border-color:#0275d8}.btn-outline-primary:hover{color:#fff;background-color:#0275d8;border-color:#0275d8}.btn-outline-primary.focus,.btn-outline-primary:focus{color:#fff;background-color:#0275d8;border-color:#0275d8}.btn-outline-primary.active,.btn-outline-primary:active,.open>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#0275d8;border-color:#0275d8}.btn-outline-primary.active.focus,.btn-outline-primary.active:focus,.btn-outline-primary.active:hover,.btn-outline-primary:active.focus,.btn-outline-primary:active:focus,.btn-outline-primary:active:hover,.open>.btn-outline-primary.dropdown-toggle.focus,.open>.btn-outline-primary.dropdown-toggle:focus,.open>.btn-outline-primary.dropdown-toggle:hover{color:#fff;background-color:#014682;border-color:#01315a}.btn-outline-primary.disabled.focus,.btn-outline-primary.disabled:focus,.btn-outline-primary:disabled.focus,.btn-outline-primary:disabled:focus{border-color:#43a7fd}.btn-outline-primary.disabled:hover,.btn-outline-primary:disabled:hover{border-color:#43a7fd}.btn-outline-secondary{color:#ccc;background-image:none;background-color:transparent;border-color:#ccc}.btn-outline-secondary:hover{color:#fff;background-color:#ccc;border-color:#ccc}.btn-outline-secondary.focus,.btn-outline-secondary:focus{color:#fff;background-color:#ccc;border-color:#ccc}.btn-outline-secondary.active,.btn-outline-secondary:active,.open>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#ccc;border-color:#ccc}.btn-outline-secondary.active.focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.active:hover,.btn-outline-secondary:active.focus,.btn-outline-secondary:active:focus,.btn-outline-secondary:active:hover,.open>.btn-outline-secondary.dropdown-toggle.focus,.open>.btn-outline-secondary.dropdown-toggle:focus,.open>.btn-outline-secondary.dropdown-toggle:hover{color:#fff;background-color:#a1a1a1;border-color:#8c8c8c}.btn-outline-secondary.disabled.focus,.btn-outline-secondary.disabled:focus,.btn-outline-secondary:disabled.focus,.btn-outline-secondary:disabled:focus{border-color:#fff}.btn-outline-secondary.disabled:hover,.btn-outline-secondary:disabled:hover{border-color:#fff}.btn-outline-info{color:#5bc0de;background-image:none;background-color:transparent;border-color:#5bc0de}.btn-outline-info:hover{color:#fff;background-color:#5bc0de;border-color:#5bc0de}.btn-outline-info.focus,.btn-outline-info:focus{color:#fff;background-color:#5bc0de;border-color:#5bc0de}.btn-outline-info.active,.btn-outline-info:active,.open>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#5bc0de;border-color:#5bc0de}.btn-outline-info.active.focus,.btn-outline-info.active:focus,.btn-outline-info.active:hover,.btn-outline-info:active.focus,.btn-outline-info:active:focus,.btn-outline-info:active:hover,.open>.btn-outline-info.dropdown-toggle.focus,.open>.btn-outline-info.dropdown-toggle:focus,.open>.btn-outline-info.dropdown-toggle:hover{color:#fff;background-color:#269abc;border-color:#1f7e9a}.btn-outline-info.disabled.focus,.btn-outline-info.disabled:focus,.btn-outline-info:disabled.focus,.btn-outline-info:disabled:focus{border-color:#b0e1ef}.btn-outline-info.disabled:hover,.btn-outline-info:disabled:hover{border-color:#b0e1ef}.btn-outline-success{color:#5cb85c;background-image:none;background-color:transparent;border-color:#5cb85c}.btn-outline-success:hover{color:#fff;background-color:#5cb85c;border-color:#5cb85c}.btn-outline-success.focus,.btn-outline-success:focus{color:#fff;background-color:#5cb85c;border-color:#5cb85c}.btn-outline-success.active,.btn-outline-success:active,.open>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#5cb85c;border-color:#5cb85c}.btn-outline-success.active.focus,.btn-outline-success.active:focus,.btn-outline-success.active:hover,.btn-outline-success:active.focus,.btn-outline-success:active:focus,.btn-outline-success:active:hover,.open>.btn-outline-success.dropdown-toggle.focus,.open>.btn-outline-success.dropdown-toggle:focus,.open>.btn-outline-success.dropdown-toggle:hover{color:#fff;background-color:#398439;border-color:#2d672d}.btn-outline-success.disabled.focus,.btn-outline-success.disabled:focus,.btn-outline-success:disabled.focus,.btn-outline-success:disabled:focus{border-color:#a3d7a3}.btn-outline-success.disabled:hover,.btn-outline-success:disabled:hover{border-color:#a3d7a3}.btn-outline-warning{color:#f0ad4e;background-image:none;background-color:transparent;border-color:#f0ad4e}.btn-outline-warning:hover{color:#fff;background-color:#f0ad4e;border-color:#f0ad4e}.btn-outline-warning.focus,.btn-outline-warning:focus{color:#fff;background-color:#f0ad4e;border-color:#f0ad4e}.btn-outline-warning.active,.btn-outline-warning:active,.open>.btn-outline-warning.dropdown-toggle{color:#fff;background-color:#f0ad4e;border-color:#f0ad4e}.btn-outline-warning.active.focus,.btn-outline-warning.active:focus,.btn-outline-warning.active:hover,.btn-outline-warning:active.focus,.btn-outline-warning:active:focus,.btn-outline-warning:active:hover,.open>.btn-outline-warning.dropdown-toggle.focus,.open>.btn-outline-warning.dropdown-toggle:focus,.open>.btn-outline-warning.dropdown-toggle:hover{color:#fff;background-color:#d58512;border-color:#b06d0f}.btn-outline-warning.disabled.focus,.btn-outline-warning.disabled:focus,.btn-outline-warning:disabled.focus,.btn-outline-warning:disabled:focus{border-color:#f8d9ac}.btn-outline-warning.disabled:hover,.btn-outline-warning:disabled:hover{border-color:#f8d9ac}.btn-outline-danger{color:#d9534f;background-image:none;background-color:transparent;border-color:#d9534f}.btn-outline-danger:hover{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-outline-danger.focus,.btn-outline-danger:focus{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-outline-danger.active,.btn-outline-danger:active,.open>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-outline-danger.active.focus,.btn-outline-danger.active:focus,.btn-outline-danger.active:hover,.btn-outline-danger:active.focus,.btn-outline-danger:active:focus,.btn-outline-danger:active:hover,.open>.btn-outline-danger.dropdown-toggle.focus,.open>.btn-outline-danger.dropdown-toggle:focus,.open>.btn-outline-danger.dropdown-toggle:hover{color:#fff;background-color:#ac2925;border-color:#8b211e}.btn-outline-danger.disabled.focus,.btn-outline-danger.disabled:focus,.btn-outline-danger:disabled.focus,.btn-outline-danger:disabled:focus{border-color:#eba5a3}.btn-outline-danger.disabled:hover,.btn-outline-danger:disabled:hover{border-color:#eba5a3}.btn-link{font-weight:400;color:#0275d8;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link:disabled{background-color:transparent}.btn-link,.btn-link:active,.btn-link:focus{border-color:transparent}.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#014c8c;text-decoration:underline;background-color:transparent}.btn-link:disabled:focus,.btn-link:disabled:hover{color:#818a91;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:.75rem 1.5rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height;-o-transition-property:height;transition-property:height}.dropdown,.dropup{position:relative}.dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.3em;vertical-align:middle;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-left:.3em solid transparent}.dropdown-toggle:focus{outline:0}.dropup .dropdown-toggle::after{border-top:0;border-bottom:.3em solid}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#373a3c;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-divider{height:1px;margin:.5rem 0;overflow:hidden;background-color:#e5e5e5}.dropdown-item{display:block;width:100%;padding:3px 1.5rem;clear:both;font-weight:400;color:#373a3c;text-align:inherit;white-space:nowrap;background:0 0;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#2b2d2f;text-decoration:none;background-color:#f5f5f5}.dropdown-item.active,.dropdown-item.active:focus,.dropdown-item.active:hover{color:#fff;text-decoration:none;background-color:#0275d8;outline:0}.dropdown-item.disabled,.dropdown-item.disabled:focus,.dropdown-item.disabled:hover{color:#818a91}.dropdown-item.disabled:focus,.dropdown-item.disabled:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:"progid:DXImageTransform.Microsoft.gradient(enabled = false)"}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#818a91;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:.3em solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:.125rem}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left;margin-bottom:0}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:2}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-.5rem}.btn-toolbar::after{content:"";display:table;clear:both}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:.5rem}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn+.dropdown-toggle-split::after{margin-left:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:1.125rem;padding-left:1.125rem}.btn .caret{margin-left:0}.btn-group-lg>.btn .caret,.btn-lg .caret{border-width:.3em .3em 0;border-bottom-width:0}.dropup .btn-group-lg>.btn .caret,.dropup .btn-lg .caret{border-width:0 .3em .3em}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group::after{content:"";display:table;clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;width:100%;display:table;border-collapse:separate}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:active,.input-group .form-control:focus,.input-group .form-control:hover{z-index:3}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.25;color:#55595c;text-align:center;background-color:#eceeef;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.input-group-addon.form-control-sm,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.input-group-addon.btn{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-addon.form-control-lg,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.input-group-addon.btn{padding:.75rem 1.5rem;font-size:1.25rem;border-radius:.3rem}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:not(:last-child),.input-group-addon:not(:last-child),.input-group-btn:not(:first-child)>.btn-group:not(:last-child)>.btn,.input-group-btn:not(:first-child)>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:not(:last-child)>.btn,.input-group-btn:not(:last-child)>.btn-group>.btn,.input-group-btn:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:not(:last-child){border-right:0}.input-group .form-control:not(:first-child),.input-group-addon:not(:first-child),.input-group-btn:not(:first-child)>.btn,.input-group-btn:not(:first-child)>.btn-group>.btn,.input-group-btn:not(:first-child)>.dropdown-toggle,.input-group-btn:not(:last-child)>.btn-group:not(:first-child)>.btn,.input-group-btn:not(:last-child)>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.form-control+.input-group-addon:not(:first-child){border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:3}.input-group-btn:not(:last-child)>.btn,.input-group-btn:not(:last-child)>.btn-group{margin-right:-1px}.input-group-btn:not(:first-child)>.btn,.input-group-btn:not(:first-child)>.btn-group{z-index:2;margin-left:-1px}.input-group-btn:not(:first-child)>.btn-group:active,.input-group-btn:not(:first-child)>.btn-group:focus,.input-group-btn:not(:first-child)>.btn-group:hover,.input-group-btn:not(:first-child)>.btn:active,.input-group-btn:not(:first-child)>.btn:focus,.input-group-btn:not(:first-child)>.btn:hover{z-index:3}.custom-control{position:relative;display:inline-block;padding-left:1.5rem;cursor:pointer}.custom-control+.custom-control{margin-left:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-indicator{color:#fff;background-color:#0074d9}.custom-control-input:focus~.custom-control-indicator{-webkit-box-shadow:0 0 0 .075rem #fff,0 0 0 .2rem #0074d9;box-shadow:0 0 0 .075rem #fff,0 0 0 .2rem #0074d9}.custom-control-input:active~.custom-control-indicator{color:#fff;background-color:#84c6ff}.custom-control-input:disabled~.custom-control-indicator{cursor:not-allowed;background-color:#eee}.custom-control-input:disabled~.custom-control-description{color:#767676;cursor:not-allowed}.custom-control-indicator{position:absolute;top:.25rem;left:0;display:block;width:1rem;height:1rem;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#ddd;background-repeat:no-repeat;background-position:center center;-webkit-background-size:50% 50%;background-size:50% 50%}.custom-checkbox .custom-control-indicator{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-indicator{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-indicator{background-color:#0074d9;background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='#fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-radio .custom-control-indicator{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-indicator{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='#fff'/%3E%3C/svg%3E")}.custom-controls-stacked .custom-control{float:left;clear:left}.custom-controls-stacked .custom-control+.custom-control{margin-left:0}.custom-select{display:inline-block;max-width:100%;height:calc(2.5rem - 2px);padding:.375rem 1.75rem .375rem .75rem;padding-right:.75rem\9;color:#55595c;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='#333' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center;background-image:none\9;-webkit-background-size:8px 10px;background-size:8px 10px;border:1px solid rgba(0,0,0,.15);border-radius:.25rem;-moz-appearance:none;-webkit-appearance:none}.custom-select:focus{border-color:#51a7e8;outline:0}.custom-select:focus::-ms-value{color:#55595c;background-color:#fff}.custom-select:disabled{color:#818a91;cursor:not-allowed;background-color:#eceeef}.custom-select::-ms-expand{opacity:0}.custom-select-sm{padding-top:.375rem;padding-bottom:.375rem;font-size:75%}.custom-file{position:relative;display:inline-block;max-width:100%;height:2.5rem;cursor:pointer}.custom-file-input{min-width:14rem;max-width:100%;margin:0;filter:alpha(opacity=0);opacity:0}.custom-file-control{position:absolute;top:0;right:0;left:0;z-index:5;height:2.5rem;padding:.5rem 1rem;line-height:1.5;color:#555;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#fff;border:1px solid #ddd;border-radius:.25rem}.custom-file-control:lang(en)::after{content:"Choose file..."}.custom-file-control::before{position:absolute;top:-1px;right:-1px;bottom:-1px;z-index:6;display:block;height:2.5rem;padding:.5rem 1rem;line-height:1.5;color:#555;background-color:#eee;border:1px solid #ddd;border-radius:0 .25rem .25rem 0}.custom-file-control:lang(en)::before{content:"Browse"}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:inline-block}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#818a91}.nav-link.disabled,.nav-link.disabled:focus,.nav-link.disabled:hover{color:#818a91;cursor:not-allowed;background-color:transparent}.nav-inline .nav-item{display:inline-block}.nav-inline .nav-item+.nav-item,.nav-inline .nav-link+.nav-link{margin-left:1rem}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs::after{content:"";display:table;clear:both}.nav-tabs .nav-item{float:left;margin-bottom:-1px}.nav-tabs .nav-item+.nav-item{margin-left:.2rem}.nav-tabs .nav-link{display:block;padding:.5em 1em;border:1px solid transparent;border-top-right-radius:.25rem;border-top-left-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#eceeef #eceeef #ddd}.nav-tabs .nav-link.disabled,.nav-tabs .nav-link.disabled:focus,.nav-tabs .nav-link.disabled:hover{color:#818a91;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.open .nav-link,.nav-tabs .nav-item.open .nav-link:focus,.nav-tabs .nav-item.open .nav-link:hover,.nav-tabs .nav-link.active,.nav-tabs .nav-link.active:focus,.nav-tabs .nav-link.active:hover{color:#55595c;background-color:#fff;border-color:#ddd #ddd transparent}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.nav-pills::after{content:"";display:table;clear:both}.nav-pills .nav-item{float:left}.nav-pills .nav-item+.nav-item{margin-left:.2rem}.nav-pills .nav-link{display:block;padding:.5em 1em;border-radius:.25rem}.nav-pills .nav-item.open .nav-link,.nav-pills .nav-item.open .nav-link:focus,.nav-pills .nav-item.open .nav-link:hover,.nav-pills .nav-link.active,.nav-pills .nav-link.active:focus,.nav-pills .nav-link.active:hover{color:#fff;cursor:default;background-color:#0275d8}.nav-stacked .nav-item{display:block;float:none}.nav-stacked .nav-item+.nav-item{margin-top:.2rem;margin-left:0}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;padding:.5rem 1rem}.navbar::after{content:"";display:table;clear:both}@media (min-width:576px){.navbar{border-radius:.25rem}}.navbar-full{z-index:1000}@media (min-width:576px){.navbar-full{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:576px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0}.navbar-fixed-bottom{bottom:0}.navbar-sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1030;width:100%}@media (min-width:576px){.navbar-sticky-top{border-radius:0}}.navbar-brand{float:left;padding-top:.25rem;padding-bottom:.25rem;margin-right:1rem;font-size:1.25rem;line-height:inherit}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-divider{float:left;width:1px;padding-top:.425rem;padding-bottom:.425rem;margin-right:1rem;margin-left:1rem;overflow:hidden}.navbar-divider::before{content:"\00a0"}.navbar-text{display:inline-block;padding-top:.425rem;padding-bottom:.425rem}.navbar-toggler{width:2.5em;height:2em;padding:.5rem .75rem;font-size:1.25rem;line-height:1;background:transparent no-repeat center center;-webkit-background-size:24px 24px;background-size:24px 24px;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggleable-xs::after{content:"";display:table;clear:both}@media (max-width:575px){.navbar-toggleable-xs .navbar-brand{display:block;float:none;margin-top:.5rem;margin-right:0}.navbar-toggleable-xs .navbar-nav{margin-top:.5rem;margin-bottom:.5rem}.navbar-toggleable-xs .navbar-nav .dropdown-menu{position:static;float:none}}@media (min-width:576px){.navbar-toggleable-xs{display:block}}.navbar-toggleable-sm::after{content:"";display:table;clear:both}@media (max-width:767px){.navbar-toggleable-sm .navbar-brand{display:block;float:none;margin-top:.5rem;margin-right:0}.navbar-toggleable-sm .navbar-nav{margin-top:.5rem;margin-bottom:.5rem}.navbar-toggleable-sm .navbar-nav .dropdown-menu{position:static;float:none}}@media (min-width:768px){.navbar-toggleable-sm{display:block}}.navbar-toggleable-md::after{content:"";display:table;clear:both}@media (max-width:991px){.navbar-toggleable-md .navbar-brand{display:block;float:none;margin-top:.5rem;margin-right:0}.navbar-toggleable-md .navbar-nav{margin-top:.5rem;margin-bottom:.5rem}.navbar-toggleable-md .navbar-nav .dropdown-menu{position:static;float:none}}@media (min-width:992px){.navbar-toggleable-md{display:block}}.navbar-toggleable-lg::after{content:"";display:table;clear:both}@media (max-width:1199px){.navbar-toggleable-lg .navbar-brand{display:block;float:none;margin-top:.5rem;margin-right:0}.navbar-toggleable-lg .navbar-nav{margin-top:.5rem;margin-bottom:.5rem}.navbar-toggleable-lg .navbar-nav .dropdown-menu{position:static;float:none}}@media (min-width:1200px){.navbar-toggleable-lg{display:block}}.navbar-toggleable-xl{display:block}.navbar-toggleable-xl::after{content:"";display:table;clear:both}.navbar-toggleable-xl .navbar-brand{display:block;float:none;margin-top:.5rem;margin-right:0}.navbar-toggleable-xl .navbar-nav{margin-top:.5rem;margin-bottom:.5rem}.navbar-toggleable-xl .navbar-nav .dropdown-menu{position:static;float:none}.navbar-nav .nav-item{float:left}.navbar-nav .nav-link{display:block;padding-top:.425rem;padding-bottom:.425rem}.navbar-nav .nav-link+.nav-link{margin-left:1rem}.navbar-nav .nav-item+.nav-item{margin-left:1rem}.navbar-light .navbar-brand,.navbar-light .navbar-toggler{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover,.navbar-light .navbar-toggler:focus,.navbar-light .navbar-toggler:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .active>.nav-link:focus,.navbar-light .navbar-nav .active>.nav-link:hover,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.active:focus,.navbar-light .navbar-nav .nav-link.active:hover,.navbar-light .navbar-nav .nav-link.open,.navbar-light .navbar-nav .nav-link.open:focus,.navbar-light .navbar-nav .nav-link.open:hover,.navbar-light .navbar-nav .open>.nav-link,.navbar-light .navbar-nav .open>.nav-link:focus,.navbar-light .navbar-nav .open>.nav-link:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E");border-color:rgba(0,0,0,.1)}.navbar-light .navbar-divider{background-color:rgba(0,0,0,.075)}.navbar-dark .navbar-brand,.navbar-dark .navbar-toggler{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-toggler:focus,.navbar-dark .navbar-toggler:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .active>.nav-link:focus,.navbar-dark .navbar-nav .active>.nav-link:hover,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.active:focus,.navbar-dark .navbar-nav .nav-link.active:hover,.navbar-dark .navbar-nav .nav-link.open,.navbar-dark .navbar-nav .nav-link.open:focus,.navbar-dark .navbar-nav .nav-link.open:hover,.navbar-dark .navbar-nav .open>.nav-link,.navbar-dark .navbar-nav .open>.nav-link:focus,.navbar-dark .navbar-nav .open>.nav-link:hover{color:#fff}.navbar-dark .navbar-toggler{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E");border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-divider{background-color:rgba(255,255,255,.075)}.navbar-toggleable-xs::after{content:"";display:table;clear:both}@media (max-width:575px){.navbar-toggleable-xs .navbar-nav .nav-item{float:none;margin-left:0}}@media (min-width:576px){.navbar-toggleable-xs{display:block!important}}.navbar-toggleable-sm::after{content:"";display:table;clear:both}@media (max-width:767px){.navbar-toggleable-sm .navbar-nav .nav-item{float:none;margin-left:0}}@media (min-width:768px){.navbar-toggleable-sm{display:block!important}}.navbar-toggleable-md::after{content:"";display:table;clear:both}@media (max-width:991px){.navbar-toggleable-md .navbar-nav .nav-item{float:none;margin-left:0}}@media (min-width:992px){.navbar-toggleable-md{display:block!important}}.card{position:relative;display:block;margin-bottom:.75rem;background-color:#fff;border-radius:.25rem;border:1px solid rgba(0,0,0,.125)}.card-block{padding:1.25rem}.card-block::after{content:"";display:table;clear:both}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card>.list-group:first-child .list-group-item:first-child{border-top-right-radius:.25rem;border-top-left-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:#f5f5f5;border-bottom:1px solid rgba(0,0,0,.125)}.card-header::after{content:"";display:table;clear:both}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:#f5f5f5;border-top:1px solid rgba(0,0,0,.125)}.card-footer::after{content:"";display:table;clear:both}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-primary{background-color:#0275d8;border-color:#0275d8}.card-primary .card-footer,.card-primary .card-header{background-color:transparent}.card-success{background-color:#5cb85c;border-color:#5cb85c}.card-success .card-footer,.card-success .card-header{background-color:transparent}.card-info{background-color:#5bc0de;border-color:#5bc0de}.card-info .card-footer,.card-info .card-header{background-color:transparent}.card-warning{background-color:#f0ad4e;border-color:#f0ad4e}.card-warning .card-footer,.card-warning .card-header{background-color:transparent}.card-danger{background-color:#d9534f;border-color:#d9534f}.card-danger .card-footer,.card-danger .card-header{background-color:transparent}.card-outline-primary{background-color:transparent;border-color:#0275d8}.card-outline-secondary{background-color:transparent;border-color:#ccc}.card-outline-info{background-color:transparent;border-color:#5bc0de}.card-outline-success{background-color:transparent;border-color:#5cb85c}.card-outline-warning{background-color:transparent;border-color:#f0ad4e}.card-outline-danger{background-color:transparent;border-color:#d9534f}.card-inverse .card-footer,.card-inverse .card-header{border-color:rgba(255,255,255,.2)}.card-inverse .card-blockquote,.card-inverse .card-footer,.card-inverse .card-header,.card-inverse .card-title{color:#fff}.card-inverse .card-blockquote .blockquote-footer,.card-inverse .card-link,.card-inverse .card-subtitle,.card-inverse .card-text{color:rgba(255,255,255,.65)}.card-inverse .card-link:focus,.card-inverse .card-link:hover{color:#fff}.card-blockquote{padding:0;margin-bottom:0;border-left:0}.card-img{border-radius:calc(.25rem - 1px)}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img-top{border-top-right-radius:calc(.25rem - 1px);border-top-left-radius:calc(.25rem - 1px)}.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}@media (min-width:576px){.card-deck{display:table;width:100%;margin-bottom:.75rem;table-layout:fixed;border-spacing:1.25rem 0}.card-deck .card{display:table-cell;margin-bottom:0;vertical-align:top}.card-deck-wrapper{margin-right:-1.25rem;margin-left:-1.25rem}}@media (min-width:576px){.card-group{display:table;width:100%;table-layout:fixed}.card-group .card{display:table-cell;vertical-align:top}.card-group .card+.card{margin-left:0;border-left:0}.card-group .card:first-child{border-bottom-right-radius:0;border-top-right-radius:0}.card-group .card:first-child .card-img-top{border-top-right-radius:0}.card-group .card:first-child .card-img-bottom{border-bottom-right-radius:0}.card-group .card:last-child{border-bottom-left-radius:0;border-top-left-radius:0}.card-group .card:last-child .card-img-top{border-top-left-radius:0}.card-group .card:last-child .card-img-bottom{border-bottom-left-radius:0}.card-group .card:not(:first-child):not(:last-child){border-radius:0}.card-group .card:not(:first-child):not(:last-child) .card-img-bottom,.card-group .card:not(:first-child):not(:last-child) .card-img-top{border-radius:0}}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem}.card-columns .card{display:inline-block;width:100%}}.breadcrumb{padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#eceeef;border-radius:.25rem}.breadcrumb::after{content:"";display:table;clear:both}.breadcrumb-item{float:left}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;padding-left:.5rem;color:#818a91;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#818a91}.pagination{display:inline-block;padding-left:0;margin-top:1rem;margin-bottom:1rem;border-radius:.25rem}.page-item{display:inline}.page-item:first-child .page-link{margin-left:0;border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.page-item:last-child .page-link{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.page-item.active .page-link,.page-item.active .page-link:focus,.page-item.active .page-link:hover{z-index:2;color:#fff;cursor:default;background-color:#0275d8;border-color:#0275d8}.page-item.disabled .page-link,.page-item.disabled .page-link:focus,.page-item.disabled .page-link:hover{color:#818a91;pointer-events:none;cursor:not-allowed;background-color:#fff;border-color:#ddd}.page-link{position:relative;float:left;padding:.5rem .75rem;margin-left:-1px;color:#0275d8;text-decoration:none;background-color:#fff;border:1px solid #ddd}.page-link:focus,.page-link:hover{color:#014c8c;background-color:#eceeef;border-color:#ddd}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-bottom-left-radius:.3rem;border-top-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-bottom-right-radius:.3rem;border-top-right-radius:.3rem}.pagination-sm .page-link{padding:.275rem .75rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-bottom-left-radius:.2rem;border-top-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-bottom-right-radius:.2rem;border-top-right-radius:.2rem}.tag{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.tag:empty{display:none}.btn .tag{position:relative;top:-1px}a.tag:focus,a.tag:hover{color:#fff;text-decoration:none;cursor:pointer}.tag-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.tag-default{background-color:#818a91}.tag-default[href]:focus,.tag-default[href]:hover{background-color:#687077}.tag-primary{background-color:#0275d8}.tag-primary[href]:focus,.tag-primary[href]:hover{background-color:#025aa5}.tag-success{background-color:#5cb85c}.tag-success[href]:focus,.tag-success[href]:hover{background-color:#449d44}.tag-info{background-color:#5bc0de}.tag-info[href]:focus,.tag-info[href]:hover{background-color:#31b0d5}.tag-warning{background-color:#f0ad4e}.tag-warning[href]:focus,.tag-warning[href]:hover{background-color:#ec971f}.tag-danger{background-color:#d9534f}.tag-danger[href]:focus,.tag-danger[href]:hover{background-color:#c9302c}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#eceeef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-hr{border-top-color:#d0d5d8}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:2.5rem}.alert-dismissible .close{position:relative;top:-.125rem;right:-1.25rem;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d0e9c6;color:#3c763d}.alert-success hr{border-top-color:#c1e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bcdff1;color:#31708f}.alert-info hr{border-top-color:#a6d5ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faf2cc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7ecb5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebcccc;color:#a94442}.alert-danger hr{border-top-color:#e4b9b9}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:block;width:100%;height:1rem;margin-bottom:1rem}.progress[value]{background-color:#eee;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem}.progress[value]::-ms-fill{background-color:#0074d9;border:0}.progress[value]::-moz-progress-bar{background-color:#0074d9;border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.progress[value]::-webkit-progress-value{background-color:#0074d9;border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.progress[value="100"]::-moz-progress-bar{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.progress[value="100"]::-webkit-progress-value{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.progress[value]::-webkit-progress-bar{background-color:#eee;border-radius:.25rem}.progress[value],base::-moz-progress-bar{background-color:#eee;border-radius:.25rem}@media screen and (min-width:0\0){.progress{background-color:#eee;border-radius:.25rem}.progress-bar{display:inline-block;height:1rem;text-indent:-999rem;background-color:#0074d9;border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.progress[width="100%"]{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}}.progress-striped[value]::-webkit-progress-value{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:1rem 1rem;background-size:1rem 1rem}.progress-striped[value]::-moz-progress-bar{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-striped[value]::-ms-fill{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}@media screen and (min-width:0\0){.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:1rem 1rem;background-size:1rem 1rem}}.progress-animated[value]::-webkit-progress-value{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-animated[value]::-moz-progress-bar{animation:progress-bar-stripes 2s linear infinite}@media screen and (min-width:0\0){.progress-animated .progress-bar-striped{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}}.progress-success[value]::-webkit-progress-value{background-color:#5cb85c}.progress-success[value]::-moz-progress-bar{background-color:#5cb85c}.progress-success[value]::-ms-fill{background-color:#5cb85c}@media screen and (min-width:0\0){.progress-success .progress-bar{background-color:#5cb85c}}.progress-info[value]::-webkit-progress-value{background-color:#5bc0de}.progress-info[value]::-moz-progress-bar{background-color:#5bc0de}.progress-info[value]::-ms-fill{background-color:#5bc0de}@media screen and (min-width:0\0){.progress-info .progress-bar{background-color:#5bc0de}}.progress-warning[value]::-webkit-progress-value{background-color:#f0ad4e}.progress-warning[value]::-moz-progress-bar{background-color:#f0ad4e}.progress-warning[value]::-ms-fill{background-color:#f0ad4e}@media screen and (min-width:0\0){.progress-warning .progress-bar{background-color:#f0ad4e}}.progress-danger[value]::-webkit-progress-value{background-color:#d9534f}.progress-danger[value]::-moz-progress-bar{background-color:#d9534f}.progress-danger[value]::-ms-fill{background-color:#d9534f}@media screen and (min-width:0\0){.progress-danger .progress-bar{background-color:#d9534f}}.media,.media-body{overflow:hidden}.media-body{width:10000px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right{padding-left:10px}.media-left{padding-right:10px}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:0}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:.25rem;border-top-left-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#818a91;cursor:not-allowed;background-color:#eceeef}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#818a91}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;text-decoration:none;background-color:#0275d8;border-color:#0275d8}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#a8d6fe}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-item-action{width:100%;color:#555;text-align:inherit}.list-group-item-action .list-group-item-heading{color:#333}.list-group-item-action:focus,.list-group-item-action:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9{padding-bottom:42.857143%}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.embed-responsive-1by1{padding-bottom:100%}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.5}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out,-o-transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.in{opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header::after{content:"";display:table;clear:both}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.5}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer::after{content:"";display:table;clear:both}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:600px;margin:30px auto}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg{max-width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.in{opacity:.9}.tooltip.bs-tether-element-attached-bottom,.tooltip.tooltip-top{padding:5px 0;margin-top:-3px}.tooltip.bs-tether-element-attached-bottom .tooltip-inner::before,.tooltip.tooltip-top .tooltip-inner::before{bottom:0;left:50%;margin-left:-5px;content:"";border-width:5px 5px 0;border-top-color:#000}.tooltip.bs-tether-element-attached-left,.tooltip.tooltip-right{padding:0 5px;margin-left:3px}.tooltip.bs-tether-element-attached-left .tooltip-inner::before,.tooltip.tooltip-right .tooltip-inner::before{top:50%;left:0;margin-top:-5px;content:"";border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.bs-tether-element-attached-top,.tooltip.tooltip-bottom{padding:5px 0;margin-top:3px}.tooltip.bs-tether-element-attached-top .tooltip-inner::before,.tooltip.tooltip-bottom .tooltip-inner::before{top:0;left:50%;margin-left:-5px;content:"";border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bs-tether-element-attached-right,.tooltip.tooltip-left{padding:0 5px;margin-left:-3px}.tooltip.bs-tether-element-attached-right .tooltip-inner::before,.tooltip.tooltip-left .tooltip-inner::before{top:50%;right:0;margin-top:-5px;content:"";border-width:5px 0 5px 5px;border-left-color:#000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.tooltip-inner::before{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;padding:1px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;font-size:.875rem;word-wrap:break-word;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover.bs-tether-element-attached-bottom,.popover.popover-top{margin-top:-10px}.popover.bs-tether-element-attached-bottom::after,.popover.bs-tether-element-attached-bottom::before,.popover.popover-top::after,.popover.popover-top::before{left:50%;border-bottom-width:0}.popover.bs-tether-element-attached-bottom::before,.popover.popover-top::before{bottom:-11px;margin-left:-11px;border-top-color:rgba(0,0,0,.25)}.popover.bs-tether-element-attached-bottom::after,.popover.popover-top::after{bottom:-10px;margin-left:-10px;border-top-color:#fff}.popover.bs-tether-element-attached-left,.popover.popover-right{margin-left:10px}.popover.bs-tether-element-attached-left::after,.popover.bs-tether-element-attached-left::before,.popover.popover-right::after,.popover.popover-right::before{top:50%;border-left-width:0}.popover.bs-tether-element-attached-left::before,.popover.popover-right::before{left:-11px;margin-top:-11px;border-right-color:rgba(0,0,0,.25)}.popover.bs-tether-element-attached-left::after,.popover.popover-right::after{left:-10px;margin-top:-10px;border-right-color:#fff}.popover.bs-tether-element-attached-top,.popover.popover-bottom{margin-top:10px}.popover.bs-tether-element-attached-top::after,.popover.bs-tether-element-attached-top::before,.popover.popover-bottom::after,.popover.popover-bottom::before{left:50%;border-top-width:0}.popover.bs-tether-element-attached-top::before,.popover.popover-bottom::before{top:-11px;margin-left:-11px;border-bottom-color:rgba(0,0,0,.25)}.popover.bs-tether-element-attached-top::after,.popover.popover-bottom::after{top:-10px;margin-left:-10px;border-bottom-color:#f7f7f7}.popover.bs-tether-element-attached-top .popover-title::before,.popover.popover-bottom .popover-title::before{position:absolute;top:0;left:50%;display:block;width:20px;margin-left:-10px;content:"";border-bottom:1px solid #f7f7f7}.popover.bs-tether-element-attached-right,.popover.popover-left{margin-left:-10px}.popover.bs-tether-element-attached-right::after,.popover.bs-tether-element-attached-right::before,.popover.popover-left::after,.popover.popover-left::before{top:50%;border-right-width:0}.popover.bs-tether-element-attached-right::before,.popover.popover-left::before{right:-11px;margin-top:-11px;border-left-color:rgba(0,0,0,.25)}.popover.bs-tether-element-attached-right::after,.popover.popover-left::after{right:-10px;margin-top:-10px;border-left-color:#fff}.popover-title{padding:8px 14px;margin:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:.2375rem .2375rem 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover::after,.popover::before{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover::before{content:"";border-width:11px}.popover::after{content:"";border-width:10px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.carousel-item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.carousel-item>a>img,.carousel-inner>.carousel-item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.carousel-item{-webkit-transition:-webkit-transform .6s ease-in-out;transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out,-o-transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.carousel-item.active.right,.carousel-inner>.carousel-item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.carousel-item.active.left,.carousel-inner>.carousel-item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.carousel-item.active,.carousel-inner>.carousel-item.next.left,.carousel-inner>.carousel-item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);opacity:.5}.carousel-control.left{background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0%,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0%,rgba(0,0,0,.0001) 100%);background-image:linear-gradient(to right,rgba(0,0,0,.5) 0%,rgba(0,0,0,.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{right:0;left:auto;background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0%,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0%,rgba(0,0,0,.5) 100%);background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0%,rgba(0,0,0,.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-prev::before{content:"\2039"}.carousel-control .icon-next::before{content:"\203a"}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:transparent;border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media (min-width:576px){.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .icon-prev{margin-left:-15px}.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-faded{background-color:#f7f7f9}.bg-primary{background-color:#0275d8!important}a.bg-primary:focus,a.bg-primary:hover{background-color:#025aa5!important}.bg-success{background-color:#5cb85c!important}a.bg-success:focus,a.bg-success:hover{background-color:#449d44!important}.bg-info{background-color:#5bc0de!important}a.bg-info:focus,a.bg-info:hover{background-color:#31b0d5!important}.bg-warning{background-color:#f0ad4e!important}a.bg-warning:focus,a.bg-warning:hover{background-color:#ec971f!important}.bg-danger{background-color:#d9534f!important}a.bg-danger:focus,a.bg-danger:hover{background-color:#c9302c!important}.bg-inverse{background-color:#373a3c!important}a.bg-inverse:focus,a.bg-inverse:hover{background-color:#1f2021!important}.rounded{border-radius:.25rem}.rounded-top{border-top-right-radius:.25rem;border-top-left-radius:.25rem}.rounded-right{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.rounded-bottom{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-left{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.rounded-circle{border-radius:50%}.clearfix::after{content:"";display:table;clear:both}.d-block{display:block!important}.d-inline-block{display:inline-block!important}.d-inline{display:inline!important}.float-xs-left{float:left!important}.float-xs-right{float:right!important}.float-xs-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.w-100{width:100%!important}.h-100{height:100%!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.m-0{margin:0 0!important}.mt-0{margin-top:0!important}.mr-0{margin-right:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mx-0{margin-right:0!important;margin-left:0!important}.my-0{margin-top:0!important;margin-bottom:0!important}.m-1{margin:1rem 1rem!important}.mt-1{margin-top:1rem!important}.mr-1{margin-right:1rem!important}.mb-1{margin-bottom:1rem!important}.ml-1{margin-left:1rem!important}.mx-1{margin-right:1rem!important;margin-left:1rem!important}.my-1{margin-top:1rem!important;margin-bottom:1rem!important}.m-2{margin:1.5rem 1.5rem!important}.mt-2{margin-top:1.5rem!important}.mr-2{margin-right:1.5rem!important}.mb-2{margin-bottom:1.5rem!important}.ml-2{margin-left:1.5rem!important}.mx-2{margin-right:1.5rem!important;margin-left:1.5rem!important}.my-2{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.m-3{margin:3rem 3rem!important}.mt-3{margin-top:3rem!important}.mr-3{margin-right:3rem!important}.mb-3{margin-bottom:3rem!important}.ml-3{margin-left:3rem!important}.mx-3{margin-right:3rem!important;margin-left:3rem!important}.my-3{margin-top:3rem!important;margin-bottom:3rem!important}.p-0{padding:0 0!important}.pt-0{padding-top:0!important}.pr-0{padding-right:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.px-0{padding-right:0!important;padding-left:0!important}.py-0{padding-top:0!important;padding-bottom:0!important}.p-1{padding:1rem 1rem!important}.pt-1{padding-top:1rem!important}.pr-1{padding-right:1rem!important}.pb-1{padding-bottom:1rem!important}.pl-1{padding-left:1rem!important}.px-1{padding-right:1rem!important;padding-left:1rem!important}.py-1{padding-top:1rem!important;padding-bottom:1rem!important}.p-2{padding:1.5rem 1.5rem!important}.pt-2{padding-top:1.5rem!important}.pr-2{padding-right:1.5rem!important}.pb-2{padding-bottom:1.5rem!important}.pl-2{padding-left:1.5rem!important}.px-2{padding-right:1.5rem!important;padding-left:1.5rem!important}.py-2{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.p-3{padding:3rem 3rem!important}.pt-3{padding-top:3rem!important}.pr-3{padding-right:3rem!important}.pb-3{padding-bottom:3rem!important}.pl-3{padding-left:3rem!important}.px-3{padding-right:3rem!important;padding-left:3rem!important}.py-3{padding-top:3rem!important;padding-bottom:3rem!important}.pos-f-t{position:fixed;top:0;right:0;left:0;z-index:1030}.text-justify{text-align:justify!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-xs-left{text-align:left!important}.text-xs-right{text-align:right!important}.text-xs-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-normal{font-weight:400}.font-weight-bold{font-weight:700}.font-italic{font-style:italic}.text-white{color:#fff!important}.text-muted{color:#818a91!important}a.text-muted:focus,a.text-muted:hover{color:#687077!important}.text-primary{color:#0275d8!important}a.text-primary:focus,a.text-primary:hover{color:#025aa5!important}.text-success{color:#5cb85c!important}a.text-success:focus,a.text-success:hover{color:#449d44!important}.text-info{color:#5bc0de!important}a.text-info:focus,a.text-info:hover{color:#31b0d5!important}.text-warning{color:#f0ad4e!important}a.text-warning:focus,a.text-warning:hover{color:#ec971f!important}.text-danger{color:#d9534f!important}a.text-danger:focus,a.text-danger:hover{color:#c9302c!important}.text-gray-dark{color:#373a3c!important}a.text-gray-dark:focus,a.text-gray-dark:hover{color:#1f2021!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.invisible{visibility:hidden!important}.hidden-xs-up{display:none!important}@media (max-width:575px){.hidden-xs-down{display:none!important}}@media (min-width:576px){.hidden-sm-up{display:none!important}}@media (max-width:767px){.hidden-sm-down{display:none!important}}@media (min-width:768px){.hidden-md-up{display:none!important}}@media (max-width:991px){.hidden-md-down{display:none!important}}@media (min-width:992px){.hidden-lg-up{display:none!important}}@media (max-width:1199px){.hidden-lg-down{display:none!important}}@media (min-width:1200px){.hidden-xl-up{display:none!important}}.hidden-xl-down{display:none!important}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src/public/default/css/starter-template.css b/src/public/default/css/starter-template.css new file mode 100644 index 0000000..8a60244 --- /dev/null +++ b/src/public/default/css/starter-template.css @@ -0,0 +1,7 @@ +body { + padding-top: 5rem; +} +.starter-template { + padding: 3rem 1.5rem; + text-align: center; +} \ No newline at end of file diff --git a/src/public/default/img/bootstrap.ico b/src/public/default/img/bootstrap.ico new file mode 100644 index 0000000000000000000000000000000000000000..b48b3442ad90fd9b5bcbef8a9f703c1852de1561 GIT binary patch literal 5430 zcmchb`BM}}6vrn=RerU~e}JE;{1#6HHI^o#f*c}>As#Wtm_$)Ph+G1TXayBe6BCaj zBvFZYti~%*t5QJ(qGiBR5RfGTyDo=F)D_J}U0&Yn+3uO1JtUi$srpv;&d&7rGu>}y z-Wx@ENqJcb3R18QQYuF%O0c3RBe134AHS+74cLDj_u(PRR21!J8}2u50HBb1J8i-H z^GnJy-(Fx}HF~CN<)|6J^~tDkV0r`F0EJ_@GTutC@5?VP>+P@wb6;w&{Zbd38W!W= ze*Rj)LQDa&q=ZI0uKrvT%db0iVypwN8I1DBq5m%sCSss@IAAI*OG0mw(cjx|y|2Ii zaF}QV#&}i&4y?7>yht*>uTqszL3?#w9`!A4Z<>(o%HDl46i*XPO*DN?& zb`b9M^*>*|{m|8FfeVLE!M5e;>^k)8=WDIjjEw|M5!hG7nJyeUiDCNBwxOXRXuNnC zGN#S<8B?4mdE9JB9v2D8$U)Lr)P8jU{+za>sTGn#qWs2o3iBrhy)+)9{~fCxj&9xU z)-G&!!HFGTLyh?iIR3cLtwmc=xLu7YA90j2J_=IC&sK#D#G_}bK6FZqMIB51b;BHK zYnC^A2{PQ1j&H2K>^HVk(O)l>M`QBlFV@Vbk$#>yQ|#@a7WaH<^Ca#$WJ(K(L4T6Q!TZE>@$hMBJPY}= zHPhyZui3gO@i5TcD;-a9;XSrhp0o)uAW873Oh$iOYddUTmabD8?EEMb4i@eJEB>e7 zT+g1t*2Qal#x`-KPly$gINV${hU?dl@2~H2x!_poUca$TYxZgA9H#V8SHqH^P+ore`&~wWw7>02EzsdYeFz}e>EoaI=f0E$P z_mRh&zn)LOX2**62!HCd)YW^$w()+;m^c@N!m!VzF?sqod7UR>I=cBQ$^VYcF3pv< z_|8Osy#xz!AxrJiAEhzd8_?e{N3?E*(aXTzc1t>z)`Q29w>2Z4tV!|cZ?+Yr}0hH@s)ccGAh%fLfy1}1r~v~}ZarnOoeQH&d78-Kfs z=B(%;e{TwmN#l85Y;THLZa=;>VAT__liOn98CMPQolSFYlORrV7lW##0>eLEH9wJJq<#YCS8{-=$+ zs?f6&z3DpzeU=D9iSHEn4a2zMGiBu7wFTzY<073@yA0=hMAFy@=N>$Votw77Eb1}c zw+&tg`5TkQNC}B@vf9*L8;h~rb-R8X-sd$qaj?2hmxH%mxdwj@K9ckh-z`+P6}`KP z)opwnYFU1*tI;~KwxW%4pBuGTptVZ_u;qTt=VdsCFRV4C$s;9-fAMBM=kVy!~kovz!EF=d`RZ&qtT_5G>kr(HMHW9fZ7(A|gi`CXu#m#%$FVv>}5QhmUm z_dLh5y3en~yq)I;2Xc#<|L>S%sCkmRCc!#{J-c=J5kLP>;pZYB+LyhVoj-X0ff$ot zcb1f6%mse_gZ=^N>#)Iqy%(rnppEi2DPO=mTz*H#!&S^Jy4yP?*_tgZU)m74RL&_w z;~;NpqEme~xt~cM?=~b;aDKnO#LS+}hG(*IOzqw1eh=YwAIRQneste4 nvNEsKToAs=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(jQuery),+function(){function a(a,b){if(!a)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!b||"object"!=typeof b&&"function"!=typeof b?a:b}function b(a,b){if("function"!=typeof b&&null!==b)throw new TypeError("Super expression must either be null or a function, not "+typeof b);a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}}),b&&(Object.setPrototypeOf?Object.setPrototypeOf(a,b):a.__proto__=b)}function c(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}var d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},e=function(){function a(a,b){for(var c=0;cthis._items.length-1||b<0)){if(this._isSliding)return void a(this._element).one(r.SLID,function(){return c.to(b)});if(d===b)return this.pause(),void this.cycle();var e=b>d?q.NEXT:q.PREVIOUS;this._slide(e,this._items[b])}},j.prototype.dispose=function(){a(this._element).off(i),a.removeData(this._element,h),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},j.prototype._getConfig=function(c){return c=a.extend({},o,c),f.typeCheckConfig(b,c,p),c},j.prototype._addEventListeners=function(){this._config.keyboard&&a(this._element).on(r.KEYDOWN,a.proxy(this._keydown,this)),"hover"!==this._config.pause||"ontouchstart"in document.documentElement||a(this._element).on(r.MOUSEENTER,a.proxy(this.pause,this)).on(r.MOUSELEAVE,a.proxy(this.cycle,this))},j.prototype._keydown=function(a){if(a.preventDefault(),!/input|textarea/i.test(a.target.tagName))switch(a.which){case m:this.prev();break;case n:this.next();break;default:return}},j.prototype._getItemIndex=function(b){return this._items=a.makeArray(a(b).parent().find(t.ITEM)),this._items.indexOf(b)},j.prototype._getItemByDirection=function(a,b){var c=a===q.NEXT,d=a===q.PREVIOUS,e=this._getItemIndex(b),f=this._items.length-1,g=d&&0===e||c&&e===f;if(g&&!this._config.wrap)return b;var h=a===q.PREVIOUS?-1:1,i=(e+h)%this._items.length;return i===-1?this._items[this._items.length-1]:this._items[i]},j.prototype._triggerSlideEvent=function(b,c){var d=a.Event(r.SLIDE,{relatedTarget:b,direction:c});return a(this._element).trigger(d),d},j.prototype._setActiveIndicatorElement=function(b){if(this._indicatorsElement){a(this._indicatorsElement).find(t.ACTIVE).removeClass(s.ACTIVE);var c=this._indicatorsElement.children[this._getItemIndex(b)];c&&a(c).addClass(s.ACTIVE)}},j.prototype._slide=function(b,c){var d=this,e=a(this._element).find(t.ACTIVE_ITEM)[0],g=c||e&&this._getItemByDirection(b,e),h=Boolean(this._interval),i=b===q.NEXT?s.LEFT:s.RIGHT;if(g&&a(g).hasClass(s.ACTIVE))return void(this._isSliding=!1);var j=this._triggerSlideEvent(g,i);if(!j.isDefaultPrevented()&&e&&g){this._isSliding=!0,h&&this.pause(),this._setActiveIndicatorElement(g);var k=a.Event(r.SLID,{relatedTarget:g,direction:i});f.supportsTransitionEnd()&&a(this._element).hasClass(s.SLIDE)?(a(g).addClass(b),f.reflow(g),a(e).addClass(i),a(g).addClass(i),a(e).one(f.TRANSITION_END,function(){a(g).removeClass(i).removeClass(b),a(g).addClass(s.ACTIVE),a(e).removeClass(s.ACTIVE).removeClass(b).removeClass(i),d._isSliding=!1,setTimeout(function(){return a(d._element).trigger(k)},0)}).emulateTransitionEnd(l)):(a(e).removeClass(s.ACTIVE),a(g).addClass(s.ACTIVE),this._isSliding=!1,a(this._element).trigger(k)),h&&this.cycle()}},j._jQueryInterface=function(b){return this.each(function(){var c=a(this).data(h),e=a.extend({},o,a(this).data());"object"===("undefined"==typeof b?"undefined":d(b))&&a.extend(e,b);var f="string"==typeof b?b:e.slide;if(c||(c=new j(this,e),a(this).data(h,c)),"number"==typeof b)c.to(b);else if("string"==typeof f){if(void 0===c[f])throw new Error('No method named "'+f+'"');c[f]()}else e.interval&&(c.pause(),c.cycle())})},j._dataApiClickHandler=function(b){var c=f.getSelectorFromElement(this);if(c){var d=a(c)[0];if(d&&a(d).hasClass(s.CAROUSEL)){var e=a.extend({},a(d).data(),a(this).data()),g=this.getAttribute("data-slide-to");g&&(e.interval=!1),j._jQueryInterface.call(a(d),e),g&&a(d).data(h).to(g),b.preventDefault()}}},e(j,null,[{key:"VERSION",get:function(){return g}},{key:"Default",get:function(){return o}}]),j}();return a(document).on(r.CLICK_DATA_API,t.DATA_SLIDE,u._dataApiClickHandler),a(window).on(r.LOAD_DATA_API,function(){a(t.DATA_RIDE).each(function(){var b=a(this);u._jQueryInterface.call(b,b.data())})}),a.fn[b]=u._jQueryInterface,a.fn[b].Constructor=u,a.fn[b].noConflict=function(){return a.fn[b]=k,u._jQueryInterface},u}(jQuery),function(a){var b="collapse",g="4.0.0-alpha.5",h="bs.collapse",i="."+h,j=".data-api",k=a.fn[b],l=600,m={toggle:!0,parent:""},n={toggle:"boolean",parent:"string"},o={SHOW:"show"+i,SHOWN:"shown"+i,HIDE:"hide"+i,HIDDEN:"hidden"+i,CLICK_DATA_API:"click"+i+j},p={IN:"in",COLLAPSE:"collapse",COLLAPSING:"collapsing",COLLAPSED:"collapsed"},q={WIDTH:"width",HEIGHT:"height"},r={ACTIVES:".card > .in, .card > .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},s=function(){function i(b,d){c(this,i),this._isTransitioning=!1,this._element=b,this._config=this._getConfig(d),this._triggerArray=a.makeArray(a('[data-toggle="collapse"][href="#'+b.id+'"],'+('[data-toggle="collapse"][data-target="#'+b.id+'"]'))),this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}return i.prototype.toggle=function(){a(this._element).hasClass(p.IN)?this.hide():this.show()},i.prototype.show=function(){var b=this;if(!this._isTransitioning&&!a(this._element).hasClass(p.IN)){var c=void 0,d=void 0;if(this._parent&&(c=a.makeArray(a(r.ACTIVES)),c.length||(c=null)),!(c&&(d=a(c).data(h),d&&d._isTransitioning))){var e=a.Event(o.SHOW);if(a(this._element).trigger(e),!e.isDefaultPrevented()){c&&(i._jQueryInterface.call(a(c),"hide"),d||a(c).data(h,null));var g=this._getDimension();a(this._element).removeClass(p.COLLAPSE).addClass(p.COLLAPSING),this._element.style[g]=0,this._element.setAttribute("aria-expanded",!0),this._triggerArray.length&&a(this._triggerArray).removeClass(p.COLLAPSED).attr("aria-expanded",!0),this.setTransitioning(!0);var j=function(){a(b._element).removeClass(p.COLLAPSING).addClass(p.COLLAPSE).addClass(p.IN),b._element.style[g]="",b.setTransitioning(!1),a(b._element).trigger(o.SHOWN)};if(!f.supportsTransitionEnd())return void j();var k=g[0].toUpperCase()+g.slice(1),m="scroll"+k;a(this._element).one(f.TRANSITION_END,j).emulateTransitionEnd(l),this._element.style[g]=this._element[m]+"px"}}}},i.prototype.hide=function(){var b=this;if(!this._isTransitioning&&a(this._element).hasClass(p.IN)){var c=a.Event(o.HIDE);if(a(this._element).trigger(c),!c.isDefaultPrevented()){var d=this._getDimension(),e=d===q.WIDTH?"offsetWidth":"offsetHeight";this._element.style[d]=this._element[e]+"px",f.reflow(this._element),a(this._element).addClass(p.COLLAPSING).removeClass(p.COLLAPSE).removeClass(p.IN),this._element.setAttribute("aria-expanded",!1),this._triggerArray.length&&a(this._triggerArray).addClass(p.COLLAPSED).attr("aria-expanded",!1),this.setTransitioning(!0);var g=function(){b.setTransitioning(!1),a(b._element).removeClass(p.COLLAPSING).addClass(p.COLLAPSE).trigger(o.HIDDEN)};return this._element.style[d]="",f.supportsTransitionEnd()?void a(this._element).one(f.TRANSITION_END,g).emulateTransitionEnd(l):void g()}}},i.prototype.setTransitioning=function(a){this._isTransitioning=a},i.prototype.dispose=function(){a.removeData(this._element,h),this._config=null,this._parent=null,this._element=null,this._triggerArray=null,this._isTransitioning=null},i.prototype._getConfig=function(c){return c=a.extend({},m,c),c.toggle=Boolean(c.toggle),f.typeCheckConfig(b,c,n),c},i.prototype._getDimension=function(){var b=a(this._element).hasClass(q.WIDTH);return b?q.WIDTH:q.HEIGHT},i.prototype._getParent=function(){var b=this,c=a(this._config.parent)[0],d='[data-toggle="collapse"][data-parent="'+this._config.parent+'"]';return a(c).find(d).each(function(a,c){b._addAriaAndCollapsedClass(i._getTargetFromElement(c),[c])}),c},i.prototype._addAriaAndCollapsedClass=function(b,c){if(b){var d=a(b).hasClass(p.IN);b.setAttribute("aria-expanded",d),c.length&&a(c).toggleClass(p.COLLAPSED,!d).attr("aria-expanded",d)}},i._getTargetFromElement=function(b){var c=f.getSelectorFromElement(b);return c?a(c)[0]:null},i._jQueryInterface=function(b){return this.each(function(){var c=a(this),e=c.data(h),f=a.extend({},m,c.data(),"object"===("undefined"==typeof b?"undefined":d(b))&&b);if(!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||(e=new i(this,f),c.data(h,e)),"string"==typeof b){if(void 0===e[b])throw new Error('No method named "'+b+'"');e[b]()}})},e(i,null,[{key:"VERSION",get:function(){return g}},{key:"Default",get:function(){return m}}]),i}();return a(document).on(o.CLICK_DATA_API,r.DATA_TOGGLE,function(b){b.preventDefault();var c=s._getTargetFromElement(this),d=a(c).data(h),e=d?"toggle":a(this).data();s._jQueryInterface.call(a(c),e)}),a.fn[b]=s._jQueryInterface,a.fn[b].Constructor=s,a.fn[b].noConflict=function(){return a.fn[b]=k,s._jQueryInterface},s}(jQuery),function(a){var b="dropdown",d="4.0.0-alpha.5",g="bs.dropdown",h="."+g,i=".data-api",j=a.fn[b],k=27,l=38,m=40,n=3,o={HIDE:"hide"+h,HIDDEN:"hidden"+h,SHOW:"show"+h,SHOWN:"shown"+h,CLICK:"click"+h,CLICK_DATA_API:"click"+h+i,KEYDOWN_DATA_API:"keydown"+h+i},p={BACKDROP:"dropdown-backdrop",DISABLED:"disabled",OPEN:"open"},q={BACKDROP:".dropdown-backdrop",DATA_TOGGLE:'[data-toggle="dropdown"]',FORM_CHILD:".dropdown form",ROLE_MENU:'[role="menu"]',ROLE_LISTBOX:'[role="listbox"]',NAVBAR_NAV:".navbar-nav",VISIBLE_ITEMS:'[role="menu"] li:not(.disabled) a, [role="listbox"] li:not(.disabled) a'},r=function(){function b(a){c(this,b),this._element=a,this._addEventListeners()}return b.prototype.toggle=function(){if(this.disabled||a(this).hasClass(p.DISABLED))return!1;var c=b._getParentFromElement(this),d=a(c).hasClass(p.OPEN);if(b._clearMenus(),d)return!1;if("ontouchstart"in document.documentElement&&!a(c).closest(q.NAVBAR_NAV).length){var e=document.createElement("div");e.className=p.BACKDROP,a(e).insertBefore(this),a(e).on("click",b._clearMenus)}var f={relatedTarget:this},g=a.Event(o.SHOW,f);return a(c).trigger(g),!g.isDefaultPrevented()&&(this.focus(),this.setAttribute("aria-expanded","true"),a(c).toggleClass(p.OPEN),a(c).trigger(a.Event(o.SHOWN,f)),!1)},b.prototype.dispose=function(){a.removeData(this._element,g),a(this._element).off(h),this._element=null},b.prototype._addEventListeners=function(){a(this._element).on(o.CLICK,this.toggle)},b._jQueryInterface=function(c){return this.each(function(){var d=a(this).data(g);if(d||a(this).data(g,d=new b(this)),"string"==typeof c){if(void 0===d[c])throw new Error('No method named "'+c+'"');d[c].call(this)}})},b._clearMenus=function(c){if(!c||c.which!==n){var d=a(q.BACKDROP)[0];d&&d.parentNode.removeChild(d);for(var e=a.makeArray(a(q.DATA_TOGGLE)),f=0;f0&&h--,c.which===m&&hdocument.documentElement.clientHeight;!this._isBodyOverflowing&&a&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!a&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},j.prototype._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},j.prototype._checkScrollbar=function(){this._isBodyOverflowing=document.body.clientWidth=c){var d=this._targets[this._targets.length-1];this._activeTarget!==d&&this._activate(d)}if(this._activeTarget&&a=this._offsets[e]&&(void 0===this._offsets[e+1]||a .nav-item .fade, > .fade",ACTIVE:".active",ACTIVE_CHILD:"> .nav-item > .active, > .active",DATA_TOGGLE:'[data-toggle="tab"], [data-toggle="pill"]', +DROPDOWN_TOGGLE:".dropdown-toggle",DROPDOWN_ACTIVE_CHILD:"> .dropdown-menu .active"},o=function(){function b(a){c(this,b),this._element=a}return b.prototype.show=function(){var b=this;if(!this._element.parentNode||this._element.parentNode.nodeType!==Node.ELEMENT_NODE||!a(this._element).hasClass(m.ACTIVE)){var c=void 0,d=void 0,e=a(this._element).closest(n.UL)[0],g=f.getSelectorFromElement(this._element);e&&(d=a.makeArray(a(e).find(n.ACTIVE)),d=d[d.length-1]);var h=a.Event(l.HIDE,{relatedTarget:this._element}),i=a.Event(l.SHOW,{relatedTarget:d});if(d&&a(d).trigger(h),a(this._element).trigger(i),!i.isDefaultPrevented()&&!h.isDefaultPrevented()){g&&(c=a(g)[0]),this._activate(this._element,e);var j=function(){var c=a.Event(l.HIDDEN,{relatedTarget:b._element}),e=a.Event(l.SHOWN,{relatedTarget:d});a(d).trigger(c),a(b._element).trigger(e)};c?this._activate(c,c.parentNode,j):j()}}},b.prototype.dispose=function(){a.removeClass(this._element,g),this._element=null},b.prototype._activate=function(b,c,d){var e=a(c).find(n.ACTIVE_CHILD)[0],g=d&&f.supportsTransitionEnd()&&(e&&a(e).hasClass(m.FADE)||Boolean(a(c).find(n.FADE_CHILD)[0])),h=a.proxy(this._transitionComplete,this,b,e,g,d);e&&g?a(e).one(f.TRANSITION_END,h).emulateTransitionEnd(k):h(),e&&a(e).removeClass(m.IN)},b.prototype._transitionComplete=function(b,c,d,e){if(c){a(c).removeClass(m.ACTIVE);var g=a(c).find(n.DROPDOWN_ACTIVE_CHILD)[0];g&&a(g).removeClass(m.ACTIVE),c.setAttribute("aria-expanded",!1)}if(a(b).addClass(m.ACTIVE),b.setAttribute("aria-expanded",!0),d?(f.reflow(b),a(b).addClass(m.IN)):a(b).removeClass(m.FADE),b.parentNode&&a(b.parentNode).hasClass(m.DROPDOWN_MENU)){var h=a(b).closest(n.DROPDOWN)[0];h&&a(h).find(n.DROPDOWN_TOGGLE).addClass(m.ACTIVE),b.setAttribute("aria-expanded",!0)}e&&e()},b._jQueryInterface=function(c){return this.each(function(){var d=a(this),e=d.data(g);if(e||(e=e=new b(this),d.data(g,e)),"string"==typeof c){if(void 0===e[c])throw new Error('No method named "'+c+'"');e[c]()}})},e(b,null,[{key:"VERSION",get:function(){return d}}]),b}();return a(document).on(l.CLICK_DATA_API,n.DATA_TOGGLE,function(b){b.preventDefault(),o._jQueryInterface.call(a(this),"show")}),a.fn[b]=o._jQueryInterface,a.fn[b].Constructor=o,a.fn[b].noConflict=function(){return a.fn[b]=j,o._jQueryInterface},o}(jQuery),function(a){if(void 0===window.Tether)throw new Error("Bootstrap tooltips require Tether (http://tether.io/)");var b="tooltip",g="4.0.0-alpha.5",h="bs.tooltip",i="."+h,j=a.fn[b],k=150,l="bs-tether",m={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:"0 0",constraints:[]},n={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"string",constraints:"array"},o={TOP:"bottom center",RIGHT:"middle left",BOTTOM:"top center",LEFT:"middle right"},p={IN:"in",OUT:"out"},q={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},r={FADE:"fade",IN:"in"},s={TOOLTIP:".tooltip",TOOLTIP_INNER:".tooltip-inner"},t={element:!1,enabled:!1},u={HOVER:"hover",FOCUS:"focus",CLICK:"click",MANUAL:"manual"},v=function(){function j(a,b){c(this,j),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._tether=null,this.element=a,this.config=this._getConfig(b),this.tip=null,this._setListeners()}return j.prototype.enable=function(){this._isEnabled=!0},j.prototype.disable=function(){this._isEnabled=!1},j.prototype.toggleEnabled=function(){this._isEnabled=!this._isEnabled},j.prototype.toggle=function(b){if(b){var c=this.constructor.DATA_KEY,d=a(b.currentTarget).data(c);d||(d=new this.constructor(b.currentTarget,this._getDelegateConfig()),a(b.currentTarget).data(c,d)),d._activeTrigger.click=!d._activeTrigger.click,d._isWithActiveTrigger()?d._enter(null,d):d._leave(null,d)}else{if(a(this.getTipElement()).hasClass(r.IN))return void this._leave(null,this);this._enter(null,this)}},j.prototype.dispose=function(){clearTimeout(this._timeout),this.cleanupTether(),a.removeData(this.element,this.constructor.DATA_KEY),a(this.element).off(this.constructor.EVENT_KEY),this.tip&&a(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._tether=null,this.element=null,this.config=null,this.tip=null},j.prototype.show=function(){var b=this,c=a.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){a(this.element).trigger(c);var d=a.contains(this.element.ownerDocument.documentElement,this.element);if(c.isDefaultPrevented()||!d)return;var e=this.getTipElement(),g=f.getUID(this.constructor.NAME);e.setAttribute("id",g),this.element.setAttribute("aria-describedby",g),this.setContent(),this.config.animation&&a(e).addClass(r.FADE);var h="function"==typeof this.config.placement?this.config.placement.call(this,e,this.element):this.config.placement,i=this._getAttachment(h);a(e).data(this.constructor.DATA_KEY,this).appendTo(document.body),a(this.element).trigger(this.constructor.Event.INSERTED),this._tether=new Tether({attachment:i,element:e,target:this.element,classes:t,classPrefix:l,offset:this.config.offset,constraints:this.config.constraints,addTargetClasses:!1}),f.reflow(e),this._tether.position(),a(e).addClass(r.IN);var k=function(){var c=b._hoverState;b._hoverState=null,a(b.element).trigger(b.constructor.Event.SHOWN),c===p.OUT&&b._leave(null,b)};if(f.supportsTransitionEnd()&&a(this.tip).hasClass(r.FADE))return void a(this.tip).one(f.TRANSITION_END,k).emulateTransitionEnd(j._TRANSITION_DURATION);k()}},j.prototype.hide=function(b){var c=this,d=this.getTipElement(),e=a.Event(this.constructor.Event.HIDE),g=function(){c._hoverState!==p.IN&&d.parentNode&&d.parentNode.removeChild(d),c.element.removeAttribute("aria-describedby"),a(c.element).trigger(c.constructor.Event.HIDDEN),c.cleanupTether(),b&&b()};a(this.element).trigger(e),e.isDefaultPrevented()||(a(d).removeClass(r.IN),f.supportsTransitionEnd()&&a(this.tip).hasClass(r.FADE)?a(d).one(f.TRANSITION_END,g).emulateTransitionEnd(k):g(),this._hoverState="")},j.prototype.isWithContent=function(){return Boolean(this.getTitle())},j.prototype.getTipElement=function(){return this.tip=this.tip||a(this.config.template)[0]},j.prototype.setContent=function(){var b=a(this.getTipElement());this.setElementContent(b.find(s.TOOLTIP_INNER),this.getTitle()),b.removeClass(r.FADE).removeClass(r.IN),this.cleanupTether()},j.prototype.setElementContent=function(b,c){var e=this.config.html;"object"===("undefined"==typeof c?"undefined":d(c))&&(c.nodeType||c.jquery)?e?a(c).parent().is(b)||b.empty().append(c):b.text(a(c).text()):b[e?"html":"text"](c)},j.prototype.getTitle=function(){var a=this.element.getAttribute("data-original-title");return a||(a="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),a},j.prototype.cleanupTether=function(){this._tether&&this._tether.destroy()},j.prototype._getAttachment=function(a){return o[a.toUpperCase()]},j.prototype._setListeners=function(){var b=this,c=this.config.trigger.split(" ");c.forEach(function(c){if("click"===c)a(b.element).on(b.constructor.Event.CLICK,b.config.selector,a.proxy(b.toggle,b));else if(c!==u.MANUAL){var d=c===u.HOVER?b.constructor.Event.MOUSEENTER:b.constructor.Event.FOCUSIN,e=c===u.HOVER?b.constructor.Event.MOUSELEAVE:b.constructor.Event.FOCUSOUT;a(b.element).on(d,b.config.selector,a.proxy(b._enter,b)).on(e,b.config.selector,a.proxy(b._leave,b))}}),this.config.selector?this.config=a.extend({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},j.prototype._fixTitle=function(){var a=d(this.element.getAttribute("data-original-title"));(this.element.getAttribute("title")||"string"!==a)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},j.prototype._enter=function(b,c){var d=this.constructor.DATA_KEY;return c=c||a(b.currentTarget).data(d),c||(c=new this.constructor(b.currentTarget,this._getDelegateConfig()),a(b.currentTarget).data(d,c)),b&&(c._activeTrigger["focusin"===b.type?u.FOCUS:u.HOVER]=!0),a(c.getTipElement()).hasClass(r.IN)||c._hoverState===p.IN?void(c._hoverState=p.IN):(clearTimeout(c._timeout),c._hoverState=p.IN,c.config.delay&&c.config.delay.show?void(c._timeout=setTimeout(function(){c._hoverState===p.IN&&c.show()},c.config.delay.show)):void c.show())},j.prototype._leave=function(b,c){var d=this.constructor.DATA_KEY;if(c=c||a(b.currentTarget).data(d),c||(c=new this.constructor(b.currentTarget,this._getDelegateConfig()),a(b.currentTarget).data(d,c)),b&&(c._activeTrigger["focusout"===b.type?u.FOCUS:u.HOVER]=!1),!c._isWithActiveTrigger())return clearTimeout(c._timeout),c._hoverState=p.OUT,c.config.delay&&c.config.delay.hide?void(c._timeout=setTimeout(function(){c._hoverState===p.OUT&&c.hide()},c.config.delay.hide)):void c.hide()},j.prototype._isWithActiveTrigger=function(){for(var a in this._activeTrigger)if(this._activeTrigger[a])return!0;return!1},j.prototype._getConfig=function(c){return c=a.extend({},this.constructor.Default,a(this.element).data(),c),c.delay&&"number"==typeof c.delay&&(c.delay={show:c.delay,hide:c.delay}),f.typeCheckConfig(b,c,this.constructor.DefaultType),c},j.prototype._getDelegateConfig=function(){var a={};if(this.config)for(var b in this.config)this.constructor.Default[b]!==this.config[b]&&(a[b]=this.config[b]);return a},j._jQueryInterface=function(b){return this.each(function(){var c=a(this).data(h),e="object"===("undefined"==typeof b?"undefined":d(b))?b:null;if((c||!/dispose|hide/.test(b))&&(c||(c=new j(this,e),a(this).data(h,c)),"string"==typeof b)){if(void 0===c[b])throw new Error('No method named "'+b+'"');c[b]()}})},e(j,null,[{key:"VERSION",get:function(){return g}},{key:"Default",get:function(){return m}},{key:"NAME",get:function(){return b}},{key:"DATA_KEY",get:function(){return h}},{key:"Event",get:function(){return q}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return n}}]),j}();return a.fn[b]=v._jQueryInterface,a.fn[b].Constructor=v,a.fn[b].noConflict=function(){return a.fn[b]=j,v._jQueryInterface},v}(jQuery));(function(f){var h="popover",i="4.0.0-alpha.5",j="bs.popover",k="."+j,l=f.fn[h],m=f.extend({},g.Default,{placement:"right",trigger:"click",content:"",template:''}),n=f.extend({},g.DefaultType,{content:"(string|element|function)"}),o={FADE:"fade",IN:"in"},p={TITLE:".popover-title",CONTENT:".popover-content"},q={HIDE:"hide"+k,HIDDEN:"hidden"+k,SHOW:"show"+k,SHOWN:"shown"+k,INSERTED:"inserted"+k,CLICK:"click"+k,FOCUSIN:"focusin"+k,FOCUSOUT:"focusout"+k,MOUSEENTER:"mouseenter"+k,MOUSELEAVE:"mouseleave"+k},r=function(g){function l(){return c(this,l),a(this,g.apply(this,arguments))}return b(l,g),l.prototype.isWithContent=function(){return this.getTitle()||this._getContent()},l.prototype.getTipElement=function(){return this.tip=this.tip||f(this.config.template)[0]},l.prototype.setContent=function(){var a=f(this.getTipElement());this.setElementContent(a.find(p.TITLE),this.getTitle()),this.setElementContent(a.find(p.CONTENT),this._getContent()),a.removeClass(o.FADE).removeClass(o.IN),this.cleanupTether()},l.prototype._getContent=function(){return this.element.getAttribute("data-content")||("function"==typeof this.config.content?this.config.content.call(this.element):this.config.content)},l._jQueryInterface=function(a){return this.each(function(){var b=f(this).data(j),c="object"===("undefined"==typeof a?"undefined":d(a))?a:null;if((b||!/destroy|hide/.test(a))&&(b||(b=new l(this,c),f(this).data(j,b)),"string"==typeof a)){if(void 0===b[a])throw new Error('No method named "'+a+'"');b[a]()}})},e(l,null,[{key:"VERSION",get:function(){return i}},{key:"Default",get:function(){return m}},{key:"NAME",get:function(){return h}},{key:"DATA_KEY",get:function(){return j}},{key:"Event",get:function(){return q}},{key:"EVENT_KEY",get:function(){return k}},{key:"DefaultType",get:function(){return n}}]),l}(g);return f.fn[h]=r._jQueryInterface,f.fn[h].Constructor=r,f.fn[h].noConflict=function(){return f.fn[h]=l,r._jQueryInterface},r})(jQuery)}(); \ No newline at end of file diff --git a/src/public/default/js/holder.min.js b/src/public/default/js/holder.min.js new file mode 100644 index 0000000..62255af --- /dev/null +++ b/src/public/default/js/holder.min.js @@ -0,0 +1,13 @@ +/*! + +Holder - client side image placeholders +Version 2.9.4+cabil +© 2016 Ivan Malopinsky - http://imsky.co + +Site: http://holderjs.com +Issues: https://github.com/imsky/holder/issues +License: MIT + +*/ +!function(e){if(e.document){var t=e.document;t.querySelectorAll||(t.querySelectorAll=function(n){var r,i=t.createElement("style"),o=[];for(t.documentElement.firstChild.appendChild(i),t._qsa=[],i.styleSheet.cssText=n+"{x-qsa:expression(document._qsa && document._qsa.push(this))}",e.scrollBy(0,0),i.parentNode.removeChild(i);t._qsa.length;)r=t._qsa.shift(),r.style.removeAttribute("x-qsa"),o.push(r);return t._qsa=null,o}),t.querySelector||(t.querySelector=function(e){var n=t.querySelectorAll(e);return n.length?n[0]:null}),t.getElementsByClassName||(t.getElementsByClassName=function(e){return e=String(e).replace(/^|\s+/g,"."),t.querySelectorAll(e)}),Object.keys||(Object.keys=function(e){if(e!==Object(e))throw TypeError("Object.keys called on non-object");var t,n=[];for(t in e)Object.prototype.hasOwnProperty.call(e,t)&&n.push(t);return n}),Array.prototype.forEach||(Array.prototype.forEach=function(e){if(void 0===this||null===this)throw TypeError();var t=Object(this),n=t.length>>>0;if("function"!=typeof e)throw TypeError();var r,i=arguments[1];for(r=0;r>16&255)),i.push(String.fromCharCode(o>>8&255)),i.push(String.fromCharCode(255&o)),a=0,o=0),r+=1;return 12===a?(o>>=4,i.push(String.fromCharCode(255&o))):18===a&&(o>>=2,i.push(String.fromCharCode(o>>8&255)),i.push(String.fromCharCode(255&o))),i.join("")},e.btoa=e.btoa||function(e){e=String(e);var n,r,i,o,a,s,l,h=0,u=[];if(/[^\x00-\xFF]/.test(e))throw Error("InvalidCharacterError");for(;h>2,a=(3&n)<<4|r>>4,s=(15&r)<<2|i>>6,l=63&i,h===e.length+2?(s=64,l=64):h===e.length+1&&(l=64),u.push(t.charAt(o),t.charAt(a),t.charAt(s),t.charAt(l));return u.join("")}}(e),Object.prototype.hasOwnProperty||(Object.prototype.hasOwnProperty=function(e){var t=this.__proto__||this.constructor.prototype;return e in this&&(!(e in t)||t[e]!==this[e])}),function(){if("performance"in e==!1&&(e.performance={}),Date.now=Date.now||function(){return(new Date).getTime()},"now"in e.performance==!1){var t=Date.now();performance.timing&&performance.timing.navigationStart&&(t=performance.timing.navigationStart),e.performance.now=function(){return Date.now()-t}}}(),e.requestAnimationFrame||(e.webkitRequestAnimationFrame&&e.webkitCancelAnimationFrame?!function(e){e.requestAnimationFrame=function(t){return webkitRequestAnimationFrame(function(){t(e.performance.now())})},e.cancelAnimationFrame=e.webkitCancelAnimationFrame}(e):e.mozRequestAnimationFrame&&e.mozCancelAnimationFrame?!function(e){e.requestAnimationFrame=function(t){return mozRequestAnimationFrame(function(){t(e.performance.now())})},e.cancelAnimationFrame=e.mozCancelAnimationFrame}(e):!function(e){e.requestAnimationFrame=function(t){return e.setTimeout(t,1e3/60)},e.cancelAnimationFrame=e.clearTimeout}(e))}}(this),function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Holder=t():e.Holder=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return e[r].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){(function(t){function r(e,t,n,r){var a=i(n.substr(n.lastIndexOf(e.domain)),e);a&&o({mode:null,el:r,flags:a,engineSettings:t})}function i(e,t){var n={theme:k(O.settings.themes.gray,null),stylesheets:t.stylesheets,instanceOptions:t},r=e.indexOf("?"),i=[e];r!==-1&&(i=[e.slice(0,r),e.slice(r+1)]);var o=i[0].split("/");n.holderURL=e;var a=o[1],s=a.match(/([\d]+p?)x([\d]+p?)/);if(!s)return!1;if(n.fluid=a.indexOf("p")!==-1,n.dimensions={width:s[1].replace("p","%"),height:s[2].replace("p","%")},2===i.length){var l=v.parse(i[1]);if(w.truthy(l.ratio)){n.fluid=!0;var h=parseFloat(n.dimensions.width.replace("%","")),u=parseFloat(n.dimensions.height.replace("%",""));u=Math.floor(100*(u/h)),h=100,n.dimensions.width=h+"%",n.dimensions.height=u+"%"}if(n.auto=w.truthy(l.auto),l.bg&&(n.theme.bg=w.parseColor(l.bg)),l.fg&&(n.theme.fg=w.parseColor(l.fg)),l.bg&&!l.fg&&(n.autoFg=!0),l.theme&&n.instanceOptions.themes.hasOwnProperty(l.theme)&&(n.theme=k(n.instanceOptions.themes[l.theme],null)),l.text&&(n.text=l.text),l.textmode&&(n.textmode=l.textmode),l.size&&(n.size=l.size),l.font&&(n.font=l.font),l.align&&(n.align=l.align),l.lineWrap&&(n.lineWrap=l.lineWrap),n.nowrap=w.truthy(l.nowrap),n.outline=w.truthy(l.outline),w.truthy(l.random)){O.vars.cache.themeKeys=O.vars.cache.themeKeys||Object.keys(n.instanceOptions.themes);var c=O.vars.cache.themeKeys[0|Math.random()*O.vars.cache.themeKeys.length];n.theme=k(n.instanceOptions.themes[c],null)}}return n}function o(e){var t=e.mode,n=e.el,r=e.flags,i=e.engineSettings,o=r.dimensions,s=r.theme,l=o.width+"x"+o.height;t=null==t?r.fluid?"fluid":"image":t;var c=/holder_([a-z]+)/g,d=!1;if(null!=r.text&&(s.text=r.text,"object"===n.nodeName.toLowerCase())){for(var f=s.text.split("\\n"),p=0;p1){var b,x=0,A=0,C=0;w=new s.Group("line"+C),"left"!==e.align&&"right"!==e.align||(o=e.width*(1-2*(1-r)));for(var E=0;E=o||T===!0)&&(t(g,w,x,g.properties.leading),g.add(w),x=0,A+=g.properties.leading,C+=1,w=new s.Group("line"+C),w.y=A),T!==!0&&(v.moveTo(x,0),x+=m.spaceWidth+k.width,w.add(v))}if(t(g,w,x,g.properties.leading),g.add(w),"left"===e.align)g.moveTo(e.width-i,null,null);else if("right"===e.align){for(b in g.children)w=g.children[b],w.moveTo(e.width-w.width,null,null);g.moveTo(0-(e.width-i),null,null)}else{for(b in g.children)w=g.children[b],w.moveTo((g.width-w.width)/2,null,null);g.moveTo((e.width-g.width)/2,null,null)}g.moveTo(null,(e.height-g.height)/2,null),(e.height-g.height)/2<0&&g.moveTo(null,0,null)}else v=new s.Text(e.text),w=new s.Group("line0"),w.add(v),g.add(w),"left"===e.align?g.moveTo(e.width-i,null,null):"right"===e.align?g.moveTo(0-(e.width-i),null,null):g.moveTo((e.width-m.boundingBox.width)/2,null,null),g.moveTo(null,(e.height-m.boundingBox.height)/2,null);return a}function l(e,t,n,r){var i=parseInt(e,10),o=parseInt(t,10),a=Math.max(i,o),s=Math.min(i,o),l=.8*Math.min(s,a*r);return Math.round(Math.max(n,l))}function h(e){var t;t=null==e||null==e.nodeType?O.vars.resizableImages:[e];for(var n=0,r=t.length;n1){n.nodeValue="";for(var v=0;v=0?t:1)}function o(e){x?i(e):S.push(e)}null==document.readyState&&document.addEventListener&&(document.addEventListener("DOMContentLoaded",function C(){document.removeEventListener("DOMContentLoaded",C,!1),document.readyState="complete"},!1),document.readyState="loading");var a=e.document,s=a.documentElement,l="load",h=!1,u="on"+l,c="complete",d="readyState",f="attachEvent",p="detachEvent",g="addEventListener",m="DOMContentLoaded",v="onreadystatechange",y="removeEventListener",w=g in a,b=h,x=h,S=[];if(a[d]===c)i(t);else if(w)a[g](m,n,h),e[g](l,n,h);else{a[f](v,n),e[f](u,n);try{b=null==e.frameElement&&s}catch(A){}b&&b.doScroll&&!function E(){if(!x){try{b.doScroll("left")}catch(e){return i(E,50)}r(),t()}}()}return o.version="1.4.0",o.isReady=function(){return x},o}e.exports="undefined"!=typeof window&&n(window)},function(e,t,n){var r=encodeURIComponent,i=decodeURIComponent,o=n(4),a=n(5),s=/(\w+)\[(\d+)\]/,l=/\w+\.\w+/;t.parse=function(e){if("string"!=typeof e)return{};if(e=o(e),""===e)return{};"?"===e.charAt(0)&&(e=e.slice(1));for(var t={},n=e.split("&"),r=0;r=0;r--)n=e.charCodeAt(r),n>128?t.unshift(["&#",n,";"].join("")):t.unshift(e[r]);return t.join("")},t.imageExists=function(e,t){var n=new Image;n.onerror=function(){t.call(this,!1)},n.onload=function(){t.call(this,!0)},n.src=e},t.decodeHtmlEntity=function(e){return e.replace(/&#(\d+);/g,function(e,t){return String.fromCharCode(t)})},t.dimensionCheck=function(e){var t={height:e.clientHeight,width:e.clientWidth};return!(!t.height||!t.width)&&t},t.truthy=function(e){return"string"==typeof e?"true"===e||"yes"===e||"1"===e||"on"===e||"✓"===e:!!e},t.parseColor=function(e){var t,n=/(^(?:#?)[0-9a-f]{6}$)|(^(?:#?)[0-9a-f]{3}$)/i,r=/^rgb\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/,i=/^rgba\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0\.\d{1,}|1)\)$/,o=e.match(n);return null!==o?(t=o[1]||o[2],"#"!==t[0]?"#"+t:t):(o=e.match(r),null!==o?t="rgb("+o.slice(1).join(",")+")":(o=e.match(i),null!==o?t="rgba("+o.slice(1).join(",")+")":null))},t.canvasRatio=function(){var t=1,n=1;if(e.document){var r=e.document.createElement("canvas");if(r.getContext){var i=r.getContext("2d");t=e.devicePixelRatio||1,n=i.webkitBackingStorePixelRatio||i.mozBackingStorePixelRatio||i.msBackingStorePixelRatio||i.oBackingStorePixelRatio||i.backingStorePixelRatio||1}}return t/n}}).call(t,function(){return this}())},function(e,t,n){(function(e){var r=n(9),i="http://www.w3.org/2000/svg",o=8;t.initSVG=function(e,t,n){var a,s,l=!1;e&&e.querySelector?(s=e.querySelector("style"),null===s&&(l=!0)):(e=r.newEl("svg",i),l=!0),l&&(a=r.newEl("defs",i),s=r.newEl("style",i),r.setAttr(s,{type:"text/css"}),a.appendChild(s),e.appendChild(a)),e.webkitMatchesSelector&&e.setAttribute("xmlns",i);for(var h=0;h=0;l--){var h=s.createProcessingInstruction("xml-stylesheet",'href="'+a[l]+'" rel="stylesheet"');s.insertBefore(h,s.firstChild)}s.removeChild(s.documentElement),o=i.serializeToString(s)}var u=i.serializeToString(t);return u=u.replace(/\&(\#[0-9]{2,}\;)/g,"&$1"),o+u}}}).call(t,function(){return this}())},function(e,t){(function(e){t.newEl=function(t,n){if(e.document)return null==n?e.document.createElement(t):e.document.createElementNS(n,t)},t.setAttr=function(e,t){for(var n in t)e.setAttribute(n,t[n])},t.createXML=function(){if(e.DOMParser)return(new DOMParser).parseFromString("","application/xml")},t.getNodeArray=function(t){var n=null;return"string"==typeof t?n=document.querySelectorAll(t):e.NodeList&&t instanceof e.NodeList?n=t:e.Node&&t instanceof e.Node?n=[t]:e.HTMLCollection&&t instanceof e.HTMLCollection?n=t:t instanceof Array?n=t:null===t&&(n=[]),n=Array.prototype.slice.call(n)}}).call(t,function(){return this}())},function(e,t){var n=function(e,t){"string"==typeof e&&(this.original=e,"#"===e.charAt(0)&&(e=e.slice(1)),/[^a-f0-9]+/i.test(e)||(3===e.length&&(e=e.replace(/./g,"$&$&")),6===e.length&&(this.alpha=1,t&&t.alpha&&(this.alpha=t.alpha),this.set(parseInt(e,16)))))};n.rgb2hex=function(e,t,n){function r(e){var t=(0|e).toString(16);return e<16&&(t="0"+t),t}return[e,t,n].map(r).join("")},n.hsl2rgb=function(e,t,n){var r=e/60,i=(1-Math.abs(2*n-1))*t,o=i*(1-Math.abs(parseInt(r)%2-1)),a=n-i/2,s=0,l=0,h=0;return r>=0&&r<1?(s=i,l=o):r>=1&&r<2?(s=o,l=i):r>=2&&r<3?(l=i,h=o):r>=3&&r<4?(l=o,h=i):r>=4&&r<5?(s=o,h=i):r>=5&&r<6&&(s=i,h=o),s+=a,l+=a,h+=a,s=parseInt(255*s),l=parseInt(255*l),h=parseInt(255*h),[s,l,h]},n.prototype.set=function(e){this.raw=e;var t=(16711680&this.raw)>>16,n=(65280&this.raw)>>8,r=255&this.raw,i=.2126*t+.7152*n+.0722*r,o=-.09991*t-.33609*n+.436*r,a=.615*t-.55861*n-.05639*r;return this.rgb={r:t,g:n,b:r},this.yuv={y:i,u:o,v:a},this},n.prototype.lighten=function(e){var t=Math.min(1,Math.max(0,Math.abs(e)))*(e<0?-1:1),r=255*t|0,i=Math.min(255,Math.max(0,this.rgb.r+r)),o=Math.min(255,Math.max(0,this.rgb.g+r)),a=Math.min(255,Math.max(0,this.rgb.b+r)),s=n.rgb2hex(i,o,a);return new n(s)},n.prototype.toHex=function(e){return(e?"#":"")+this.raw.toString(16)},n.prototype.lighterThan=function(e){return e instanceof n||(e=new n(e)),this.yuv.y>e.yuv.y},n.prototype.blendAlpha=function(e){e instanceof n||(e=new n(e));var t=e,r=this,i=t.alpha*t.rgb.r+(1-t.alpha)*r.rgb.r,o=t.alpha*t.rgb.g+(1-t.alpha)*r.rgb.g,a=t.alpha*t.rgb.b+(1-t.alpha)*r.rgb.b;return new n(n.rgb2hex(i,o,a))},e.exports=n},function(e,t){e.exports={version:"2.9.4",svg_ns:"http://www.w3.org/2000/svg"}},function(e,t,n){function r(e,t){return c.element({tag:t,width:e.width,height:e.height,fill:e.properties.fill})}function i(e){return h.cssProps({fill:e.fill,"font-weight":e.font.weight,"font-family":e.font.family+", monospace","font-size":e.font.size+e.font.units})}function o(e,t,n){var r=n/2;return["M",r,r,"H",e-r,"V",t-r,"H",r,"V",0,"M",0,r,"L",e,t-r,"M",0,t-r,"L",e,r].join(" ")}var a=n(13),s=n(8),l=n(11),h=n(7),u=l.svg_ns,c={element:function(e){var t=e.tag,n=e.content||"";return delete e.tag,delete e.content,[t,n,e]}};e.exports=function(e,t){var n=t.engineSettings,l=n.stylesheets,h=l.map(function(e){return''}).join("\n"),d="holder_"+Number(new Date).toString(16),f=e.root,p=f.children.holderTextGroup,g="#"+d+" text { "+i(p.properties)+" } ";p.y+=.8*p.textPositionData.boundingBox.height;var m=[];Object.keys(p.children).forEach(function(e){var t=p.children[e];Object.keys(t.children).forEach(function(e){var n=t.children[e],r=p.x+t.x+n.x,i=p.y+t.y+n.y,o=c.element({tag:"text",content:n.properties.text,x:r,y:i});m.push(o)})});var v=c.element({tag:"g",content:m}),y=null;if(f.children.holderBg.properties.outline){var w=f.children.holderBg.properties.outline;y=c.element({tag:"path",d:o(f.children.holderBg.width,f.children.holderBg.height,w.width),"stroke-width":w.width,stroke:w.fill,fill:"none"})}var b=r(f.children.holderBg,"rect"),x=[];x.push(b),w&&x.push(y),x.push(v);var S=c.element({tag:"g",id:d,content:x}),A=c.element({tag:"style",content:g,type:"text/css"}),C=c.element({tag:"defs",content:A}),E=c.element({tag:"svg",content:[C,S],width:f.properties.width,height:f.properties.height,xmlns:u,viewBox:[0,0,f.properties.width,f.properties.height].join(" "),preserveAspectRatio:"none"}),k=a(E);k=h+k[0];var T=s.svgStringToDataURI(k,"background"===t.mode);return T}},function(e,t,n){n(14);e.exports=function r(e,t,n){"use strict";function i(e){var t=e.match(/^[\w-]+/),r={tag:t?t[0]:"div",attr:{},children:[]},i=e.match(/#([\w-]+)/),o=e.match(/\$([\w-]+)/),a=e.match(/\.[\w-]+/g);return i&&(r.attr.id=i[1],n[i[1]]=r),o&&(n[o[1]]=r),a&&(r.attr["class"]=a.join(" ").replace(/\./g,"")),e.match(/&$/g)&&(f=!1),r}function o(e,t){if(null!==t&&t!==!1&&void 0!==t)return"string"!=typeof t&&"object"!=typeof t?String(t):t}function a(e){return e||0===e?String(e).replace(/&/g,"&").replace(/"/g,"""):""}function s(e){return String(e).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}var l,h,u,c,d=1,f=!0;if(n=n||{},"string"==typeof e[0])e[0]=i(e[0]);else{if(!Array.isArray(e[0]))throw new Error("First element of array must be a string, or an array and not "+JSON.stringify(e[0]));d=0}for(;d",e[0]=l}return n[0]=e[0],u&&u(e[0]),n}},function(e,t){"use strict";function n(e){var t=""+e,n=r.exec(t);if(!n)return t;var i,o="",a=0,s=0;for(a=n.index;a]/;e.exports=n},function(e,t,n){var r=n(9),i=n(7);e.exports=function(){var e=r.newEl("canvas"),t=null;return function(n){null==t&&(t=e.getContext("2d"));var r=i.canvasRatio(),o=n.root;e.width=r*o.properties.width,e.height=r*o.properties.height,t.textBaseline="middle";var a=o.children.holderBg,s=r*a.width,l=r*a.height,h=2,u=h/2;t.fillStyle=a.properties.fill,t.fillRect(0,0,s,l),a.properties.outline&&(t.strokeStyle=a.properties.outline.fill,t.lineWidth=a.properties.outline.width,t.moveTo(u,u),t.lineTo(s-u,u),t.lineTo(s-u,l-u),t.lineTo(u,l-u),t.lineTo(u,u),t.moveTo(0,u),t.lineTo(s,l-u),t.moveTo(0,l-u),t.lineTo(s,u),t.stroke());var c=o.children.holderTextGroup;t.font=c.properties.font.weight+" "+r*c.properties.font.size+c.properties.font.units+" "+c.properties.font.family+", monospace",t.fillStyle=c.properties.fill;for(var d in c.children){var f=c.children[d];for(var p in f.children){var g=f.children[p],m=r*(c.x+f.x+g.x),v=r*(c.y+f.y+g.y+c.properties.leading/2);t.fillText(g.properties.text,m,v)}}return e.toDataURL("image/png")}}()}])}),function(e,t){t&&(Holder=e.Holder); +}(this,"undefined"!=typeof Meteor&&"undefined"!=typeof Package); \ No newline at end of file diff --git a/src/public/default/js/ie10-viewport-bug-workaround.js b/src/public/default/js/ie10-viewport-bug-workaround.js new file mode 100644 index 0000000..0aecf6b --- /dev/null +++ b/src/public/default/js/ie10-viewport-bug-workaround.js @@ -0,0 +1,24 @@ +/*! + * IE10 viewport hack for Surface/desktop Windows 8 bug + * Copyright 2014-2015 The Bootstrap Authors + * Copyright 2014-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +// See the Getting Started docs for more information: +// https://getbootstrap.com/getting-started/#support-ie10-width + +(function () { + 'use strict'; + + if (navigator.userAgent.match(/IEMobile\/10\.0/)) { + var msViewportStyle = document.createElement('style') + msViewportStyle.appendChild( + document.createTextNode( + '@-ms-viewport{width:auto!important}' + ) + ) + document.head.appendChild(msViewportStyle) + } + +})(); diff --git a/src/public/index.php b/src/public/index.php new file mode 100644 index 0000000..628bdd5 --- /dev/null +++ b/src/public/index.php @@ -0,0 +1,116 @@ + $config]); +$container = $app->getContainer(); + +// Load all classes +$classes = glob('../classes/*.php'); +foreach ($classes as $class) { + require $class; +} + +// Load all models +$models = glob('../models/*.php'); +foreach ($models as $model) { + require $model; +} + +// Register component templates on container +if (strtolower($config['templateRender']) == "php") +{ + $container['viewbackend'] = new \Slim\Views\PhpRenderer('../templates/'.$container['settings']['theme'].'/backend'); + $container['viewfrontend'] = new \Slim\Views\PhpRenderer('../templates/'.$container['settings']['theme'].'/frontend'); +} +else +{ + $container['viewbackend'] = function ($container) { + if ($container['settings']['twigcache'] == true){ + $options = ['cache' => 'cache']; + } else { + $options = []; + } + + $viewbackend = new \Slim\Views\Twig('../templates/'.$container['settings']['theme'].'/backend', $options); + + // Instantiate and add Slim specific extension + $basePath = rtrim(str_ireplace('index.php', '', $container['request']->getUri()->getBasePath()), '/'); + $viewbackend->addExtension(new Slim\Views\TwigExtension($container['router'], $basePath)); + + return $viewbackend; + }; + + $container['viewfrontend'] = function ($container) { + if ($container['settings']['twigcache'] == true){ + $options = ['cache' => 'cache']; + } else { + $options = []; + } + $viewfrontend = new \Slim\Views\Twig('../templates/'.$container['settings']['theme'].'/frontend', $options); + + // Instantiate and add Slim specific extension + $basePath = rtrim(str_ireplace('index.php', '', $container['request']->getUri()->getBasePath()), '/'); + $viewfrontend->addExtension(new Slim\Views\TwigExtension($container['router'], $basePath)); + + return $viewfrontend; + }; +} + + +// Register component Monolog +$container['logger'] = function($c) { + $logger = new \Monolog\Logger('my_logger'); + $file_handler = new \Monolog\Handler\StreamHandler("../logs/app.log"); + $logger->pushHandler($file_handler); + return $logger; +}; + +// Register component database connection on container +$container['db'] = function ($c) { + $db = $c['settings']['db']; + $pdo = new PDO("mysql:host=" . $db['host'] . ";dbname=" . $db['dbname'], + $db['user'], $db['pass']); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + return $pdo; +}; + +// Override the default Not Found Handler +$container['notFoundHandler'] = function ($container) { + return function ($request, $response) use ($container) { + return $response->withRedirect($container['request']->getUri()->getBasePath().'/404'); + }; +}; + +// Override the default Not Allowed Handler +$container['notAllowedHandler'] = function ($container) { + return function ($request, $response, $methods) use ($container) { + return $container['response'] + ->withStatus(405) + ->withHeader('Allow', implode(', ', $methods)) + ->withHeader('Content-type', 'text/html') + ->write('Method must be one of: ' . implode(', ', $methods)); + }; +}; + +// Load all router files before run +$routers = glob('../routers/*.router.php'); +foreach ($routers as $router) { + require $router; +} + +$app->run(); + +?> \ No newline at end of file diff --git a/src/routers/api.router.php b/src/routers/api.router.php new file mode 100644 index 0000000..fc616de --- /dev/null +++ b/src/routers/api.router.php @@ -0,0 +1,21 @@ +get('/api/loop', function (Request $request, Response $response) { + $oStuff = new models\Starter($this->db); + $results = $oStuff->getAll(); + $body = $response->getBody(); + if ($results != 0){ + $body->write(json_encode(array("result" => $results, "status" => "success", "code" => $response->getStatusCode()), JSON_PRETTY_PRINT)); + } else { + $body->write(json_encode(array("result" => 'no records found!', "status" => "success", "code" => $response->getStatusCode()), JSON_PRETTY_PRINT)); + } + return $response + ->withHeader('Content-Type','application/json; charset=utf-8') + ->withHeader('Access-Control-Allow-Origin', '*') + ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization') + ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') + ->withBody($body); + }); \ No newline at end of file diff --git a/src/routers/handler.router.php b/src/routers/handler.router.php new file mode 100644 index 0000000..6bc3838 --- /dev/null +++ b/src/routers/handler.router.php @@ -0,0 +1,12 @@ +get('/404', function (Request $request, Response $response) { + $response = $this->viewfrontend->render($response->withStatus(404), '404.html', [ + 'code' => '404', + 'notfound' => 'Sorry, The page you are looking for is not found...', + 'router' => $this->router]); + return $response; + }); diff --git a/src/routers/hello.router.php b/src/routers/hello.router.php new file mode 100644 index 0000000..c9c5fc4 --- /dev/null +++ b/src/routers/hello.router.php @@ -0,0 +1,10 @@ +get('/hello/{name}', function (Request $request, Response $response) { + $username = $request->getAttribute('name'); + $response = $this->viewfrontend->render($response, "user.html", ["username" => $username, "router" => $this->router]); + return $response; + }); \ No newline at end of file diff --git a/src/routers/index.router.php b/src/routers/index.router.php new file mode 100644 index 0000000..5a9a47c --- /dev/null +++ b/src/routers/index.router.php @@ -0,0 +1,17 @@ +get('/', function (Request $request, Response $response) { + $oStuff = new models\Starter(); + $hello = $oStuff->setHello(); + + $response = $this->viewfrontend->render($response, "index.html", [ + "hello" => $hello['hello'], + "description1" => $hello['description1'], + "description2" => $hello['description2'], + "author" => $hello['author'], + "router" => $this->router]); + return $response; + })->setName("/"); diff --git a/src/routers/loop.router.php b/src/routers/loop.router.php new file mode 100644 index 0000000..236e284 --- /dev/null +++ b/src/routers/loop.router.php @@ -0,0 +1,15 @@ +get('/loop', function (Request $request, Response $response) { + $oStuff = new models\Starter($this->db); + $results = $oStuff->getAll(); + if ($results != 0){ + $response = $this->viewfrontend->render($response,'loop.html', ['items' => $results, 'router' => $this->router]); + } else { + $response = $this->viewfrontend->render($response,'loop.html', ['items' => 'no records found!', 'router' => $this->router]); + } + return $response; + }); \ No newline at end of file diff --git a/src/templates/default/frontend/404.html b/src/templates/default/frontend/404.html new file mode 100644 index 0000000..d2234e7 --- /dev/null +++ b/src/templates/default/frontend/404.html @@ -0,0 +1,43 @@ + + + + {% include 'layout.global.meta.html' %} + + + + + 404 - iSlim3 with Bootstrap 4 + {% include 'layout.global.css.html' %} + + + + + +
+ +
+

{{ code }}

+

{{ notfound }}

+

Go to homepage

+
+ +
+ {% include 'layout.global.js.html' %} + + \ No newline at end of file diff --git a/src/templates/default/frontend/index.html b/src/templates/default/frontend/index.html new file mode 100644 index 0000000..491ffa0 --- /dev/null +++ b/src/templates/default/frontend/index.html @@ -0,0 +1,43 @@ + + + + {% include 'layout.global.meta.html' %} + + + + + iSlim3 with Bootstrap 4 + {% include 'layout.global.css.html' %} + + + + + +
+ +
+

{{ hello }}

+

{{ description1 }}
{{ description2 }}

+

{{ author }}

+
+ +
+ {% include 'layout.global.js.html' %} + + \ No newline at end of file diff --git a/src/templates/default/frontend/layout.global.css.html b/src/templates/default/frontend/layout.global.css.html new file mode 100644 index 0000000..74a888c --- /dev/null +++ b/src/templates/default/frontend/layout.global.css.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/templates/default/frontend/layout.global.js.html b/src/templates/default/frontend/layout.global.js.html new file mode 100644 index 0000000..9878634 --- /dev/null +++ b/src/templates/default/frontend/layout.global.js.html @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/templates/default/frontend/layout.global.meta.html b/src/templates/default/frontend/layout.global.meta.html new file mode 100644 index 0000000..9540030 --- /dev/null +++ b/src/templates/default/frontend/layout.global.meta.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/templates/default/frontend/loop.html b/src/templates/default/frontend/loop.html new file mode 100644 index 0000000..8db3665 --- /dev/null +++ b/src/templates/default/frontend/loop.html @@ -0,0 +1,67 @@ + + + + {% include 'layout.global.meta.html' %} + + + + + iSlim3 with Bootstrap 4 + {% include 'layout.global.css.html' %} + + + + + +
+ +
+

Example loop data from database

+
+ {% if items is iterable %} + + + + + + + + + + + {% for d in items %} + + + + + + + {% endfor %} + +
#UsernameCreatedStatus
{{ loop.index }}{{ d.username }}{{ d.created }}{{ d.status }}
+ {% else %} + {# items is probably a string #} +

{{ items }}!

+ {% endif %} +
+ +
+ {% include 'layout.global.js.html' %} + + \ No newline at end of file diff --git a/src/templates/default/frontend/user.html b/src/templates/default/frontend/user.html new file mode 100644 index 0000000..bcd8bc0 --- /dev/null +++ b/src/templates/default/frontend/user.html @@ -0,0 +1,41 @@ + + + + {% include 'layout.global.meta.html' %} + + + + + iSlim3 with Bootstrap 4 + {% include 'layout.global.css.html' %} + + + + + +
+ +
+

Hello {{ username }}

+
+ +
+ {% include 'layout.global.js.html' %} + + \ No newline at end of file diff --git a/src/templates/defaultPHP/frontend/404.html b/src/templates/defaultPHP/frontend/404.html new file mode 100644 index 0000000..51397fc --- /dev/null +++ b/src/templates/defaultPHP/frontend/404.html @@ -0,0 +1,43 @@ + + + + + + + + + 404 - iSlim3 with Bootstrap 4 + + + + + + +
+ +
+

+

+

Go to homepage

+
+ +
+ + + \ No newline at end of file diff --git a/src/templates/defaultPHP/frontend/index.html b/src/templates/defaultPHP/frontend/index.html new file mode 100644 index 0000000..b869224 --- /dev/null +++ b/src/templates/defaultPHP/frontend/index.html @@ -0,0 +1,43 @@ + + + + + + + + + iSlim3 with Bootstrap 4 + + + + + + +
+ +
+

+


+

+
+ +
+ + + \ No newline at end of file diff --git a/src/templates/defaultPHP/frontend/layout.global.css.html b/src/templates/defaultPHP/frontend/layout.global.css.html new file mode 100644 index 0000000..d0f8097 --- /dev/null +++ b/src/templates/defaultPHP/frontend/layout.global.css.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/templates/defaultPHP/frontend/layout.global.js.html b/src/templates/defaultPHP/frontend/layout.global.js.html new file mode 100644 index 0000000..5a6be5d --- /dev/null +++ b/src/templates/defaultPHP/frontend/layout.global.js.html @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/templates/defaultPHP/frontend/layout.global.meta.html b/src/templates/defaultPHP/frontend/layout.global.meta.html new file mode 100644 index 0000000..9540030 --- /dev/null +++ b/src/templates/defaultPHP/frontend/layout.global.meta.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/templates/defaultPHP/frontend/loop.html b/src/templates/defaultPHP/frontend/loop.html new file mode 100644 index 0000000..9050a67 --- /dev/null +++ b/src/templates/defaultPHP/frontend/loop.html @@ -0,0 +1,67 @@ + + + + + + + + + iSlim3 with Bootstrap 4 + + + + + + +
+ +
+

Example loop data from database

+
+ + + + + + + + + + + + + + + + + + + + +
#UsernameCreatedStatus
+ +

+ +
+ +
+ + + \ No newline at end of file diff --git a/src/templates/defaultPHP/frontend/user.html b/src/templates/defaultPHP/frontend/user.html new file mode 100644 index 0000000..d192253 --- /dev/null +++ b/src/templates/defaultPHP/frontend/user.html @@ -0,0 +1,41 @@ + + + + + + + + + iSlim3 with Bootstrap 4 + + + + + + +
+ +
+

Hello

+
+ +
+ + + \ No newline at end of file diff --git a/src/vendor/autoload.php b/src/vendor/autoload.php new file mode 100644 index 0000000..ecac66b --- /dev/null +++ b/src/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/src/vendor/composer/LICENSE b/src/vendor/composer/LICENSE new file mode 100644 index 0000000..1a28124 --- /dev/null +++ b/src/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) 2016 Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/src/vendor/composer/autoload_classmap.php b/src/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/src/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + $vendorDir . '/nikic/fast-route/src/functions.php', +); diff --git a/src/vendor/composer/autoload_namespaces.php b/src/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..213236c --- /dev/null +++ b/src/vendor/composer/autoload_namespaces.php @@ -0,0 +1,11 @@ + array($vendorDir . '/twig/twig/lib'), + 'Pimple' => array($vendorDir . '/pimple/pimple/src'), +); diff --git a/src/vendor/composer/autoload_psr4.php b/src/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..1335182 --- /dev/null +++ b/src/vendor/composer/autoload_psr4.php @@ -0,0 +1,16 @@ + array($vendorDir . '/slim/php-view/src', $vendorDir . '/slim/twig-view/src'), + 'Slim\\' => array($vendorDir . '/slim/slim/Slim'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), + 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), + 'Interop\\Container\\' => array($vendorDir . '/container-interop/container-interop/src/Interop/Container'), + 'FastRoute\\' => array($vendorDir . '/nikic/fast-route/src'), +); diff --git a/src/vendor/composer/autoload_real.php b/src/vendor/composer/autoload_real.php new file mode 100644 index 0000000..07c5181 --- /dev/null +++ b/src/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION'); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInita5f2f7c7d6862663f0e67d96fb654d82::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\ComposerStaticInita5f2f7c7d6862663f0e67d96fb654d82::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequirea5f2f7c7d6862663f0e67d96fb654d82($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequirea5f2f7c7d6862663f0e67d96fb654d82($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/src/vendor/composer/autoload_static.php b/src/vendor/composer/autoload_static.php new file mode 100644 index 0000000..5cda9c9 --- /dev/null +++ b/src/vendor/composer/autoload_static.php @@ -0,0 +1,96 @@ + __DIR__ . '/..' . '/nikic/fast-route/src/functions.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Slim\\Views\\' => 11, + 'Slim\\' => 5, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + 'Psr\\Http\\Message\\' => 17, + ), + 'M' => + array ( + 'Monolog\\' => 8, + ), + 'I' => + array ( + 'Interop\\Container\\' => 18, + ), + 'F' => + array ( + 'FastRoute\\' => 10, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Slim\\Views\\' => + array ( + 0 => __DIR__ . '/..' . '/slim/php-view/src', + 1 => __DIR__ . '/..' . '/slim/twig-view/src', + ), + 'Slim\\' => + array ( + 0 => __DIR__ . '/..' . '/slim/slim/Slim', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Monolog\\' => + array ( + 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog', + ), + 'Interop\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container', + ), + 'FastRoute\\' => + array ( + 0 => __DIR__ . '/..' . '/nikic/fast-route/src', + ), + ); + + public static $prefixesPsr0 = array ( + 'T' => + array ( + 'Twig_' => + array ( + 0 => __DIR__ . '/..' . '/twig/twig/lib', + ), + ), + 'P' => + array ( + 'Pimple' => + array ( + 0 => __DIR__ . '/..' . '/pimple/pimple/src', + ), + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInita5f2f7c7d6862663f0e67d96fb654d82::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInita5f2f7c7d6862663f0e67d96fb654d82::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInita5f2f7c7d6862663f0e67d96fb654d82::$prefixesPsr0; + + }, null, ClassLoader::class); + } +} diff --git a/src/vendor/composer/installed.json b/src/vendor/composer/installed.json new file mode 100644 index 0000000..ba1cd40 --- /dev/null +++ b/src/vendor/composer/installed.json @@ -0,0 +1,543 @@ +[ + { + "name": "container-interop/container-interop", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/fc08354828f8fd3245f77a66b9e23a6bca48297e", + "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e", + "shasum": "" + }, + "time": "2014-12-30 15:22:37", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)" + }, + { + "name": "nikic/fast-route", + "version": "v1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "8ea928195fa9b907f0d6e48312d323c1a13cc2af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/8ea928195fa9b907f0d6e48312d323c1a13cc2af", + "reference": "8ea928195fa9b907f0d6e48312d323c1a13cc2af", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "time": "2016-06-12 19:08:51", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "FastRoute\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ] + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06 14:39:51", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ] + }, + { + "name": "pimple/pimple", + "version": "v3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2015-09-11 15:10:35", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ] + }, + { + "name": "slim/slim", + "version": "3.6.0", + "version_normalized": "3.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim.git", + "reference": "a685fe91a9435e1432e8eeb7cf516e2f5cee7f64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/a685fe91a9435e1432e8eeb7cf516e2f5cee7f64", + "reference": "a685fe91a9435e1432e8eeb7cf516e2f5cee7f64", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.1", + "nikic/fast-route": "^1.0", + "php": ">=5.5.0", + "pimple/pimple": "^3.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "squizlabs/php_codesniffer": "^2.5" + }, + "time": "2016-11-20 20:48:49", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + } + ], + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "http://slimframework.com", + "keywords": [ + "api", + "framework", + "micro", + "router" + ] + }, + { + "name": "psr/log", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-10-10 12:19:37", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "name": "monolog/monolog", + "version": "1.22.0", + "version_normalized": "1.22.0.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "bad29cb8d18ab0315e6c477751418a82c850d558" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558", + "reference": "bad29cb8d18ab0315e6c477751418a82c850d558", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "~5.3" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "time": "2016-11-26 00:15:39", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ] + }, + { + "name": "slim/php-view", + "version": "2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/PHP-View.git", + "reference": "122ed121a8d9cf91a94020814d2a3ee6c836754f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/PHP-View/zipball/122ed121a8d9cf91a94020814d2a3ee6c836754f", + "reference": "122ed121a8d9cf91a94020814d2a3ee6c836754f", + "shasum": "" + }, + "require": { + "psr/http-message": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8", + "slim/slim": "^3.0" + }, + "time": "2016-10-11 07:43:08", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Slim\\Views\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Glenn Eggleton", + "email": "geggleto@gmail.com" + } + ], + "description": "Render PHP view scripts into a PSR-7 Response object.", + "keywords": [ + "framework", + "php", + "phtml", + "renderer", + "slim", + "template", + "view" + ] + }, + { + "name": "twig/twig", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "74f723e542368ca2080b252740be5f1113ebb898" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/74f723e542368ca2080b252740be5f1113ebb898", + "reference": "74f723e542368ca2080b252740be5f1113ebb898", + "shasum": "" + }, + "require": { + "php": ">=5.2.7" + }, + "require-dev": { + "symfony/debug": "~2.7", + "symfony/phpunit-bridge": "~3.2@dev" + }, + "time": "2016-12-13 17:28:18", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.29-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Twig_": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "http://twig.sensiolabs.org/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "http://twig.sensiolabs.org", + "keywords": [ + "templating" + ] + }, + { + "name": "slim/twig-view", + "version": "2.1.1", + "version_normalized": "2.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Twig-View.git", + "reference": "16fded26a44b8e8e0e041f1cff32afa21daeb284" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Twig-View/zipball/16fded26a44b8e8e0e041f1cff32afa21daeb284", + "reference": "16fded26a44b8e8e0e041f1cff32afa21daeb284", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "psr/http-message": "^1.0", + "twig/twig": "^1.18" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.0" + }, + "time": "2016-03-13 20:58:41", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Slim\\Views\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "description": "Slim Framework 3 view helper built on top of the Twig templating component", + "homepage": "http://slimframework.com", + "keywords": [ + "framework", + "slim", + "template", + "twig", + "view" + ] + } +] diff --git a/src/vendor/container-interop/container-interop/.gitignore b/src/vendor/container-interop/container-interop/.gitignore new file mode 100644 index 0000000..b2395aa --- /dev/null +++ b/src/vendor/container-interop/container-interop/.gitignore @@ -0,0 +1,3 @@ +composer.lock +composer.phar +/vendor/ diff --git a/src/vendor/container-interop/container-interop/LICENSE b/src/vendor/container-interop/container-interop/LICENSE new file mode 100644 index 0000000..7671d90 --- /dev/null +++ b/src/vendor/container-interop/container-interop/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 container-interop + +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/src/vendor/container-interop/container-interop/README.md b/src/vendor/container-interop/container-interop/README.md new file mode 100644 index 0000000..ec434d0 --- /dev/null +++ b/src/vendor/container-interop/container-interop/README.md @@ -0,0 +1,85 @@ +# Container Interoperability + +[![Latest Stable Version](https://poser.pugx.org/container-interop/container-interop/v/stable.png)](https://packagist.org/packages/container-interop/container-interop) + +*container-interop* tries to identify and standardize features in *container* objects (service locators, +dependency injection containers, etc.) to achieve interopererability. + +Through discussions and trials, we try to create a standard, made of common interfaces but also recommendations. + +If PHP projects that provide container implementations begin to adopt these common standards, then PHP +applications and projects that use containers can depend on the common interfaces instead of specific +implementations. This facilitates a high-level of interoperability and flexibility that allows users to consume +*any* container implementation that can be adapted to these interfaces. + +The work done in this project is not officially endorsed by the [PHP-FIG](http://www.php-fig.org/), but it is being +worked on by members of PHP-FIG and other good developers. We adhere to the spirit and ideals of PHP-FIG, and hope +this project will pave the way for one or more future PSRs. + + +## Installation + +You can install this package through Composer: + +```json +{ + "require": { + "container-interop/container-interop": "~1.0" + } +} +``` + +The packages adheres to the [SemVer](http://semver.org/) specification, and there will be full backward compatibility +between minor versions. + +## Standards + +### Available + +- [`ContainerInterface`](src/Interop/Container/ContainerInterface.php). +[Description](docs/ContainerInterface.md) [Meta Document](docs/ContainerInterface-meta.md). +Describes the interface of a container that exposes methods to read its entries. +- [*Delegate lookup feature*](docs/Delegate-lookup.md). +[Meta Document](docs/Delegate-lookup-meta.md). +Describes the ability for a container to delegate the lookup of its dependencies to a third-party container. This +feature lets several containers work together in a single application. + +### Proposed + +View open [request for comments](https://github.com/container-interop/container-interop/labels/RFC) + +## Compatible projects + +### Projects implementing `ContainerInterface` + +- [Acclimate](https://github.com/jeremeamia/acclimate-container) +- [dcp-di](https://github.com/estelsmith/dcp-di) +- [Mouf](http://mouf-php.com) +- [Njasm Container](https://github.com/njasm/container) +- [PHP-DI](http://php-di.org) +- [PimpleInterop](https://github.com/moufmouf/pimple-interop) +- [XStatic](https://github.com/jeremeamia/xstatic) + +### Projects implementing the *delegate lookup* feature + +- [Mouf](http://mouf-php.com) +- [PHP-DI](http://php-di.org) +- [PimpleInterop](https://github.com/moufmouf/pimple-interop) + +## Workflow + +Everyone is welcome to join and contribute. + +The general workflow looks like this: + +1. Someone opens a discussion (GitHub issue) to suggest an interface +1. Feedback is gathered +1. The interface is added to a development branch +1. We release alpha versions so that the interface can be experimented with +1. Discussions and edits ensue until the interface is deemed stable by a general consensus +1. A new minor version of the package is released + +We try to not break BC by creating new interfaces instead of editing existing ones. + +While we currently work on interfaces, we are open to anything that might help towards interoperability, may that +be code, best practices, etc. diff --git a/src/vendor/container-interop/container-interop/composer.json b/src/vendor/container-interop/container-interop/composer.json new file mode 100644 index 0000000..84f3875 --- /dev/null +++ b/src/vendor/container-interop/container-interop/composer.json @@ -0,0 +1,11 @@ +{ + "name": "container-interop/container-interop", + "type": "library", + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "license": "MIT", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + } +} diff --git a/src/vendor/container-interop/container-interop/docs/ContainerInterface-meta.md b/src/vendor/container-interop/container-interop/docs/ContainerInterface-meta.md new file mode 100644 index 0000000..90711c9 --- /dev/null +++ b/src/vendor/container-interop/container-interop/docs/ContainerInterface-meta.md @@ -0,0 +1,114 @@ +# ContainerInterface Meta Document + +## Introduction + +This document describes the process and discussions that lead to the `ContainerInterface`. +Its goal is to explain the reasons behind each decision. + +## Goal + +The goal set by `ContainerInterface` is to standardize how frameworks and libraries make use of a +container to obtain objects and parameters. + +By standardizing such a behavior, frameworks and libraries using the `ContainerInterface` +could work with any compatible container. +That would allow end users to choose their own container based on their own preferences. + +It is important to distinguish the two usages of a container: + +- configuring entries +- fetching entries + +Most of the time, those two sides are not used by the same party. +While it is often end users who tend to configure entries, it is generally the framework that fetch +entries to build the application. + +This is why this interface focuses only on how entries can be fetched from a container. + +## Interface name + +The interface name has been thoroughly discussed and was decided by a vote. + +The list of options considered with their respective votes are: + +- `ContainerInterface`: +8 +- `ProviderInterface`: +2 +- `LocatorInterface`: 0 +- `ReadableContainerInterface`: -5 +- `ServiceLocatorInterface`: -6 +- `ObjectFactory`: -6 +- `ObjectStore`: -8 +- `ConsumerInterface`: -9 + +[Full results of the vote](https://github.com/container-interop/container-interop/wiki/%231-interface-name:-Vote) + +The complete discussion can be read in [the issue #1](https://github.com/container-interop/container-interop/issues/1). + +## Interface methods + +The choice of which methods the interface would contain was made after a statistical analysis of existing containers. +The results of this analysis are available [in this document](https://gist.github.com/mnapoli/6159681). + +The summary of the analysis showed that: + +- all containers offer a method to get an entry by its id +- a large majority name such method `get()` +- for all containers, the `get()` method has 1 mandatory parameter of type string +- some containers have an optional additional argument for `get()`, but it doesn't same the same purpose between containers +- a large majority of the containers offer a method to test if it can return an entry by its id +- a majority name such method `has()` +- for all containers offering `has()`, the method has exactly 1 parameter of type string +- a large majority of the containers throw an exception rather than returning null when an entry is not found in `get()` +- a large majority of the containers don't implement `ArrayAccess` + +The question of whether to include methods to define entries has been discussed in +[issue #1](https://github.com/container-interop/container-interop/issues/1). +It has been judged that such methods do not belong in the interface described here because it is out of its scope +(see the "Goal" section). + +As a result, the `ContainerInterface` contains two methods: + +- `get()`, returning anything, with one mandatory string parameter. Should throw an exception if the entry is not found. +- `has()`, returning a boolean, with one mandatory string parameter. + +### Number of parameters in `get()` method + +While `ContainerInterface` only defines one mandatory parameter in `get()`, it is not incompatible with +existing containers that have additional optional parameters. PHP allows an implementation to offer more parameters +as long as they are optional, because the implementation *does* satisfy the interface. + +This issue has been discussed in [issue #6](https://github.com/container-interop/container-interop/issues/6). + +### Type of the `$id` parameter + +The type of the `$id` parameter in `get()` and `has()` has been discussed in +[issue #6](https://github.com/container-interop/container-interop/issues/6). +While `string` is used in all the containers that were analyzed, it was suggested that allowing +anything (such as objects) could allow containers to offer a more advanced query API. + +An example given was to use the container as an object builder. The `$id` parameter would then be an +object that would describe how to create an instance. + +The conclusion of the discussion was that this was beyond the scope of getting entries from a container without +knowing how the container provided them, and it was more fit for a factory. + +## Contributors + +Are listed here all people that contributed in the discussions or votes, by alphabetical order: + +- [Amy Stephen](https://github.com/AmyStephen) +- [David Négrier](https://github.com/moufmouf) +- [Don Gilbert](https://github.com/dongilbert) +- [Jason Judge](https://github.com/judgej) +- [Jeremy Lindblom](https://github.com/jeremeamia) +- [Marco Pivetta](https://github.com/Ocramius) +- [Matthieu Napoli](https://github.com/mnapoli) +- [Paul M. Jones](https://github.com/pmjones) +- [Stephan Hochdörfer](https://github.com/shochdoerfer) +- [Taylor Otwell](https://github.com/taylorotwell) + +## Relevant links + +- [`ContainerInterface.php`](https://github.com/container-interop/container-interop/blob/master/src/Interop/Container/ContainerInterface.php) +- [List of all issues](https://github.com/container-interop/container-interop/issues?labels=ContainerInterface&milestone=&page=1&state=closed) +- [Vote for the interface name](https://github.com/container-interop/container-interop/wiki/%231-interface-name:-Vote) diff --git a/src/vendor/container-interop/container-interop/docs/ContainerInterface.md b/src/vendor/container-interop/container-interop/docs/ContainerInterface.md new file mode 100644 index 0000000..9f60967 --- /dev/null +++ b/src/vendor/container-interop/container-interop/docs/ContainerInterface.md @@ -0,0 +1,153 @@ +Container interface +=================== + +This document describes a common interface for dependency injection containers. + +The goal set by `ContainerInterface` is to standardize how frameworks and libraries make use of a +container to obtain objects and parameters (called *entries* in the rest of this document). + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in [RFC 2119][]. + +The word `implementor` in this document is to be interpreted as someone +implementing the `ContainerInterface` in a depency injection-related library or framework. +Users of dependency injections containers (DIC) are refered to as `user`. + +[RFC 2119]: http://tools.ietf.org/html/rfc2119 + +1. Specification +----------------- + +### 1.1 Basics + +- The `Interop\Container\ContainerInterface` exposes two methods : `get` and `has`. + +- `get` takes one mandatory parameter: an entry identifier. It MUST be a string. + A call to `get` can return anything (a *mixed* value), or throws an exception if the identifier + is not known to the container. Two successive calls to `get` with the same + identifier SHOULD return the same value. However, depending on the `implementor` + design and/or `user` configuration, different values might be returned, so + `user` SHOULD NOT rely on getting the same value on 2 successive calls. + While `ContainerInterface` only defines one mandatory parameter in `get()`, implementations + MAY accept additional optional parameters. + +- `has` takes one unique parameter: an entry identifier. It MUST return `true` + if an entry identifier is known to the container and `false` if it is not. + +### 1.2 Exceptions + +Exceptions directly thrown by the container MUST implement the +[`Interop\Container\Exception\ContainerException`](../src/Interop/Container/Exception/ContainerException.php). + +A call to the `get` method with a non-existing id should throw a +[`Interop\Container\Exception\NotFoundException`](../src/Interop/Container/Exception/NotFoundException.php). + +### 1.3 Additional features + +This section describes additional features that MAY be added to a container. Containers are not +required to implement these features to respect the ContainerInterface. + +#### 1.3.1 Delegate lookup feature + +The goal of the *delegate lookup* feature is to allow several containers to share entries. +Containers implementing this feature can perform dependency lookups in other containers. + +Containers implementing this feature will offer a greater lever of interoperability +with other containers. Implementation of this feature is therefore RECOMMENDED. + +A container implementing this feature: + +- MUST implement the `ContainerInterface` +- MUST provide a way to register a delegate container (using a constructor parameter, or a setter, + or any possible way). The delegate container MUST implement the `ContainerInterface`. + +When a container is configured to use a delegate container for dependencies: + +- Calls to the `get` method should only return an entry if the entry is part of the container. + If the entry is not part of the container, an exception should be thrown + (as requested by the `ContainerInterface`). +- Calls to the `has` method should only return `true` if the entry is part of the container. + If the entry is not part of the container, `false` should be returned. +- If the fetched entry has dependencies, **instead** of performing + the dependency lookup in the container, the lookup is performed on the *delegate container*. + +Important! By default, the lookup SHOULD be performed on the delegate container **only**, not on the container itself. + +It is however allowed for containers to provide exception cases for special entries, and a way to lookup +into the same container (or another container) instead of the delegate container. + +2. Package +---------- + +The interfaces and classes described as well as relevant exception are provided as part of the +[container-interop/container-interop](https://packagist.org/packages/container-interop/container-interop) package. + +3. `Interop\Container\ContainerInterface` +----------------------------------------- + +```php +setParentContainer($this); + } + } + ... + } +} + +``` + +**Cons:** + +Cons have been extensively discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-51721777). +Basically, forcing a setter into an interface is a bad idea. Setters are similar to constructor arguments, +and it's a bad idea to standardize a constructor: how the delegate container is configured into a container is an implementation detail. This outweights the benefits of the interface. + +### 4.4 Alternative: no exception case for delegate lookups + +Originally, the proposed wording for delegate lookup calls was: + +> Important! The lookup MUST be performed on the delegate container **only**, not on the container itself. + +This was later replaced by: + +> Important! By default, the lookup SHOULD be performed on the delegate container **only**, not on the container itself. +> +> It is however allowed for containers to provide exception cases for special entries, and a way to lookup +> into the same container (or another container) instead of the delegate container. + +Exception cases have been allowed to avoid breaking dependencies with some services that must be provided +by the container (on @njasm proposal). This was proposed here: https://github.com/container-interop/container-interop/pull/20#issuecomment-56597235 + +### 4.5 Alternative: having one of the containers act as the composite container + +In real-life scenarios, we usually have a big framework (Symfony 2, Zend Framework 2, etc...) and we want to +add another DI container to this container. Most of the time, the "big" framework will be responsible for +creating the controller's instances, using it's own DI container. Until *container-interop* is fully adopted, +the "big" framework will not be aware of the existence of a composite container that it should use instead +of its own container. + +For this real-life use cases, @mnapoli and @moufmouf proposed to extend the "big" framework's DI container +to make it act as a composite container. + +This has been discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-40367194) +and [here](http://mouf-php.com/container-interop-whats-next#solution4). + +This was implemented in Symfony 2 using: + +- [interop.symfony.di](https://github.com/thecodingmachine/interop.symfony.di/tree/v0.1.0) +- [framework interop](https://github.com/mnapoli/framework-interop/) + +This was implemented in Silex using: + +- [interop.silex.di](https://github.com/thecodingmachine/interop.silex.di) + +Having a container act as the composite container is not part of the delegate lookup standard because it is +simply a temporary design pattern used to make existing frameworks that do not support yet ContainerInterop +play nice with other DI containers. + + +5. Implementations +------------------ + +The following projects already implement the delegate lookup feature: + +- [Mouf](http://mouf-php.com), through the [`setDelegateLookupContainer` method](https://github.com/thecodingmachine/mouf/blob/2.0/src/Mouf/MoufManager.php#L2120) +- [PHP-DI](http://php-di.org/), through the [`$wrapperContainer` parameter of the constructor](https://github.com/mnapoli/PHP-DI/blob/master/src/DI/Container.php#L72) +- [pimple-interop](https://github.com/moufmouf/pimple-interop), through the [`$container` parameter of the constructor](https://github.com/moufmouf/pimple-interop/blob/master/src/Interop/Container/Pimple/PimpleInterop.php#L62) + +6. People +--------- + +Are listed here all people that contributed in the discussions, by alphabetical order: + +- [Alexandru Pătrănescu](https://github.com/drealecs) +- [Ben Peachey](https://github.com/potherca) +- [David Négrier](https://github.com/moufmouf) +- [Jeremy Lindblom](https://github.com/jeremeamia) +- [Marco Pivetta](https://github.com/Ocramius) +- [Matthieu Napoli](https://github.com/mnapoli) +- [Nelson J Morais](https://github.com/njasm) +- [Phil Sturgeon](https://github.com/philsturgeon) +- [Stephan Hochdörfer](https://github.com/shochdoerfer) + +7. Relevant Links +----------------- + +_**Note:** Order descending chronologically._ + +- [Pull request on the delegate lookup feature](https://github.com/container-interop/container-interop/pull/20) +- [Pull request on the interface idea](https://github.com/container-interop/container-interop/pull/8) +- [Original article exposing the delegate lookup idea along many others](http://mouf-php.com/container-interop-whats-next) + diff --git a/src/vendor/container-interop/container-interop/docs/Delegate-lookup.md b/src/vendor/container-interop/container-interop/docs/Delegate-lookup.md new file mode 100644 index 0000000..04eb3ae --- /dev/null +++ b/src/vendor/container-interop/container-interop/docs/Delegate-lookup.md @@ -0,0 +1,60 @@ +Delegate lookup feature +======================= + +This document describes a standard for dependency injection containers. + +The goal set by the *delegate lookup* feature is to allow several containers to share entries. +Containers implementing this feature can perform dependency lookups in other containers. + +Containers implementing this feature will offer a greater lever of interoperability +with other containers. Implementation of this feature is therefore RECOMMENDED. + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in [RFC 2119][]. + +The word `implementor` in this document is to be interpreted as someone +implementing the delegate lookup feature in a dependency injection-related library or framework. +Users of dependency injections containers (DIC) are refered to as `user`. + +[RFC 2119]: http://tools.ietf.org/html/rfc2119 + +1. Vocabulary +------------- + +In a dependency injection container, the container is used to fetch entries. +Entries can have dependencies on other entries. Usually, these other entries are fetched by the container. + +The *delegate lookup* feature is the ability for a container to fetch dependencies in +another container. In the rest of the document, the word "container" will reference the container +implemented by the implementor. The word "delegate container" will reference the container we are +fetching the dependencies from. + +2. Specification +---------------- + +A container implementing the *delegate lookup* feature: + +- MUST implement the [`ContainerInterface`](ContainerInterface.md) +- MUST provide a way to register a delegate container (using a constructor parameter, or a setter, + or any possible way). The delegate container MUST implement the [`ContainerInterface`](ContainerInterface.md). + +When a container is configured to use a delegate container for dependencies: + +- Calls to the `get` method should only return an entry if the entry is part of the container. + If the entry is not part of the container, an exception should be thrown + (as requested by the [`ContainerInterface`](ContainerInterface.md)). +- Calls to the `has` method should only return `true` if the entry is part of the container. + If the entry is not part of the container, `false` should be returned. +- If the fetched entry has dependencies, **instead** of performing + the dependency lookup in the container, the lookup is performed on the *delegate container*. + +Important: By default, the dependency lookups SHOULD be performed on the delegate container **only**, not on the container itself. + +It is however allowed for containers to provide exception cases for special entries, and a way to lookup +into the same container (or another container) instead of the delegate container. + +3. Package / Interface +---------------------- + +This feature is not tied to any code, interface or package. diff --git a/src/vendor/container-interop/container-interop/docs/images/interoperating_containers.png b/src/vendor/container-interop/container-interop/docs/images/interoperating_containers.png new file mode 100644 index 0000000000000000000000000000000000000000..9c672e16c72a08708ea5bdeb51757c2951d3ee66 GIT binary patch literal 35971 zcmbSybyQSe)Ha~B#L&_)bW0DVbV*5fgOniM-5m-_2`CIDB_Z7*AR-OY(%mKSp837& zUElwoYq5lxbI(0zpS_>`>}TH_qpm8CgGG*ogoK2ns34<>goI)UexMj=;1kyB18eZ# zV>c;9Ee!C_AHymd{Eg|Npznr+gxiDoL4L=MOM!$$i=-&?Ld!dQZ^6r7OM6lBXf#J6 zO~scv;{C5se%8#3_r_Nc^0to?;hjnTRvn0g}1g$d6&BaEty%YORqNr4@H*z5~1PvwDk1!1zh=> zFeJn;>U~ym(b3UxY~ES$C;g6;N?HLb*RkhEq+4b)7ZAsk4jHY{0Gy&e0%&pm0;<7>gHumv!y1PRqXY$jH*si=eVH$aQ3=qPV?%rWu(OtW==`lcC?C2Ugh%6}VK=-q`3e(2l6N zyG?=l^2^;{pegLD(08S!&WS&sAxYZ(=L`Rt!OB!5eXK(zf#q1h@Z zNp=JOL^ zoC&6=-q3#u6zcVdw(0xxQ9u6t;hR#f`*LB0+@qfwH>FZty(M4)E%3dJTy%_NWg;Xj7DnT3|T7X`GCT!!X6enmuiS!i`u{_8-VASKP;=)p{< znZZ-m;4M}An^zm<^fy7s{EV}q{`N5vJr?C2u$^G9jg3^{boXqcm2$;6ovVW2@Zeyy zg5rzSWjdbT-WL=kQ#dJT8R4B_DLmd)OBbuz3oP>uc-FU<`Ho8xdhG1h;^ucns@4#3 zjbw`&*$L_N!=^;%6pF|W<1M%)X);68^kTGJqOBU8{ zd0Oi$jHUC)-$CLK^1|DWcR!S<3(Qd0amVwFa0r`8dj)6IWO)*Ozr$53*_kSeyZV6Wf zIj_+J@Bb3Er%_D6sL)J(h|{C@nE=cgrGu{6)`=z0;qh-bUXK5vQIC@p0!Hq&Ed9Bem~7&-;Sl>$4Dc&6C$XooyAs~h{*(t{=ncc zZMMIbM8`pg99H=;?uG6@0wyNC31oEL@{YtW53k)Y^Wopzhl6&V>|SQGg~Dq=6k|Q3 zE+*^`GD->@FRHx^`I*(f-4jA=YPR`%+PqA{SY2}S`(_I70fB;;dny_5mKL-gv~F0*5xD`xm~oWr6&MJX;v zuhhgWz_LVuBV>gI>9`Rre_e7p3!HVp$UXl%52Cwo;|-iacfMyi&tyB{@M2{OKDllD zXekoE`sk(@a9>G@TQACo74-IkzhIIJX9y&M1)P@$Z_;>-I88t_)x&krQMNh#Z}{00 z?;KV5lSt>pM35KA(>M#b+~4l%I2xREJh(+=Dj?mvw8Vw5)pdKy;;M zmS=Nz6EfaV`I`=c$`3#(XYc4oW%YG0W7Xu;&E3m4f|kQHBo+fLJ}NaVEI!$)<-S{L z!qpzw;r(IJf31cC&X+LMRJ36H1;M>maqu)@4iZQI1O2}hCX+UArh-K>Ecd$RA-Tz) zp1aHBFnCQiT&6%&$J<T(+>2V&-Vc*hOyY8A6=7`cakar zXS@gTEL+wi*%2WbrpWI$+y~!Sn^5a42GNiY4H3u3v$%i^x&v*MOYEvLHAVlXCuKYA zgw6Xf(vTwNNR}~`pUiuobopttCngYcx*^CH>7ogS%D{l7LMaE`k#UZCl(*nd*g}mL zcznd5KrN`UogCd1KEKl873A}zU&t|ddjG;nje-x2@BUJ8i-DdfK9QvgIMI$3NE*Mg zMt->jh+w&bY1kk9AhBNiq0?)}PNN@1u-m<_Aopm|sw+J78qC;nacioPzK;`3lAfxG zMfB`BDscY%Is)PdH{gy~j4LB_41$VCl``&&Pau%YyJ5HJQF2=4MnP^LWNrDAfA5Rv zcMu^08AIijkujTF?X7`vHgty^QU_7uk_;098^A~%wBP-v!$)n|I(wqhcquW9?+fye zmpt>A>IJ!>gCt}6dP+)Fd-aCLcMtb35uUObtY3S#v21Zi0s$_@R|+COw)s%T z*qjbG@SL&!e%JboG(DF0+TnN7u~xs^jV$FhJF?&XiD`^xGU7PJfwcD0*3MjQ|JIBH zcPW1Q?z~30em*=pRC+e#(bY|xAJ0|kce3;5C20p97Ak_20T{7)G`u(*gWX(!f1I7s}j`8$;c4d3!gta zSHO)!eRF^E)|(2K|KTx6j*T|kSv+#`?1*fj-olJ-Ux$VnhF;73K?(%-PBoU-LB~!M zO=xQywS5k*Zu`YUGLK#IZ*xdQ%c{7@%-xhBNQ$Bb&z|udE^NSQN-kB*+;BJ(}=sJ@lPS zVHjAdLN;kI?1V=*;Ubvoj*jbx4_o4{^jh|HZ|1*xEr#~5sPnKDd<6>wsAX)}YS=&9 zEK3(VNCJU)G&c^bSdC<(YR45^EgZIuEOKHgB6zO?r0~smVp#30S5B)TQ7OXDQ|LjK z8vt=+Uoq9N-%&HV;tC^mZ z42f#$+b`H8TCe4Z_HPt{k8eEzFq7iC{iTMF@G|g(*P(&sGY^o36M8Wc5LS**JYu!U;F~siaX;b2 za*bheu`=>yT}8(|J9qw0l~^>#dEu-PZB`>oS8O+}CVbzGSK~x@_&_t8Eg?fB3{~vv z5YZjLT6fc}3>~-E2DbVMY$f&ceTIq55ToQ;P0g=O(sp$&T6$RV@CQD~`(z}58pBWd zT(3_bg6W=VZdXOaP&4$3liynHlWS4Jpvv`J-*1!DOT3L<5DoDAiWiCB>xTFCbrI2U zvu846d8*X2eBKMZq?$cnOk>KQhQ|@dMn=QvT?g>LNYm~Ri2W%}PI*{Mf{Xdmu7EIH9iEnW)#b@@(f1^{=WQIyD z3TUBX)PS$94R|@^Pi^!3YY!}RSCE;Aah#kKzUjVn(XVK<>#S4Xbytd#nVpJ zr56yyi^^6p)>s+DP zN-Tc4sID3v~&A!;>5vL|581n&8%#%+7o`^~)= zg0#eX9cscT>35i%a2Rzcg_jLQLRpd#gWg)5+XH|EY$Gt3`=E(1N++-aVKkgMawU#G z-^0K{hyX_Xs!C7)1bYw;F7m+Fwioa z!8|$|;UzoHPuG|SeCvPUQHK|{nRG@+&&d-=MIZRqbVuQ>{mPdf7#s}2NAbHnkacq6 zY~p>4Wsw1MR5n#7Rn&3YUvBibEyFMF$a}&K#4J)L*V&>oTdWYksje%kx1c=VHb< z)lq~Jx8-K1Os~cY^W^mOgV`ABE-7`SA39DrR52Nfsf~+HJv3Y9t|v1w^6|E^Q8)i9 z5d;!)eY&}pB2WE7P7Y106uYcx=j>y}w22>1z|C1_z~w^JT7MGXSb-|<)dcI(NUMR* zIY|Tv0!9p;v4XFwWu zaOEvV%#w<}pLi3`5bkxLp`j6Sd)Q7RwB*mHnkzQDVze2WRv{ZlvT`y1#x_;f*v2K@ zAEWx0eMAEGF9L{$(CuFBC{NJ6KPAi^F5Mlwnd3M)HD&QLTf}=Mf;7`kBZHihl}5^{ z6+k|#^?bckCa)!0z2hv3mnie3_@UKq`~7ZvmHlMtX{)G4v$b5N_4Ee>;-|*O#=0zc zOq-A9itq2Nz$&dA#tLMwz#{0v%_>PeSX-fV^^;Jy@7cT`0X{*)pPQT0Ik%qc{N3El zJH4P%t$)vXDfS2|SDBQ% z1SKwduJ>vTA1-tKuHm+e@X^`0n%h+}2zj}PZsbaQb!7D%*oUHXOaYmN7Z@RxZ8un= zo_oP%392EUjD~xY;B=p_k)WU^=c!SJ) zH>^^xAR>iPZrn_QgM(AE4+iN}81(+{AXJPyyso{APM#w_~uuR-DK_LkX5K+ zg$@t>&NdrR9Hs?qn~!4%nVa~@3iv)`y^lp}py;O{S9h-MUd3%e6?TVFMdHyUo}IbR zI1279wdZvo*rueUgk(84VMgInb=6o8ExW^-*L^&|Kpcul#K3(|N?{kI>+5T}F^4<= zNfd8dAU-}mdTst9d!Eg|=~%lC&I<-?q}z{N;HsQPc^1zK>VHNoKcusg)#WPQgXMZr zehzo3BY%}ffUFpbkHS?+`fxYy{H6N@6YqZk!0)h?)TZ{SX530_UeNthJh3CBsjtS^ zmD7Ij<+pt{M)Q);slAbad(KpuKZ=cuYd*W~>guYp8>9c@HYyJMJ~cq~_F}(V8c#fY zvP}1OB#KaFVA521>Y>j=ZJcBSyW%u@1FaX>s6x^$|v>h&DK^2++JWGc*G_q_K6<0gbi^u zKS8Rp=*8NbtHU;^AW36-*>!cicHBqtX8E@=x2%cDBC5Eq{MNZIhiMz7Wq)EXv93`_ zg<*q{f${;^Ivv+5QGS0z2{k9~PquZT!UetgXnnB6Hc z{{6XJrtpR5op;&bN_v52$x#oXn%R$ZRu(RFFV`HvqzT=wgxE&3Cw zT$&f!Mqi(8jWzCmZC-W}=DNCuk!OwBh07I&m{ew**XJaG8Iu?#)y`s;jFDY#6t%8yFsz0eHEyy9*&EjsQU*RXAog z^q#YZdb6iT#^~)J03zKZ zblmK7uiWJ1V2wgKIwq!#a$cd`rFKaW5on63RbGcCf7jQqsZA?%7}(jdwY9ZbxVh1h zGDmDa7Z-Qi@k@Qtq~qko`S9U`i!db%2ge!3s48nOm5B_Xlwh0EQ-rZixySno3 z?CjuEQzss;^@D_pA|fIp?0W{^JI*pK8?*{FsF1U_XM4uSXWlS>XX2oxMT7`rlUCnO zn_6>lU@UBGCvm!9;e33A0s;ao?CheIjRwjuU-p9K;ggU=wzP-=$L+6d`+6+`60o|V zVcd%_H1nh7PGR>gd|YZV_yE3d!gvf@)FZSyayG?KjS9JPlO+1Aig`=A6NaA!UWbb) zNZc*%yX&2FVLg)lMjR&8q6RHup5F%IdDy85k&b69lQ5eXxC9+%XbTDo<^tMhX7r9X zvff~U55=gH4lNqyd(+e;SV4$eHRSPHM#9U=2uCGu%NuVGnq^*Ca^@^eG zW``YA0d!hy@4VL%gu+K`2u&(=y<1I|?kI7iGDX~q)XOGOf}@j?NDtc|+Dyg3!B~L6 zsG0sZz>pN(*C$u~dYlPDL$j1Wx-I!|om6E|5mU7QCp6QpKdEl49r?-kUg0H`8CW&3>-81B?ICE~V$ks!z5d%nZC7;tGkHZ#NG$I&p)Y2tq@ z+kUtJlDMp_Y=qwTw_kwsq&PVwmVN##bD^jFo&y44KCn`fi%YfT?qa^6x?1Dn=5lYA zp)Y}g*)OU1Wfl_%>z-6bWq=f7boLGo5%c>b0Q3vqEVaen$uTqa#JyIGp&xQ2fhlUX zj4Xe#H8rrP=~eD9&ZQBMdhhsDZQtcxiyc3Cwap0m%G>WfOZOK-05s=3+6M*{j=S+B zF*1f2-@bj@L*?~{?rYNy+i^qt-OGuI35=wEhWq==cI>du<>paP5k~Y{K-SmSyVl>v z5VPwt37t7Vh~qHQTr;Szk7!XZe$X0uC3oi3JwxL3NyHFrq(v8@${!?uePo+BMG<3 zUflHu+gylT;h0xm`@`K~mwjC<@F6__{4b3Bk9$|g^rpomhz#Xstrm6LSlj?1BUF!kq zi3=>q1(;PZe>LG3ci}e^=ClQ_iwU6>F7UNZ97Wsdb!{gSN63@>EC%} z5HWxq5CqYixqhk_62_jh)&)&XbC)6&#?3l^d^U4dXKnL{N#bN1jksKazhYJBup>nS zP>hwQ_K^XorwP8OcHh=SL}Lt~!ZGRTM0AR&2vf)c*5nQD)ms^K=WrZn`cDdxWedK2 z(?U$vjU#kLT`3Y-TFO>!KgkB6pkSY$qMY_x3Sd5H+T}V1)H(L%#uwoNFNGGMx42PJ zQE_kDuI0e_9fL*1Jms^Ac3ta>vCZ|7)8im<{W}2RbySa+?EtTkOx(b#1$2MRn5!KHM(ddS>$S@>(1&wsx(!4EQ#DOydMj1Sl(}e$oUY z+5j5COG+TKv$Jhf+xrbmT;MQ!FZSnUD@|Ihc-6855gr~rH)p`>IKu<%i@3#j(aVDR zdYvaU5=K*x=^ST8*T}5E|_{j$|O2JKS$(ddU5du(gvua#m+=Y zK~0V3#MD%53WNO3{ZzK2KzqZ8xOXb_+IRXrr{Tt>!E~i5HwQ^vCd{MsmmUAfVUT}| zf|62dYZOus$WDylEAQL=hJev=N@?I7dab^7rZwxw-aQDM-Zl2ST8WaCmJZXacl@x| z+5)f>VVJ9br=!-bA>;{iEg)kfAV=(|<562!=!Vr0@Z8HqKee9zer&~5t2`m@ma{Wu zWh04L6s6L^$0gS)GLKbV{f9y~`uh4j7v#$(Ee`&C|7Bn2ecVfGefl-})sP4x_k+9* zXi|h619$!(kl9Uqw;7j0i*(8+g}(FvS`&u=b6w?EAc+BjhGJLxU8Cf$zceYIwxSX&4ynH0R-?WC=QT{mcBBYVud^|DFX{aP~hP5vrPXB$EtmS#l8Z+#>^c7ysjjQZ~%{&7}k5j^f;9 z8RHI9hymr{xo<%4QIQUBZp^HsoPhy3*cdJq>~F&(4_MCC^=2Ng>#EEXG9D!RIs+Z3 zQ=P#bR!>=3*~+;uA|=gNW74VX>Y~#{1kyMP+<+j6{N?-`UPQ64N4ibz zEZ|PqbdPIlYUb*?0YBv-djg0{)%<>-D3tCoI5xWQ=X!cGsA`XX{kx*_$=>)}@m)xv zY*MOnx_j;T+Xt2$)(w#VmVqJqR(s=k1OIueWD7(3lW6*LycT2X9Hws=dG;s&6v8!2 zc-*(tt$>8MQ1s~&lH~mr!=9%ofI{x=;o;$Mg9?dFeoFELG_VHT1P^9))rCEi7lI&VjVWgu6)PLugf1mD^(fzUQrHy6p~5TtWgWl9skM z>)R%OcXxiIt0FgG1DEecz|yvRPwf-G`)t8y0gxquvoZ%l*A)-|Qa*l+1>k@ehK?t3 z_&8q#xn_R95#$ygkR`w-e{kvR>wE82_y3xi!6+V&S!nj^-uJXT`8$Y#hxcaMO*rRg zZQBO$429H*mC+K0!4#;XUW+&H*Y}V0to}(cFcNGs4X>Ya`G-yrc#Z zd=6khJ?rqeFiC=qqK~oQ!V(;6_a{v{oytpNFP~OqzIc}Kj+6DF6&GP1=Rd-L7OZ4< zO9Q}W;pB`ksg(EhtY6xH$wVlZK#r=IdNi&knd5b+<|iX7dyRuED}6-S}`1x@aOd(PRU|A>By@|nVemLyviv>Jz#clE+|R; zVeS9cxdGk5`{V4DF)bw!v@vq%dJ3(y^-YR_uK_`{L*W^@wfrAzFoArh(Utf1HI5S}#q`b|#fCgt19D zI)Hc@z}vjwyyXr<9QI<|d+B1qto`yz(E!0rNvfy1zRNvIL~7CL9#6D~K=xyW#im7|&U`@k0q7Bj!7gPz` zfSQDlZzJgJ%B8qdVt#AHwank7c4{m#USFS4WphijCv<|f^ZQq8G!N11Lm8Hcg75oV zMu4N?pu&7!p<@Yh7AP3xeSE%GUTlI?-%~ej5(iGy}r*; zC%U!mod+u2W9RSdB=Izo2yYbtaOVnkB3(JX1}X_Tef{| zrV0AT3$!6ua9C?6L!eL-H40Sb8jXD(8g6zBXu3EbVCRU!-DQl^AM6FkW`bvnVNNlW9qdBiX$`=AXFlX z(hQ@wm^MGNV+@_YqXPZplFDo#%go`D18^7i`8T@Om8x(n(?`7@#ucf=3mvI|Qu8Fe8o> z#MjC@U6amH$%lB5-V1|3pKrSTK0?jp)`>glNDrluBXlAbyq6R>0qrKVY>eK2|VzSLXA$=Jfe z0zrMYvxvU=$Q?ySq8$U;9#>w@G3zfqI5@ZpnBe=7uLtM8y=szoP@Cb*6`{)i_+mgn zt6sH5grh3sr`JDOK?R!|aG^gd14rt4xLbNal*P{Ncb-c<86&5T6(+57LRsFYr_+E! ziXJpd17WCrIx{<)<3G|7zYhvfpd={^)`}tl8qo_@%$;@qXZJi7(80KB%l?~<_>Y24 zAK{=32BG$4>B$CSZGaaMsusTjr7NPGV$3jFZGK~9lzp`+`LHe2{2kODKr|r)V#8(D z8Y>KDz+uudBNXO4G`bC>3H&fLoYWE9cj@WS_guzoSuYmB?EnJb+IYXYzIs)xil|Fg z%PQMaNAyvj1BUQC{r!8yfT0athrnC`hbfGx?iIfZAo&vk?f`{C|KoudZJU4pMgt8= zl~Uxb3?ew&6fKaWSzi?aaS!YGeA@JOW7gimaHtWef3m8oiH;}p8<*aLsf=wRiw14| zrvAqfWISfUhj$;JS;qo7h7?3cU36UBtA9d+*xVuBuN4VwMO1dji}65@WAx0nK$W<7 znqkoF`QZ_d9Tf9ALFcc#g@%i*J@tvAii(Vp5hVu+Hqg_TkG<#iu7TG9eP9b*Jz+0^ zg^fLG;7A35AU7`@G%)2r6+tEq@3lCl@YE`s<(Ulve~03ES&bd=H1NU0<#t(cFBh{y*M zf>Z$nA1Q0=^{0}|wX;v6y2mzlf#@35zZO!FP{7GhSk4K@q-y1L+ju09VL_BMSt~9l^j!Dv^nA;23=ctOc9wO~wWgjbx>kgg8qMi_` zKSnw%!3NJaz-VykevLZHeU^Jvp%mdAhTP|w3sIgR1|7q5Br&Hv-;bJER0X|%EAEDE z4vrLf#VAr(!kqxTWtavcgeqY=#&Z^C3W}7c=8-pA3ntuY5D@`_7O^8*RKz1x<{)zb zgz?R=9PGL<4tlJ&>$uI1QcE(0kl{K7Oz=ozZ||1Jmi04>=T?uI7?~`*-M=AvGZ^qH z(d8rG0L%;^#f8h2xoAxwo|ftD-O1Wv(Mo+>2hH!>)`CIlu^_!VS(VO!*nN(QaP600 zNL&)pG%7dYZZ;VKZC03K^-Gh6cV<>8VjhxHjw!3d2G`S7KUUOP2Abx9^ zhaTdA%S}rm2h~Jzs8?`>z^-RsoO5MJrQm~SCM1{)KeHr2J7+9h^|JaOWdPCN4}6YF z3Oy*6KLFM{AckAU<=v*+RW;RtJgCeMG(z64Li*1r+=e>II3T3(`m+I))9;hD&AH4 z&&WLv4c!JVC`ikt6}^%-wo;^og}|o2K{>c`>|F&?CZg*Fc!yHdgwsr5IEkSKgpzpO zWqP8+p@G43&K>U2(jYZl_!Nc)nxZcP0|SL3$3>Xba^oE;TS;ndMx=nO zFe{8EJSG-Xf*-8ZJIWr6Xfpx@0+h?#RsPp+`T6--s5FZ$Zy_Idu+%JOX`CavEs(>5 znK+(I2i=IA+0_2dKPMBm7~{FfLZmm9`7%8D^>n7tRGXc(Hj-dI1{&6ldUvr`^AV}sh)Bf0{BLak)I zY3q!JFwCeEcp#Fr*xlJU(Aj$be0R$qdu*$XGM|+DA{CnH_XVYNyt#G9vVVD{lsFdS z-9=$*sKo3|H_d%8n8qAXbEPP$35GAl#cLo*^#f9jW1KwMUUj#nUobwNZ=d2Q@cx~^ zJMLtQ(C>F!(VtZ7O#-j2TTXtkgQ6@M8YBN%2Fvb#e*uV0hynrV`(NA@_PZ_$H)B+k z!#=}Wj{=NW6utfIIz2yOOkETfhSt~s)H0u~vCb55&}k+g47e7U@hTwcA0q!{=T$g% z&~^O#7uPUD6FyTbc`MF`*Tx-ItnotsEHZF-`)kKL?Yy`5JSUNAwW zhhha3KC^T^J-b(k;d?RJcc_jLf*|lf2@CIDQK#*yufE%w)uJY4s`Mi;$QWsGx|@hv z;gMk>(;g)(-csp7zZ;T_ zz;9ej6g>jGr<)jQh^P9F7KyBR+gfIVR0>wNJ1^l(XJ5dlS35ltT5%^#^Fs8zo4v9H zB`kmcV6i*myzy{peipbDVOe+BW(HNq$Aw-Ozkhxqpc}H5B$)^#W7LAv{);fAPrPCz zD8+>AuPjla+;M-K4Xf&CNZIUCE5K~Og=Wom8In&7L`gs=a}J9hmBaX5RCeLyk@ubG zAR!@PHRuom(u7Jcny`;9;6_$J`+2-sqOg@@Z(LKA=NnB99hj(}Ca7g3^7r;dP5u+j z@wPiYIcL+IW_6dhK96%v>uDn1B^=e4Dgg=rYEo{XLlDJ&#hk~ppR$V$vpW{YDB}); z>g5%uo7qXiq}8E@d^W=!fZopkn-^W8k~RNQm&H(k{pF-F|Dp+E_-?aV{ip^e=Ivq4 zmCf~#^S;xp<1p}2v^P<8Hc(wTVFMaX(%i=5z?jAvfeJiIKa_s93G~9|#n>udyI4%B z$zsXMl1d#qmL{Zx2SXq4MgAhoI2^9<;wpQvvH2alM3d}a&xv_ zIPlceQ4-Er+{uxptGrh8;;@Q^3?hKtgP6s?RM_X_uVC?^S5qr+Z>f-IIG^1g!C8-D z38Q=kIA0FSo1Wv}XiroU!b^KUN2Ef{!HmNGS1t{?eIplPbDEhja`H-Sl2<5uMRs~G zdBMX=7%dlte5n4TQ$Q7;qePj>Fzqh^b*A7uAcxmgHrYARss> zGlA-ciI+L?40pwa9M)v^g?zyzyNszdC?$7WD#$d~!N`y1{SPRKZzy! zEbXhu==-0a)u9u!Y&(vD#V(Itx^1|7jiAos6)Y1d?91C?U0Ia@e&poLU0GN6Jts75 zj-AHK>vbc(BOG4t_NhX) z$J(|}YOx(EoBHvUS!RdVNd^n+2YJ=HI_ex*7oGxI1y(@nFuc5>uz*WgBJS(*TR%Fa z-i#OKx2`swe*&|WK`_A*!tVzm3enFxwlC-_8*}&3)89zKUUe`V{vG|Ky`oF_%%yjR zf2)oSuDY-OIQU-;8i#RCRKRteZ4iD$-PhTgr|ZR=YQw5MEtuPIxSQFVKpc+xjwKP3 z?5A;tHr8|!nn_eq?8pB$Z&%J9d$X&FtFnBf*(BiDkJ2das!O{yo9b=-q&*}V=i#Sx z)VCwXKlfe1lf|rt*s2T1UQ9TMCDKZ!lRdZso!zDU8!{{bOZryE(#-zxy{(Dhfsd1@ zeX}cErV!=+W*)MYLzEb`G!sF%o?zSFYA8`s0-liYShk|V%_;Bi?1Bl;>2=X1%S1W2 zj-RQhkfkD>mwY!@_447HE$D!-?e~9wtbEJMD++PrQkAXijp2;?p)tMH^-E%mY*7ep zJd&|+Y@<(fH?eCZto~EK6??P-l5-0OVHjV5y0z=-Al}8YcbwdI_jx}rtpJK{ykgka zMxfg}K<>3)a4P;P9W};+yAEQfQ^9KYZaPC@VmB8D!D`xIm;cTi7?LTSdW1yd)U^*g z-V|ME1O6wOKY=)*Z#1auI{!|Gq-T7q{!C-HSvVj&=ZGWm$w4hGJn3~DIiJ6a5Qywd z9Mr+zP9utpktU#heDpgutR|AcZbw=~OD{<};z=(A$junB}`TLA9hPS*gZ7Ay`T ztusLJ?fsyxu^BwF9rB{AYZ*LyK(p*!4*EXrY5jS3%v~k?l_~oE2tn)j##x46d4Nbm$ zyA|Iza^l#sa$%FX;HmyrdrXj=@=qq*KKEuNlh8zlo}dxw{792Q#*|#wUg^&pN;aJe zX6;U$`5=qTnm(75=|Z)~#r+n0SABd-vd|k?BnoTS1+LyESg4DXh47KPHg>dB$G$I3LkI^l9z!`h`^b^qgz=Ss;)7B*1!h;E>W!z-iB!!ecUyo2e&RZuRK5szkS&)A%E3izJ5^9VIteAot}P zGWSN5$8(SDvzbex-Gk{3(m%^4;f*1S``K74zNWCxg+UP{o-n(}jaCaw;%l z>g#jafgR$Ie>0SPo~f#QBKnqn%w;!jfMN*LcC@e7>+)T!kzW*2EB9qN z#36FEy1@%L*_h?2n-L5O8MC3nC7oJRa25Y%ubmb5nEe`#B zMafkzS$Sc3PC>IgGez}les_gaBGI(%ir}QiqWaqGOToVUQV$QD5%Wu(ApcCL8|U*> z%Nktin_?N-%0}de+fHgd)jJojzd9C6G2~)z_hk{5 znOaXTXf#;tbrQ5c8cVvqa$Lh)GSC(*CY?6=7;+p&ShcUt2u>P+>;QXte`dKbnTNSv zbMs@L-t(G%gZ2g@ia;*umuuSxM|ly!FbUN5oZqAjm+#C02l)2&k@$GYtxX{s%^Tqm zH6h@>T90hpym{JX9lyTfRijJ|9yYOY6xRyL(4jmd9^N3Hmaf#c+^K@bQ;{XT6OFSv zbcolFqPcj#vb40(wbsp2b8WR(8ool;lQtqGHPhILGIo8gjKRZ!cL3LX@bBm4~saKq6XU5 zH~X_#fftO5>LoBiZ7IWQ0UG4`_!0Ok1};s!cUX=7?%XQFs_6^NOh)K+N};o#BMcY^ z?FE$V9qpF?W)J}mG$7D#BhZV?xmxu;gFzLwcP{)kSD3LH#RtC}TT`JPTNi-pH~jq3 z)3XPKYyU3kQsbT|{b?GRZ|yiDf0hc(_ey$i)8b=2LlZTs-!I30B&{9Db}{tZpY=uGqN-WL2g)kw-kMr*W!{>>orak@O)r z@@;CpQ-~8<3AvWWH=kJD_vo*>-+b0?@&e1bd=`t|5bu5W8ZlWqbwtSPl+Bjbh3ywF z3$X#`czS$~EiP2iZ;eKqtZT9Ttq?wi{$T}8fNO^eC!;^kt4%aC^oJaVyIQEFn=i=W z;RcL!bV`SQi&LZp>irkpc?|kz3 zW2JG&@QH|GZeyCWe60uhZl9t4;;kPlB^UREJ!Un;*T*RJaW=W2dIJmnB>dy5w#O>I zg#&eE$CUIsQZRe}mrlo%!(s3ZmU)cS(|NoglR6SX>CfCN1X3L)#w+YDyE_*QQ{~-x zBvuT(bJ3%M#AdS_D5~6q1;X)UGDg%P(`^#rk`L1^pZmyF3k<;z9X(e>41 ztiL3a=Fjb+&bebWcbdb|SmWlvKZtbZT#l?bnQ#<)7Ibm@e(WAR!}x2Q%;)H?l%==W-;OS1%1MH6T#erp-$B`J~;V zGg$*8VI<7%8#zlC3h_k0ld|LMK9pJg93p>^T|a=Bpbk7`9;V0(*=Bf^O!7v`SBea& zW97umSd~>`Aw7vrd0d}^B#Q!(8GutW_2DJ<|51L1#81^r&H1`xerbJWbFHqXP_we3 zB;UiYB{1NxNo6qp%R0)6C>$L}kr?EX6vRe-*7(f4e2&iRrMx3L49k)_;M$h%I+P?P zy!c2iEj^*O5(`O^!#_idwLP{!SlFpK=HlCt7XII}06NtIpa|V56u%lt56pydU}7_! zUl4B=M7i!G9PyYn zZa?bbqOXdqM@vaPuPLFf-6b!+#f^qbuav>dFMF*%Tt-ksy>Iez7v($74z7dBoLOe%_p5x1bU4c=Le zG9VwP_-3Aw18QgPk?l+iMY2glJ5E8HFkApdk#uy?(~k?(E0;#Lu`4Z>k3MNAE5!14 z!Cw_FZpc|zP~{3P)&i;9u=3ime+p&G)t0!`WuELabNKZ+42bmkU%%cd_ZqPPXR*9> z_4T_sP8bkt!Lonz>d;8>rguj2NXc6OW=DlXR=Y1iRv~~CZ28EhL*OlWJd`;`@~2Pd=JzOyu-w z7}C`!9s?3$0}X-$_4D&~?xQ5#KK+qJ`RQrIQ+$g>;-f2=Ph6xG&(mQ zc-A9RS_~xKg@aU@yr@B)xVZs-j5@Siu8(-diw};>K#hHNwW$U%4xl7>yvqKz+Uv^0 zWxLyqPj1MW)TScTo$XYKbq;lK_w3wAD6%)NnLQSc162E_obUOR_DO`i0{!@gAFx4# zUWLBkD*?Dd$@|lZFaE0lqPxVeVShclNJ!YTX0#5gp09h-715K*In;5Foxu57)F@H% zEoe{?uODf~Lmxxwmd)2>WsJ+&1jjS96s2hQdq_SL9Y*SEcIKyDmS>euDscS_`RjU} zTJa(jT8I#TdPqK=$l#a`10nMb4{0mLJ>rF_T?Q~82XIrYe*BmZ6WR|(u^ zUNosmcZmY|FCl{lF;^#H>M`TzX0j!PGi9=y2}_haHWg(re9{$Uj7PuV^|!5@x*XR( zJl}M5{0N=g@Awojlq_n8i**w(-ztJiok0DxUr1`QK(~xe47pBYyDdB(UN}=LyYR!T zEXBB?flZZ>euph#MNwGzue~?_%i0NLyV7+f$$HFlNb53x=E^%Q1&#&mchmdLrmjB} zyeDB11x4x|2vJ+3*6kg5{TiB2@5+Pqj4MJn!1p>obI$)v{6<{Mnc1Q=snf13g}5)H z=t)$D6R|N;pdZ{&Kn-`M+{2s=qM=K`Mx1D)Dk~EJu_!yC^$|}FCoN&F?;s#anki+d z>AFfaVOahh#Y)bHDtY0UQqV%a|NV^&cnvLFDIqWFr*s(WlrB#tfr%B>Jqe~Yx>dw6 zqatZ1d$mp+*As-DD@NtqYi#49iI$&by7JuWy1ahbHtxyqEDBvYKewO!kY*mEon%q7`pPTg(0~873eI?ss(5l?$cRZJb;J#wIl-%9 zn$ghw&(j!0Hu0xfMzbZ@($-yOs+Z4mz}vtc>?PWxB#1Go>Lsq9m$WA2Y(ekmB>aWT zwhESB^81vVBoX4yDV(eR`OwNxaY_|I^u9KvfyV`+^6M5TucAM7kU45(#OLRQgDFOUI!>5tQc8 zAf3_;N{f_qNOw20@!tFHTQhHFy*Dmht^?=W-_C#U-)~r*xRwzvCbZuOd1lh6x9Cz= zZW+l)?iKP#sIB78J3&46C%g*b@%@#4^TZ5#tI?7I8Z7-hGOHi&;W^%vdu}ToDMEw8 zuaLpAKH!IN%#iyATKyH+%~;j*OytM1~C? zB}CIxpDedBV&7|YBa}}3F+iFWs=M-Qv7F^ia~63r7FzA+yk0wGT3VKkr`0*7OGVwX zayl}Wi$7&${r7?ryvp-G4n!J9!uptVu;eKk);fb{&Caa8zVV3yAA)|@pbb-GJG`5s zrL9C`qR%Ono@|m!lFF8tL=**=g1ALP!6lxm?XLvb5|e!LgO*SsUSXF(jKXaiBGEV@ z$3PL>_^0xZ-?*`s7 zjbHCFbXb8%@+GL(SmxOD;nk1sA5T-&dpOlTV)902Yst6hDVLQodm4aWwHiK+7r0N? zS+(gnl=$@k$}*;_wb-hGkA~*JSf_HE@V1DtEi^+>~%Wg_~wp#O-WfX z{%&sW+U5@}TlsV${KqRj2zXF&&4}6{)Bz1~S^ChDf-cb^*1`ob%P0dg%=Z{W9RpM% zXjzQ6UN#@tiE8KFIhxoQ0%uR7djAOBz&-*Afo{LRsXi&?%ON>qs ztgsX~yWJAcs;QIB!K$PLgYE_mhh3+}YdzKqQ6>0Y@-{5_d_j6Yjg8k`ps9q7&Uffb zD*3>N)qLp1i3YjP)(@DEUFGl#?AD6>@ANzTTccz zqf|bBn9+1L>m@NuGc{4^u(@bVD2u2dv){TD8d}yxhCU4D{OsFl@T_KQ+-(Fn{$%CPG(8ah`TsHFRRy2@i+PwPKqO zi-(o2e1`Zhr`XB$z`OnDVoqcuD?t5S!(?RVuqejll60S*BSPhnIFw*FH7BVAxp6{`dSxG(Q z)mR?fv!772`H(cZ&c3^YAzkKp{e{C~A%k>a3=U$4MG%iD#S6b1PJ=LP1V}0g31icm zuo=5Ss+Hj5ecH$DXwTr}sn5Ul$p z62B2dh~dkzGg};piStqDt)3KG!u_SnUFOp-3xDROVEFXjI6A7Z`VM&d3tHfb6xdc}LH3*ZFJxwXg zUd|oQ3dmEbKOFWu3ML^ESe5RD_B3Cmy)twGxYvq%S{*a!*44VB27Emof%AJ&k`qmOv z?|etBL`Wxj2Gd;{X&D{eI5IqXikkr9Fn#NXVpCBrWRf+ta~GohvVL%KV42`%rxux< zpO+XQ$G{l3HpDR16$%ay>9B}TQ^Z2!@Q`)h2CV{A0Fy~}4?X=b-#8qH9LtjzHj1z1 z3?ylChQs!Hc{G@nSAGmsoTi@_24o3}b)C!n!*Eg^PS(2bz<$m!Ye7Z9Poisesr z;!@XF$6FJ>+XvbG%%w4v#nQ4cI#G(tB*U05-&lW_Ttg@VT-#uB5yp{M6+gu+ML${& z_arplk#KolLhpP~&qU8KN)eTLBbs(?;~GM9-jBtLe30w2)w5(Et+SJL|3HzGcX^IB zUK4jMKX_ktUoT#GX~3@eO*i_-LOt21y(^T=?Wx-hI)}+|tSgcB^;8I59zo46)_hH$w6&x$GQ{drAYJl?QLpTifDa)R4RdIhA+oc>@&AY1dLT)hl(;i zZfGRi>j{N%SSU<%{>R&^R>9bXE!lz4Bdd=|Fuq&4CpnvZMp_km`IVBD8q>|d%{{~5 zlQr=&--rSeqa)ln5{1zAI<>^W^$+OX z=&Sy>zT+Es1sow``6V%+qv`+Vbsbn1a5ZX8@GDpkc_>?im*{oscB zz01s%U}SEh4DY?Wz`j{Nx0gEhq;{QaO*d4zF|l_%K|7H{*9w9E$@-I~ns!*;D@c=& zjzLOXKM?g`QlcZ0wPIvRsM)I8V#yl@m2TtU_?Mx4BM)$5}^*8SCs^67MSe_s=dqhK>6HI^}yu8n(_LL8&8WDemq!O;LvadZHf+c#7 zH7mE)U2bkN>lOr)dW4wd=NsGl0Y@D%j={Tzu_(L^eCN{|5-R35m>g0kA#VKVlP9l! z%v5&yP@oM;<`%ERXG(1H=o;Oo_};Z}%>`Ql-^-ipi%TB+)LA#AMK2*W=6Z|Q?*gP! z=m?8=V)X5CMhj9z+4;=4KWJOAmg*V6boePKCRS{V0$;GFKgpe}77H`%z6swH^k`yZ z<%^XCmOC`gRv;%qH5id(%1s*4KS9LyWr~r$J!26?$J*jUR|)^RvYX}hp!g9&TAF;w zCw9M)OgJoK_V?1TjpF`VVTbI9{=o~i>SYIJYNxFLt3T+5n%TFz0&r6L)m!{-W>;^Bxt6h=?|I$H-P}m&stB30E|)nr?uh5^%=9l z_X182C5WKC1>yXxfzYaHtJml=!QCv;UCN9Y@iOs_s-KLeQxV#;Zk@>d{&MMJ@%TVk;E+KyLegC)2GXHw)ba>9h$P}cKBlXz~Essc5jgC z=BeRm8xGe8Kl8HdS3drr>19}bzWlN3=z4C!PUGP7XMEf@d;A;0{Xybore)fKoWOCh zYczg5>lFl}T^bzxT>s7vt7kpc0MckFSW(7=`~yj>_wB^zteN7FvOcaTW0qvc8BE1@ zeY9|gPmpx)ea^exiA$PZR6Tjm%*2AGi4bhJ@z8uhlPPXw(C+IeIx+;3^iLVeFC~x& zmHWvL-H#TUDl)R48+VnPYm3C=ZXX1ELn z>BeQ0*cS|w&gwoF50*BrZXZiZ47~dQD8d!3GKCanpufoUthq_eq)Zp~dCd$FJ5Fg*bXI5LF1Q(K4r;!(STzX{r{dia4# zl95?!%Y7`D)09VH@2efoLPQj5@O9&G^I_)W^|u0ahIt;0eS+<$XseLkw(X73>a!KP zH~c&pOpk}&t$D6gnHIbl#qHVo!(wo{6N-1SUjeKS7!3X?;f|4>UVCV;{imnr7e2A* z6liJ|2Rm69_(ya$$y5mKSr1EjShNwYT4^G1vT5b-W5}ajh*}9Z0oQyBf3!K{0N4>DXKyw z^{~=QP8xx)&3exXsZ7BKYS+R0mKe#0sUUF8Nr-E}GCU?VM2|)^#5l2uZZ~4{|JU;dZJeky{_I{K!qlc(npj0 z+bzvGY=Rg(eh1XSaL9+w#Qut{#am6i_USq9^ z9cl3TTrpvZotkxd_c(Dh5Fa9k0M}6}I(&E1^yL!J4HnOLdh#IrE;4Gx3^4G{%#xGE zg6f+8;Xl_d4DHwu7<^zO74SppwtM-f=@Qr5_Q-UKJH?1f6~ve37upz;PtbLwBu<(v zpBT#IeRgeEFk^cgBdv0e^|e2dZvUgAv5Jz+_T2|Y?>iwEin2!#f&~{IZbCMc9k(0~ zy(na4oEYu}% zO}=an+1{E(26!aBNGggL!2SKTeJZYEN2#7S+!eJi>Oa*aQvVZ6ddlV?9V&NJ%@Y6J zjEQ-&h1|zbUjf~Ty$L71M-IAIN@J~*E`;%5i*|1jjcDoftbSnfA+8;u#Q^~_F8vy`|Wx1OO%mxY$F{!2& zquUEN5eM;xq9TZF`)~XC$Zt=HsNet5Wt(v*Wx`dV*Xg=BylP^T|W(j9(nELb) zCn+g*s!iPS@>12*RBDe7LVnlDbC-z>X^6?&ouP(+a0ljfO)w7Q{c*;Pq@sd^0@#Vm zGCwH0o)XZOmKUtY^yyVTX&4Rz-wv~R;l%zXSQk@);C*)1wE zj<_RUaA!ahOKD*74rkGVANIlJ>qarshk)874-=(-sdXHwIDz(q5H0Xu_KQVs^~6)7 z5=+8}P7#-$f2u?4xy5V7{sOyY2&;N;TGR}UpvdoQI9OWLCQ-}3%r43qF;}qy9h38v zrAJJ_sC<(1CnK$*t)akE$p2dufckAHDScu{C@p!K6zPG>gjG3Gsgy%paeWC zpBtF?HgMnN?*8gzE^b-JaT?Pm*V%^T*SPlelI!2n#xz$8pJd{Ex!c{Rz1?vxKkmhD z*D@NqgyVLUo8wh7I2>Bx^~VKxdZ;TH=_(L}0DoGtB7w$JW_w2h$TG?OJ<3t%_h}Rd zwbj(-6E>?x9$YzjS-ED|vz>2ZvR0VNjfi)31Y6)=TQt+h>$1onDz8e0%Hgx$oi&}Ln0g2Yq~E&)hi>D#a$0DZ8r0@pT7 zLFL;0Q*vvVvenHp<*O(97Px{;ilsRAB<<%P5t12;suL65tzb@K`-Zhyo^5Oqsb2mm zzMW{WVw(<9uABW`c3x~Zhgz_3!ezqEy=ZG2GW?hH$kWRibZbkPw9W=g%&se^D z$q{cFb(ep3PK3!W>Y3?qcC5pnfN)3Rc+kclF6dX%2knWgM*Po!!VSUwUw?GmL>1=K zl3iY9QcZge6Kfp5ZV(x(}l!tc;BQd#B3>&chw97dVyL2L9iUhhI)t4Z}AY zw&NW4@T&LbB8Qmhqe}m)7NCgvnNlk3*MbUdC|rv?8QiNWf5ppXMV@*oX8yDTfL?%8{WWs(%Y^@%2y%uUH+>OSrz5o z%`?T+MVGt|JO=Hx9X zQr&Ue@mTbA(b7VQ2rOJ^BGLsb{j0hVI$w#bW#1IKwyR%p5Y8}qbBRUnvGpVFyE?Fh zQZR>3Ty|~_I~^#*9zh%|%rXy>x8s0vFPito@JbSYlRJ=#{L%!k$mn{eFBHL+->ggz z87&|blL{q_D7+7P_sMCPH)x@?_9;+G2DLVM^xk#JKYt!f+xrD!#@^!L54{Z_0a#sd z=OkjoiGr!7e8l_PWjScWAeCSbbkSrAZd}DcPs7eZ#f?*&GmOE<|9n*pv296QWCdFf zlDQ52(}MZ1V{Kl~m%mj+3pf(L|3OuaST^||A?!HcGa;~r1B*|nsUKA)vIgxPXT85Z z!~MWAVAO)hG-k2bwPy&OZ|?uNz!*O8ld75kO*Ukb?y!G-p?5gL6DfHZY5Mr@w|ut1 z@8XS-$>*jK#YoKlbYx;(p=|UQ)Ye&}=4pfh60tdO5EXd{{R*$Vi*=M@v^p?)@E9=l z|0@KZm7^jNuP$}&Ar%prE|;kb7|i9 zxK!^hpt?)nmS>0?|1wjmzb!tft%2`MgY3~B<(X+X;{dpa)(VsR&k)aX9i3FbZ$1eW z+QfuBG1nPBjtJrH%ndKjAaX_4U=wFj{i#hnL_~j8&3{h3KU_GIv9?WC&D8k}4;;a$ z1iC$htwB8`f&OGudqscZyzhYG+z1K0dKw;z!kN z#E%%nl2r?GKJ5A9K-)?9HDdU$)XLg$oO}0Y`E+5-ul++oWmQNe-fq||kA$l#Ij@&8 z+S0$k0RAtGhUtyCSkx|Ngjy|NY8C zy{!IDXTZ}73yTgV)cX+msO ze!l5Mr+5Gz8z!pnDHU|S=F@RC46ZfYL^VZigD6!YIm#zrh$ISv&xPWhg|=GqE_D)* zd{LFv)IRZ`*47ggSVTXApLb)T6Uc_B+TIy=A-K_yun^Zmr~cCvuuYkm4gi@#vcpt{ z|LsV=#MSGHm`JP6-;Mi{2F5hPKe6cE_b5G6PN8hl-6GxMw~^2pKg7MI1c4?g3HirN z1Zdq08gOPSYUnFJmBesq?byT9>vv-+wT8-tD@%$$1Fa%Smero)Ryw$r`Jvj8SrgZ8 zB>ca{_|yD0A2Hp2((&HZRd4s3U68o?KMe6x+c1+e72-%ygWV4DFtht&hOVx_V;rT$ zslPe)zR{_;^!8u@Dt3 z;U>i-RzLRND(@&G;+>Gz#tvm8O*ck~Z%6#~uGifC{0HCnP)9$AKTeW@uN<8*E__Gh(TdV>!`5e7UJ4ImsH(p`Lq|#&yO4f@? ztPYP05JfCL9tz~$v^wPV@d*REoi<0v6fc?QIZbI5uIJ|&HLZG_WGjINR&<_!{yW-s zRDau2A&~nhvS|N882`^WgmW#c*=2tjsUad}aFPo?ZuI*n`h>Ka);k0$AgV=w^HUjg zfj|xzpL7o~`S#PX!NHYN5<^4~BSGs-)I~!;LqG_K&PXCL#7xUlZe+d*_)0d6hpS83 znaFgZ(g*gI@BJU`rsG4SGrO<9*F>Ls9Cb&JoV2~@Ym+s5MRJN7uT_ITGWAJx!ZEPv z)kRPuVphR{*E7oN74d_}h1SP|>jA%AB^3J7FS?bJfywn!y?~iu9BX_pcq=MQwC8cw ziNWtS&=eoO;)1lgH>L>cyp%0KpHLCRPXA(--A{`I{PX}E$fID@%mV%be$<>&$w^l< zG>OUAD6A+Y8+4BzL9z;P9^$US4=8=_)N%u&MIeA1G>CtBd{lbVU6&p z&df~;_RmA&z%KS4ou7st;#sNF59iinMnIbqF?>_KqH^IguM8bXHF43+mwm+Uw(*sh zN)Y-^R)#m(so1@JbdK_7dPPaXbizeGWTr6M9Bk5GJmmD=Wy8-{TdgD%whtG}3OB@9 zXAc>6zJ3rb!WiOis2=NxlQlE^t~Haj!NAZnxjGauOlb{OHWLYD`kerKZjn-iII zH@JvPUrEw?)cL@%nE+`Gj%W`<8JQ1H$!DI_Om$ z)APE%5}V8UydSqH6b60A@BZS~_PO*XA6E+?DR6lm1yD)U5){SMNREyx4*M5tdQo)n z@F7Pr_MNl4_o%YRa!q2iw2;1%Y0Sn+3Uc!enBq%~H3IM)C0ZW z?g{6;;4+oPpG7l~$olta<6|U;!r@RG{A6t8&naLo!f=W)h5}a&*wEii!3#c5X;V~c zE9=-WH<)NFDGc>mDz=1_P9?;(eg0z*T2&|3-zF=A`*~h7^bHV`P#`Axgq$-$5)sl# zC|k0M|5EySZ-#sZPE!es7^Xhq!tUG8LJmq0d7F}s?-@5JTxo;x>`Abmf{9fh%_wo~ zy(vw32;cXrrRZ0cJ0Xg^PN4TS>M8xwzIKWOn3vP|DN(kTewYluo`*gvof@)WojpPw zTB3!+4utU?%ziQ37#t$)d_7kY6!sMY*{4Sjok$SVp*`E1-db|*G41V`8A%YTdAsPwnvFr^WAwgJ^{mlN@gp>50l`tiLgrBv~@p5hA83gD^H|(ojRd%)1}>l zA#s*x4z696p{=U4@OeQOd8BvZ46?bu^D5&m_1Ks~q{SuBc2b;0U*|->^fF~i=E=|G zt6|)6#lyu-LkC^SU7Z*?t5jY+KyS|k-&G57u4QEQG{p9n6l3;`$4$o!8$TwYvu$e+Lu$ z?%1Vc9mbRQ26*T+V2^1Ybv?^6n~%PuX&2KuuV48!lB^C}O;kX;C=(g@HWQZI5rU$r z017~OvFOx)e++Q7Mp4fYy{UaLB2viZY3ux1Xtt!jlo76w6R0;qmBERY9?w6MJ zr_ZXjB^8Wfq&qu}Ww>*Pf>~X9ENM%#5fJ_1*KzZz5e+wc8JP)!1?|Y+YusoX?WUi**QInQP_$(&Kgp ztTKgXaL&H6c`RA+>Egc%19JLKlsO$^jA^9z3VpvxEAzvi&|5e&#>3>k4$j4-7Nx0r z)v!HX84l|)Vk3PX%EnzY@q0NgOJ;l~q$|<%-%CI0g9^^F7xjA?yq8jc_ckWnXu_;ZRSz4U;62PUI2DT#Vut9%NJF0inDrA<9)^ckj_)DYPf_DW z2cG0{18XDDHY#sQHANIdeTn-sug{G&&6xG84755PTq8?b(97$|h2`8S8D8{=SR4O8 z)+Ro{Hgf6Bb^4A%=ARX0vsSVAh5Zm^g2+@8{<{ktA7 zH|XEvWS)7D%PW2d9;-@}ILpYWQgtUF=5=n;>CjdUew)byx?Nw%+SHlnGBqJa5Ln-QC^ z?M*)oU{R4q52#I$s-`E(wE4vhjS<`^n%k)C%lVg7L7{sjQt?AhNAh{E&_mKp2ITF) zf%#{={MX0C#1Xw{rt5`3xMwtb2n^Q4_&Sb3k(qTNIPhl)Y`-APU&h)AVe4p()DVQB zq9sAqreef?!AmJ`%;4WNIWlgyzS)Z4$N;S!$y&(ohY*a@E;`6bJ< zD>f$CjVR@iCpX_V#LOz(BVi~wGouiB&#f1DXJO<|ODxC{YpzMBAfI(wHJzwuUp zfa4HDtLY+Kt+!k@aXdaQI_82Eiym*(9|-=@3O1YuB1HcuTs2@0Jo-?Q-hPnqaWn{ejBNn?I?%Y7-5SN`?2WWFe8XP+^~>L3Y<1p?6~_sW zUIw$!@HqA6X7S~6K65aiaAf3~{kTE_3tjuTi;DR?`;K~9n3Ni^Bq(pn!Dd!{1AX#} zU$45FDw}DmQ`^vR&c9a!FBP{mc%bO{{I5_O15&_nZE`>nie*pkEf_o)}VLc*X9L<4vLC zrsk1(7H#|z*1GEIh{|KMdKfde^a=Mr`*aGR-`jTG&K0YL z3M;-OBtccR%=j^dc$R8#gNrK#4UvlY)xGpt7?_wh^2gT=INkj8Vc}N8oim=QIm*r*P9DRk^7rtqc?+&`M5fsf_!YhrTNOQ5ajY=h1Q;< z=JWYZ00{UbW%eh)8IdS*Bd4k(NGHDkT(G+{9zi-f>l;N#6IP$-baQc7P*4DSClsXk z>Qy@c42@4qo7*2exNvDYSi)_(`oR@cL`T|aK}f&yVsc2v3y$;?(pmKYCI|>6?4hPV zK=Tmwj65dEvGd7A(*v>@O`LABxJGwj$MEjLzS z)ArMhnZPQ|mEqO1L3fz$Ks0FJq$2t8TF_?8Kcf=>%NhXKTL5qwUc|-^+9c`S+3l78 zDr|$$))kI_*p5n-Sx^9N6F@)kQ`|kXnsUtn_YVbD9F)mo#@%J=)oG6vBdUz8B+&_# z6gp=Q3*{AEI*fH_M($@AA=S0D%b{d;Z4Iv5a3@3ZS}K`%)0#8WXG&DH!8i}F`gcc3 ztq+U`LHu7Ji(@VqypNTWMvz*qTs4UFUNq2YvBxt-55Ec?E0w@SEMk7AK`@2roQ!fk zFb>!=6ZP%d!>D|TYm%(0ZmH45i)*RqjIYX(ArWwD4i0Pp>Bn1IzZ^kBHqdfngvo#l zapC7{{{Sa>|LHlIp~7#~&sk(WEoUIk4)Bvm053(*B-vc2JwWv6=>NIf>wx*fMma-_ z0u_rKCa;z+jTUfm;c>I^QoI8IqT60KC%06HMbXftdhyB&} z#==hgH9feAx4KT#`GCsOq1#{J*f{q<1JH6>V*kJf;j#C9-Qi?IjX#w)r~brcmBRpS zdK<~;>fHUp-5I^4q{IUtf;!xuPCfun_JXwUEpZpkgNl7)pu z-*9c#Uh1b$_!A~_pAzuW($WBeC>g+YdsjEZWjRD{;Mo22fuZQ*r%znoXEs>)s<4o{ zg@A}yF?5ttIx1fn{?otmWa90MKjAOU&FNz(UW7g{F#*J=Zt=TAY=HO){*Jw6`yI3! z=9((#^r-Ca*pN}s5_hQO_;n6J?8>!8X2po_lN|fui8yCK3fOvw0bptUrXtaaw6p-3 zf^proK%J&mDWTHsoS~+_NIuULwY~6YpgSokW+}7R1}JTr%z94>NZQPwm!+f6U^_SP zzrc6K!^>nw7ioyWPDwf34A=hRhw0=@TD;rqI%yaN5PVVmC@U?C>+1z~kV8J@y&vJl zw5yxhP-l#2rl;Ra<$f%>@PX(#{qlIH`9|f)4Fai9E!Em5GW?+Tj*jI_cbL&>5rGX8 zZmH1s0Tva;%n^1U@{KWtEVnx_IAoNRlmxeFxMy1?w5vUv+l7#@WZQ+llH_|3;L4`3 zU|=k2s+21D=FEl6F}&u4s&%m$Ht;?ywYIuC9w0raEGGo+O$@O{^rkb5e( z&10H_i}SlyxV5|)J9o?K|*s2>bWH}6t=R`mGSJ)sl~FP7gv+5 zF@b@BzSfOt+(3{;k?=Uo*w%&Qa>Aj%>fEhgTcLlnkuHssl0kW;s%4Ovf$9yqJct<$ zJ@I%_8r1_Y)W}~hetXNTj`C&y#kcZYMs(kUAgXfketUOCjID-QntqAYj8C=>S~9$G zPlcxC%crHhY9=Np+WU>zxZ402d&unUEP^{LvGx?=4TcM#lQPWKBUY=9hwzV2qWZC0 zyD!N%_SXl(pb|`OFNOvO{FPygKX$43_@Fy`{PoOqOmF|%I2$dw$$=tU24eOt}=-K<0^}a(%8@S)&s8uAl9|^3o$cx zjhJCr6ef$nV}Nawyj>!J(b98q;oe`B-A6q9a&vRDll1S4U7!<^^Rg-{@#%Q zQF>E|kc{E5vop6FfOXu<;d_Vyz&I5_agJuVc-|T?4Uljo=MD`|6p(|+xw{JhIvCjL_T&n%@Pffl^!3TSTlx|b!HYlM7=gDUeo`H(UF|= zk?;H2qg?zpkd1$0{cT0CZ<`0=^Sw*1+y7FnI)EWi z>xCP*IU*l90IK1kUxIPI+<(>wWqa+W6sM;|xD+gpJkO?tQ}xpOf_ zwdC9w%^tl`4P4@A2_J3k*7*|KVR2NQ56uBuxVrtd;$pF9E4@KPs^A=A-a*o<_m#foW7Eg z)Lf7R{sIFiZFwiveC0W1S?=9GCxzCuTY ztp0-0%^NWIA4y^2&yTBmkf(XvX?Te@X}HyVUU5%>0wa1Sg{%?mFj&i zTJAjht`hR_8u~{=w-X;!y$xx`)!xRY-JdhOPGkV6KEonGwM8p587bxq%~Tcd--CD# zFQ_7b4M|986As;7O-hI2ySH2*ZYd}{`4-V95@U0LgSz00-(u7m=G@|fbR^^>PuNio zO3y6bUdx)Dq_dX27p&Yv0-V%oQaa8&{P)9S2QDs;Vsl@A@xQE_?-70Z1KgQ~4aKqX z6ZW?_ixX&t@NnU--ycY(9uIoU+17c`7GIszsbrN*F6S0?1{}9si(*lI{5uuJ?%#** zv_8~*Dei-Fi7_k(sMf>8VXw0qo54MR&=bgT8(>ZmTn8uav{*uxpWS)J{KmcExuk~9+p>w^Xrc0nui zxqZ!>NEr-nOKEE_?_=NMw3M=EE7GAy2HCep*R@_7`kZ9kW2tyIf=nC<2(mCD{(7z~ zwImc7D6Yt)*6hq6=5xcf(O~z^Tqe5=Y$2bkl>CuhtKK^x4I8oVa40Y_<&tF0h#y`E z$oprp<<8pm^zMMV1@3Zg_~^rK8#@c?uhLkv=T3R6|zu7^h!Iyfgr#4t& z0pqwN;DRz^$H&MBhfNNuFj0Pf52a%-lKQ@CAe`QuZ_q)DOR*m79ow~#VAwVDt=gwS zS;5LgZ~{sQ%b_mW*t@8JNTp#doiKOb3kWGTMc@t}gZ0_z+m4R14-_%=9!SM>5m;ak z$l)M%D<*L}#1|q5fyjgWNOJX-6l=qEibGd7$r}o_6P%AplDH&tTf#ARe}d2N|Mni7 z={YOb%y8|ml`W>)-|z(EplHkUmgiM+H{E}t%F2b)t}=JNDrWDx#1?&GDHR9?BTIB< zgG(aM_%l<~Ame`FK5IEU_Q|&x=sVF*ODYLTJWyOrP>vMej)GypQ`$l*tC&dqhD6OT z8WnqaaDR4kzepN|4c-oFzhnc_+fGCBeIo%AG69=m1pUGtmS*D|5a3cdF=S!CmrYI)6z$w7N3=5h7QtI73T!x8zK6X|%JuIqxEiSMqKWW&`07I_@$r$& z^GT;2M&Xp?DE1&245b}Zx8Vb^+2p$~3f8D2m16NRpm5f*S-5xhv*4TKpS6 zc#n=w!3OWId5s#^?Rb-@xEffwvqXUdmhI<9F7{xj!&t*a^<|!qgXWDc|4c5r)gT4L zR;UsbV4<$vjYWU)UQvdJCkWms(rK3`^9IpTh*2y9HPo6 zp!=Ubs+&w=_;ttJpTDfv*O#`_u;uTvG&gN8sWZL;hA}k$YE*=B=Xz$LeD_`y`#c=K zJvwKls?uS1{|@yPGBS#eycq=;^z*!HABXWt0RMRPEbmfKHDKcl6SJvn&mLM9H+G$zHtIpQ`dj(F!QKt z_+ZITBxWcyG;Sss2NX*Yup32H*)>y4S6Pr~(vy^&deHOV@84%Guf*OuS6%VmUs|Sx z{9W*ayb6&8EcB${-i!=kmNjWp8U>?nBVnV_g>`F|tgKenluchsD}<-)nzo1It(|Hv z!TSeITeU{NiOz7loqenV)d1d&;=SOaNZ;#W|S7^dR*P_;rv$9*T8g*J> zU{M~eFQ4E4%TG&XJE%X zE4&;e`zsj-w`Gsxxo!ORbjde)GFbT8Fa^qtkY1X=1D>kaKVdNYJCq(o4;XDvm^6%? zg)s|0LU$gX<7A=-wcg6Pxl?!f;&7i8ISB>Ut2NO#2cPu?5&pn=V^NFkZ_)K)DPUF0%eWE)1!9T)xG8(YP|Q}8LzCL<0$FLZ}nvMs4)hk zw(#3T8E9G~?RrKp?SS+i4l|yjB~{D`UOSeonR$6qzGe6#J|QhAI5m|MuoDf=Ooxxx z`6AB`4ZZ7gdiMN~S^6P`jyuZK>@=AMWflz{AQ|KOyQ~=h!jDotvX{h=Fiq;V0T0jmg^BMFIIlGUpk zZ?|lV_1C|5XCg5`nWW9omnHEx3jR7NcqB1zU13mp)OlZguSF~V!tD_kiUY4vr)orS z2-L2IW;|N3U z#B0#pJWieV!>^pt8$P(yZ2;26j(|!#*;JD1ewFQzgo(OFynrP;FfF7HE6vH%>A7iu zs30Yzc@#WIIQxs61l0%X>-gw+bsEbkAC4B0?e9dYTMMe?TyQ%oZSeFp# zhng2T5#)Tz(d0vO`-dy;_=L%+k{vI>|BZwMRiG(PSO$_xtDh=9nz506y* zB=#`j*?K+ZrO*AXBh>nrSlc#NYI%To`&2l@-CkNN`VQIjiP3i@d)V3EkQeR< zz9=j;$g*dmpa+-sPhy^b=Rd{q?~xlR(qc}qID&j*Wmjh+fG`fO9{&8iIKLb=Tgsy8 zH}+Rh@Q3EX2dIxofe2<))W~jw%jtQTm&H`}N12@C_uHrsP9Xm;2Y-;slP6=c0U0`2 hbv)?O|MYPb`>H&(X%L-O0RsNKl6@^xD)lb#{{kY3MT`Id literal 0 HcmV?d00001 diff --git a/src/vendor/container-interop/container-interop/docs/images/priority.png b/src/vendor/container-interop/container-interop/docs/images/priority.png new file mode 100644 index 0000000000000000000000000000000000000000..5760dc71b557fea7ec5bd5699adfd1c228b9cdec GIT binary patch literal 22949 zcmce;WmuHm7d|?4Dcuqh(vlL=9g2jcbP58J(%mt@5K2ggfHczGk`mG--Q5jm&-**^ z{r}<2C3?v`&$HKFd&Pa+ zvXi8OIy!iHp__$)|6|z8e{h08u)E+t2;W$-pFtqcAqvtG>Taof3+`To=l5bqQ-cPt z`Ca8D*`L!;EQ>x(oX^zEA}pWqm_Vv#y!W;iDXOumwvw@$s0p|%d5&lhMteyaLx&qp zPfx%0JjH1^dH4|ahy4See^8Jazq<%E?31~R%Z5m0%4N$YPsVj5eGu+*T3Xudx3sP< z5NYswP@Mb6D(tw1?&#Xo4^LH&oYM%Bot+(%ni?4y`6QA` zgd8<6YoV5|y2rf5`}V?kz5m97xOUuQD7e`;cCGwIb zkC?+ClElExKbR0q+1cIncVzp?`ao=K>}d9Y4+&eIA_IZXG1PErfTPunMg|PEAZubm z`_K2$JCckCRcAj~F0R3d%XvaWYq_YbB>*>|`@aW0i=(Fjqp)fgx0M@rvQJA+?6E>g zI9zJwD`Mbla3GED-_apC@6bcpJ223=^y?k58t{(8Wvt2nMv28moSB)aJ8ZdC(bSyW zb2R84*D~}D7Q0)<`QL2j2qc%{l||d394%pmg@p`>$6Gw+)jFd^S{#HVB(B;qz*gxN z_UF$bi@uK{vQgyg6TbbchGk7Bm~Y;^5qu>8%%C;2sMa~|B2a$iUO&9R-L1(Cw{1fq zE=Z$D(|BVrd1U*`!Sz7`bIlvz9XwuTbpIc36G2FHn6@;Uv(V7^m%N7$L-Je!zvb@a ztzt5Fw@6a{69HIXt>-f`e%Z(m<~ z1QDxI<&2mzcF);9auLlgJ&CP$<3s6X^5?+2yOMCfoFp0+O!@-$=_vahm z$HT|)h6X{0y=ku4m>Bz+S`rEhHY%ShV^sr#gY(z>bG6EPAaW#&CR}bDJ`J3Oirq=5 zYiU7OdtlJ7U%x8BLJ1g^hM1X|73wVAlVQDTW@hyNyf5_?(*%hM3r#@!O*gu7oOy^a zy!#&Hu=;zsr1oFO)@V+9g1&MJzuoVIm%8QFQ|#m8<8_gLqzo-*dJ-h5#wjEa^xW@ys0C=5$z*HZwOjzCKwe74s2xbaI-jDw*UYeFFE! z??|#h>(YAExth;`g@uVtEhW0OCZ`*JrfcmCE0v2Gv!k`NwcQS=n?25V&UT^+7!8xn zJ`i)X-u)`qDWMe;GmIqXePj8G96?+@j-FltA6Pi(YEzrG!UcFY?X~{+iEoG?(IqXfB*jFH0#Bcj|;lLy`NEX;bOaL$6|Qx&jG!&Z#nE>}a_Y5194q7x&(5 z^}udjgJ^zL)vHudZ*mO{4Rj0)2~AC6P=J3`Ryy2WIl#^vFL;@l(8;4v!^6Ynm6b!g zO^#=$j*je~kZ~i6cwPj}*!&(!6H--IUn44$&5`i%5M*IxbpW;GZN#n@xQCR>1UVou zFg7Ix3LNf!Zs+tlr>>5aAsz$FyT=a!3AB^I&QP4%pVd(X0}Ain?Vt7k7e!R=Yk0;L z+Fie+p2@hmxj`36h=^Vo_})8%UkcysYqJp_uf;1zEG-$q)-6C9f}+?}IV{TRbDFZ~ zcJ`;Rtc=8U-XRnn8=DwMySpt(hU64Zt<$;O!*_Jp^;Tsc+@NA-GV93+R7<+uU6jL=cJggp87Q3(FB#)vRP)^}{_cbvQ>ick- zv6Ak2a4cBE(R37WD-uT^K4swfZhUHrzrL*WK^H6rp8URrb`S z?QGSj?BZg@V*N%_soW3rec+%}Req{YSk zZbf)3h6s6id9$mk)Tj4dDy$}0DlA87j~{Liv5F>GtgNiMTo=6=*@&TTt5N)2*)q_s zL_;5D8@?0)kFx@KMa461EobLyCKeWamqz_!!&U<}ohtcx-^crv!DJp#tB?Cx%iIXR z$mXcR-<-DLtFT)XbpVSQ0icVL@Wa*WLDJ(f%r^{_luOTa_YE?z3LK($M^aq2^8)bt zaD3E>0|iD*-P0Yi;J03_U|qtlyTS>&rEo;(F|n|M7i>w&nlHY)T`h-puXICY^z>3= z=oNy3g2I4rWxYpT_dSK{1X1-cTB!m+e>sA z51KCxj`u2u;e`}uOMF$h61z3m;A0I!6 zUSZ{*=OGbzt?bt>5>FZWHrJi>vvwc)Tneeae@_e6wQf5vWq#MGTSZlsrsZab6_1E$ zookqAs&>(b)#rZK5NeU(tqFP=R^tM@ZT&hi?9g6bKJ01*$`LeSMU>%n5}w3m8bT`M zQhe1T_CNz)QQ!vYR-gO%L;`&jU1Q^{@);YGfrOVPeQ_^Vek2;;S5#D_JzVyO$8#vM zIW?pU@DduCgg)$OehpD}!)YU~!-S5{XSPG2bCkLA510F|u6j5{3E z!g@m|BDIL@FgYssE`3~N6qIo1-ARm;KMcUvR}*DE>kkqD>k{5x9+GGlP2@QDYbY{D z(5AbsV8Dofz!wWds_KUi%-lRY3u0fv`qzRiArNxe!v*Pf7UsS){u7@6Ns0_ypqSGr z#Gzwi;)7mH%n+~egi>%k2PMbBkV=c|d_B?7qbd?^z`GQ_@Yz`%F3@z#Og(o?Y9m1Z zIzH~9hK7ZqUwV~jvKn}7zW_+4oBHueE^BI6#?rE2y23QHG3L{!(MFILs`~mbT@L2A zM`3T`=()`M2?4%fS7eAM74@pS<<;U^7py5I7Ep+rFEIu6>}YoC=`+4Ccy|StmNjCS znwUs~=+tVhFnqk3%FfG^ZMxn_IoPi=DJUqwHT1o&*5djBvJ-@&h)xfc+Y*ARt}b|x z_Ob&Th~~CH#JvgPb1h*>t@VV?aOv=f^=(%W56irQK{5 z#@j@8XEDyomKLhr$r6A++-~=4k3q23-~IwV2OSK{uW}g5=J{}KJM#-=SmZLbCw{Qj zb|HdJF8aBE04eA(p=<+GTpaoh;|sNYM5lT0Qs5W~mP8~Y;q5Jgh=j!T=`TI(-qO1D zE$Hv=ryCMQ6Dv{t|BmXgMwac3{(R>JO&1P}5D^hGC#=AF&?w*oEUR1Pou(9OUDgbr z7Dw|LK0w*?hXDS zzsmdfD-)Yry|Hv42g0JGFK*Ib^$@`;noubq1VgOsz_NO$P09Q*IOIF2V+EEi0V3cx zV_S1D=SO>&6|J; zD;XuF=qa4sKOZ~6Jz*FG4A1H4kjSHc=MXr0dpB#L?Ex+a8W^Zg?sxVWG%6YzC^X1m z`p*^OdTw`icA6&5`BGvN6GN9fL+M_>Ci}(a*}XTriPddWpm|!U=Hipum zfIpa=?@Rz6z7*8_g8}dbz%5leFAwTBsRmQ{hfP}v@bPg;NyB=3<$@W5{01`^lW)e~0KVuWpXBL2q2w=<+ooe#WMpJYs3n@a?76ciBCo~R z72+~SE$2&0larIT#`0lPx1dc`7`NSDr;-nTK`ZQr$T13cvCnb0fbZ5&~-#_ZQ=y5f&@7H-kDkpjHu26CU%sLk&!J46p9y4$23uym)h-60p_qrPzEaPXUq$ z?p$umRfxyM$N!uCdD^B1>%a-H9(o0<^Ud+^D{^u4@a9~1{QFsj`{_q`U;!ThNcS6% zniPuk<@dbNZ#6V7xmv&#qpiM=46x%)9I-uzD+kNHoZrrFs}Zj{xn)WWLUU zq}fYNR~HJ<9_bTQvyUG=UQ!bh2I0}kipSCK{cFCB)3wu~6&9Y~#eb)*O?q>&k41wV zO)Z8kqpAXKTUlAb`Yr5rW#9X2k6nkHK=L`j-TVt*2qsJQp#ZN(l5!!EN9D;lq_;MM z8V^U+pzZ-ickuZYZ@qzTwH49eM;DYO{00L64+ zvWV;@fEKzz_g}IXVD~hnpRza*{2fQHe?&^4x?7$^Yh?!Np?0f}2)#lFXy=^XH(y}X zz8?~}?EVSE!9Eh|6Tb!99?+FhWmL=Ej!l4QH&g1lIgN9cDOPL}aqks2%YM`Kw;a%Fbm! zgEBb}kA|KKORrC3HhhNV&cAJIb3maMOv8rvM(s)4T2m1GDugJl*MICra^!HzE_cs8 zV2GBt7yEG2AxmP9*Em2wT31!+`xOZaoC%l|DYqFqNHWbbgQqEfLKg22T>ry&kHbC} zJWr^ENdf6U@NQ9eX{7vENe0FOC57sC=f+MUPWVEuY|3;f^>xWk@4wk3xb*;b%>$@p zu&Ac>j2?Zb#6Vm`0EL7=87lwy zhn${)!Q^1R9x!OPo9%o!j&>dBd}i0=kAg*P)D?y|4df6wxP(7ytv9yrW~T@Na=GZ+ z>PdeADilyZ@BqW?F4QPeF))ZOE@lHL7OlIx8}yYa6fE4vh3GUPR})Z5+;)EEbAH)U z0lNe z#hNXtU(pKU^dzrz^?61B?4~Swo$ERBb}DSLy1II$ddh$R6zp?SJ4R7a130ju$_Xqg zWN|tC2N!FWc}xFReHxPny;Zx~>RV5eq@UB^kDD70IB@oOa)u#(cYo+hLJXCG>xc*b zl=`cR-uLHI2czZsb7jTFgqQP9FtDx$x1`k69RJ)_pTh8ZG&G1jFZPZ}lPT;)?GCOt zGcHN1bu~0V#leQp?=ne2EnkTUAVRN)>rDeX`<|X2C}3)uAUXj_1jG~%E^*v8kB*MQ zS21yFYaZ~%V(kh^peVRUIRJ%`2Hrm|(U(BMSRY7)KHT4WMs5P#j}d4-8(fGDKpq0n z_yDi}S($JDS7x9gNCHkeYdXjPp{1jf0_sv2;B6j}92>JkaH0Ab+$gKSlvK1K6ShV2cipv7oxhfZ7w*8^0T; z2%{3grKF4kxRj8HNJ8~1gDyM0f~34WIuMdjfE3cv(Shaik&is8T${~cSUR2_!`Rpu zIP189tzgs=mz;d3CFLmu9vLvv8+6?o8xZtZM%6liPoZjPIDkOH!q#7kc+Ja8q*HPc z!1Xo`Ik4T&pxJ{4=ovuRVPa)H)v2k!cPxR6Do|eVtadg`P{z2oE1w*P1ld9-PrHnBLE=> z7u3v{dS!ja#T&YRSD8e6Vz)`6R*FCo9=3JjNTjys2UkhQQQ7js8qohuDoKyOcH^gx z&46pHr7bsBAS#^NWN5RM+Cp{Sf?gLh7I00|%Gz2;DA%RuPU4IEm(aV>BwR#4YGO5@LUaMOCm7T~ zZUlV&j}}9z?We6pu8Pcc$`}aP(%-5=T9Vwg$cr1H(^)~L~1^x2=_xmhh2aG0Lqa&gdJR<`$kFW zcYcOX1o$EMXl?-`5P=bf)`zciet5N*?*Ja$0o0Cpi+JH8`W<0Obdc2KaJ-?&pk@*3 zq&i3~-O{;uBKP*{&Rn$F07k^BX#|vn82asOg(Go;7uahl_9u!jci6DUKkNy7_**U1 zf>$jQBM~@Gl@()-aUb6iLrNK>>YamEFr{~N_zvA45T@0_qSB7-v3w!Ul?2m==M7B_ zjt`bkGdS|dGgSF5+tnv$k0xt-D(>aUcG&cH1=&$P$-eH}?(Iga^y{wLt$ zIc*F|fWm<$=jH(HOv_W;dZxR!`DaPT;5o^1KsY#|E?JJ;>8O%I0w{9h=yAk?(rVO zlb#<(eOY*=%@?yD;UjlQ%a1E0cz=WLty*LibnI4x=4o`i(hWLA8t%+%_)UG(Cvv*i z>I35k3ytuGTPAJkRG@vALCM5cc9`26#?+hHu2vf@{$8#YaftRvOv{KkKrEe8U z22b0~)8b(TGW|uxmb4_auV2@94zneTh223p z)?pVb1ggNLuU@zQnw)Hp#Z`Tqykm2Ku&ejov+iiU3(UjU>jiusTiX-!?W;M|kdh~7 z(Ks?yToBFBh&!HYrm~AgMjL+OXCK?fjWL)6)SxA=%`I2(81g|EUw}C6Qul=M4G=>5Nyk|c@A0FqTHTYc7K((7cG}*O>5#eYC3eP; zlLVk897_0n(odOm-BKrVRMJ?rHzq26Cv4J|C>^wAKT!20@);>s)ShC<=J(tWim``A z4@WNvxILE&W5We>O*Xl|a204Fhg?K=jnf<#@CY4E-mKamANi$&1Fo5l@>ykilar`sGGnWfX|oyefr9mL1w%x?*We#In0*z$FjC2T#(l zlXJeWpGt{*`I-VXEs~(STEHn*_n&Db>RC8WuoCO)7nx?Kss)A%F&aVi^`~?45g3y1 z6*)2ASgUFuxRWIL1ufJQT`l+Uz@w>8YGs;KMl}4o^YTPa$(YCyvq*U3C*rhY9btUv znRuo_V2n5=lv%9Dq`xP!FDzJzjxN8PTyFRlsVQbe{H1Ni#FRT(p=zjJC`-AClc?xy z5}Ieu$F+8ET8GCwMX#0|+#d{Bw%;ew`~{!VD+EBwt*k}=8TcZWBIF*nG*JISO>OKt z&(AQIgs7Feg?olDA2d@sK_w%-2Q~LNx7I+vXP2f|^Ie#-(nhSB9~; zD;~{WJRBb*Oj8V9poF!IUlYcku_n02Gd1k>xO z4-{^u3&+I1#L{WOa3A$_qeEQKhKKHeQ>ba*wt-B6@9eo-=-x3!K5vdhE2EBV5sB($ zE$K{bj8OStEwz{~duOJI@53jG^6N>{?SjH3B}W{+L;}6yv+sVug%@cBU6ZNY^s;Ei z7X{LHW;Wc8%2P(CG@F%wTK;*G%UJAkd79Bi`s;XA){?GIXln0cV)pMMKvq#N+K#H2 zY=rGmgN3VDTdPGYi~@ACOEcMDi)HHI~HFG3tXSNEi6Y{cntl z=X4|I$0n$DXUkiWpZN+0_BS~FnLs;v7xMyx|J`=f@~7D8bhVJv+YfxM)}EG`#MHKf zJv}KW%a-ygABzI6qf9FKyM&F6*#3HknCL2zC6o&89mIV1Y#>KXLsbjuGFEIQEnd)A zbI_xcI=}LHB)i)j$SHs2@H(*9kki-gWc>~gV3Ct0x87et)YxA$hjvn2Y2OcAcV(un z<;D&>4_3`i5IYZy%C`|AnLK}sulbqvNU-zuS3AjrZF1xt-d_1v9?g#`5=5G|BJ0N^ zeR~8%_Dn1$kwc=`FEupmF6D#R^ZR!)F%e8-sD90#sLEic6?RzYV$Fx+LN3w-uo`Jo zE4EWB`r=NDH3Nd2XS%5@)(Y((mYvg$g5s%LUak7~bftEQ4e4zmyCmvZ>>kg zn4gH`1k$`cBAp+y(rQkRRbS+s&QfAE{A*dIn&ok(j4h|<200Ua>LH5);=I>!Hw533 zg>Q;XZ*>~+oLoAT?D;_3M2?4vlCYod3=+MKEI zXe)I_+gl0|W87MYcea>bp+rX%04~bPzg#pt*yJCqwzPjr($zkiB9ixP2+}3eWBhc; z0!W17O;NExl7%EzV=V49lMDi2T#IKOvRTi6mMIiy1(L7 zup)vI9+Q9@(f$)pySf@O37uchDnP`Rc|P)7qRhJV&<`7;qGo3|L3tk+q#1SYJ8aMAP#zE7qx%{ELu>F0ercM2e_B7}_qM^oU01uRX-5@>6TKx4HgCEAf zarss;2z+sS0A;)bq-YSAN+|zSMq3E=uv>L%?4wBrGj-*@Ju{&OK;;5AFM{>IxI^Db zn5R*Cltg|_!Y!Lh8xF)oc^;#yH)d-gv6(?1f^5ktz>DZ2)2+KmB_-ibi7c4s$2CW& zu098rpey9r{G5m{?aoe;m`)kpi5x5pGT&;1n;nI4wG`@Ayr8-^nh3q42AS_S8y6~vU&&cm*NNdA3@H%jo6mAOTeqY&R{hO#P3&H7hFNGWAX z(8%*cRIQS|JX{f)Xx-AW!D75YX+ z_fQ-Z?a1CRROIrzlzBdwd3FJ!9JDYdmNZ1yA~#D}lBpx`9KPtm>mMmZ%{#jMMUGOe z(Tw(^)gXS%QUo#oc+P$?NVU1^o(ByIob7+=TC}4Ke=3%cS==A61bGQo$aH>3qHAE2ZJ=KWP*RBG@Xx7zL+`!1#98&*|cB-Bm{hCdJvUPuTOi1 z882nue?f;}NO{N^i4(jGxjh+L8GDC93kt6@ms*BPJ$Rhf}pi& zBZ%Kbsgbt}hUmZ4&`@80# z`Ll3HdPhaQjL$}c2)AP?@if$& z8(Gs)-g^jy5o&z|gW;?U&3I>}PI*?=lj`87I2zbEYoaK7m*em&9t1K=t&|&?6gGB( zZJRw|odd3b3d`w`zX3ebfl7f6_olI=lxw3Hd~DjV;)e=|UjeR2%Zw+vORyB1%BIP~ zQQ931!U*4&^u!m>McO^xFd*fx2`#I+DJZhL?G%3g{7K8fQKl9M--CJo{(aNIWYX!? zjZ#D}zx>lj0;*yu%NT6p6adSMZgJ{=t|OgsgD8?rp#Q3+t7=-qKQ~O%o9X|jl+T@y zupluD^VRh$7X?IEE5EKIx^b>bsh|2tx)cNCRapL2eCw~W+RKpGbGUvtd!lJ`}#8K z>N-bpHsU(x(;#(AtQfu9C7uxVY0qoQG_lz?ue|JSl244Ymwj2R_X~=f-3S`}oX(bi zTd0uy6$noowy}PQe2bnrZiccXA{qfv(LWS-_Acr1dP@+#E6JtjXk%}%BcP$VxX1l+ zCS=M3MW`a)*Z83>XnN$~I!B23%>tfK=6?t7{{K*a{%_qWbAJy4j+Un5rcoj|%fyY( z;Hd~11I23mt1QY_OA~{fdg+(r1#WTl@}-9aP?pvnpi^ymNeaAuLBnY-Pm%ZJo%qKx z`6EWyY-f8@<+M{1A}imf6dqe)10sBcC|<(%k{X=Omcl+l{P8k`)kXe4V@Z?9F#eX5db_L_x%fOsW$tFeM*jW#Wje~yGMU6t6V-;M+++Sm_HML2&Ehe57P^R1Xbcd3;7HcKPP#9Mnjd+Z@;<} z3;1nx^u3G)Ge{QYdFrB=i}dANn`EHaN!MYreDl`cIw6q!#I!U+yFLup?CM|pj-;Xx z`OX(0p}%6uy-GXP0*YZ6x%HJX!JTmq_UMs_>G8o7{L(boOSN%{Y0O#P|8ouSnpa;M zITDG7hS%dlU+pxEodoXf1;5-WIIQ-y)w@4hBgn{llbX*gc4NFsgTo52OyFVZZ^Ec7vDX#e>wUer<*;tnHJpU~4K|lC?Keg3VD7v+RB6`OPBEG|68|2Uiq4561%+}pawX1Y}{gm@! zADa4tqs_KZ`tFP@rb>s<>xPTX;!TrDMT%lM|4l8!E5be}pNOquHd5N0rGk>~nP$(J z$I;MoJ`dl$Z}DljM?0)xedMs9d4Kq?%{Hjf&*K5g`G&|egWfL2j;RTJ$3jG!xZK`*q$K`AO z$Ns(%WYm?B(_utxv|^%l^xY|Ht`DpBJMplE-HDJ;I-i>_+5`QW+JiAjh`5ujV(KYR z^N_?0s?HTi$iiuS-Jkf25U3_MlMw0FJm-8(CQ_Oj*=yWcamazw2&KOioqxndG%;1J za6d>CGn2NJ z$I!MQ)Zmqk>WckKbrChcqs@HEHFa#(z zFC_9i)5Y%bHaaB56Ji}Mo5PwNxg+nn?Ol4X2}=xuD?MF|yPPZemGhUUJyUstik0JRTMa+@EVv9iP?TE75akhtYAgEU^#j&|c@C z!d(>laQtfMU;zo;KfBtR^Jp07xzFsEuU?JV8`+Y>byV)XWQn*wqv6e3dgvbA!ENdm zfL^n&Mxm_vOVQ!tIdfvPRXpAgFN7-mEDPOUf(>0i-j{i{=orEZ1H~jKce7yVXb|mz z0oChOALJJdY0v(N$Kj^%QWY*lN9Z@ER=(mkOCye$z;uYE6(_)t2+Qg;ddVs?lkkU1 zMhg55st}z`oA_2h;KF@6J*3CrwyZJ+5uzc}eV`u$LB0sHyRL|#Pxab-2@8+`_a9;X zRtqUdK+1ZG{mDU$?VUX*ZRnH0>o0hP(Q9>6%oHx_HglLWM&kDG8QUESlD1&x%R z_IoH0^+Heh^?uW_5JG`z7Py>^63YGx`KFvxol+R`BkRZR{Kc+uNrAY1*}svXppJhd zu5Zy1CN{{5Oe(-(k`HCTdKHZ%-VK@g*em!Ce?go%JPSzBOyU;2F}Nt{E!i3 z6e$ua)DI+qS-#%*lBtbFDi%)yb4SIWIhG0#6h_>P<=>ZvIgVP-X#S0bH=Aq0DK~|u z;9()vO%$r8ziA)iVYpnDoK@{K0;ip*m*yPZR{qU-H8I-?3oPcD|&XX)++)1D5SYRC}N@UIk{j;V}Z*vmdk_lxs`B168MIf z*lIzE%ymtPeBA~k`oLCb(lNnOK~eB_&;5b<*Bg)VwGCR2J7no7(%_qbfR9)x-Fi3^ z9|PHG5l5^RT&od=V#l0~jaAr)nX3PgnC{of}b+_#xw!= z1b9g!l^VC=U%23S;Y7+A<+?z`9p4xU6;U$tEc7XE)WKIIzd|WA)uU}j`E-72DeA{( z{k5NE{v4b7$wI?uq;N$!34$|9x@Po+RLdPvZXS;xW84?-dmpdpl7MORMI>>Xe?rUW zVjqv?N*bJoWPX+fE|^JPwJCPJOF>w+Z~JltVe8IM@rISpITX^Q`}^MToQPeMZgi*( zx%u4iv@MIbGg5n=&otmJm*?28V?McRz2!V=xs81sMJo_-K>TF@EBCoK-kZXaGt}An z5QATh3x{CX^n@t5h>{w!e2X8Y`doKo|K;Npudf%lJY`*~h!KB4JIrY*$Nh;_CU;hM z97ZOF05K{nm28)z&bFU;vzg{LSG(N%_r;deK*^TVT-khyJa4^R>}~5~Hv6I3fC-B5 zhLDvtm{X{Hs}tVegE|5gjk~L<9wTOEQ9F_J#OJIoHew{hCADeLO>Oq*`%5w4)|G(c zSG5s30}&8LN6WP|+|Qh? zY(@6*lq+`==(jpLv#@w2K{Y+M37)E`v(zhj^>7c%TS zOj{oB8u|Zdx7&V1i8fpF`_*y85Vrqa)L;#?G!hPPSQW_MS%iv9iw6DR2Ak&p+f%oS zpwG(9M)fkn(r4#u%&C;hbm;(`rGQHTksbq|-e%sjsWK;v-DcpyYjc(8#m`EqEIv50 zT-&%)jVH$}eDtXyFticuon*Fh6~bOK?z?@qtU}(R&sK0p4tECc3R|w3{4Z3Ol(t1f z!%)V@M)HVG)T}~3T-?%=H|2xb#8#I3Ujb$yr+g)d^eV(?3sLfUU+FV)ZIx?Q`1IN1 zki`S&`(k)?EUP@wHT(+;W1b@vI+pr@kEWH-D8xX(!{cPp6s-Mh^+OtpvQ7t~%I zO0y5E2Sq&t-Vfn$haq7==*ePuW2yxq*;(J-;Al7taGWmdalO!1RK-$w+WmFh9^=F0 zPO8i|*-e|0I5UH_Q`7%gOgz2(uP043Nu{ zmz;8EuvN8jUc&gP3DxBX)8{KhV6CLB*y7ydZGjNQ0MOJUTQE}^NP3*})u zI`H1cyV*o7h_PfYYW0j*Ssx!>p(dALG|uJE1Brc`Kam260+*+UeLZmV1a*t1cx=Vl z4jNdHJklbkT17)RTuJmpEE^f4@nylsAUkbSVnhBp(162LfgUnT+jFww`JWbsFY6F0 zwC~3J(VzZp&m3EtB>Mz3VuQS}MsR=u9P1FZQL*Z(#~VlJl0{x&G>zA}^C^-2{h8-4 z4gZ(hc*vGB`IB7&=VaOgAGpDI*X8jRRw55C5wr!kh?d)Lh?{-eyVRvac0|}!-$JlA zQf9gO58%)pFBa!^DKx%ZNF8>3aW44OHYM3-^zwHQ%F-;CLV71B1TLc^xqsWIvUcr; zA06opO=J7nt+nY)oCr}QC55(#aLgZWPi}wW+!FTP`HW3u=j$4PBV{EAU(YbFc|g89 z)U8jM{J@DueJ%4>x9M5lv|Kg5ky=ILT8&T@ZB5r1ll|!bSzRXxMar-py8ONou_9kh{YrAQ{t*xF0mwZ_4!+WURc^aU!f2298X8-;4a0 zDhLfs8{z;vs$ijfHpc#38%Vu)wb@C1Vqf#yW5#r6=zmWlOOfhkwT9_^sRs<;>ZA!l z?B_i^=s@c(mFv(eaNgNJ6d;t8`|lvnT7H6e+T5P?oKt@}V{JB4&UQ;x8l~Ux>gU|G zf!tHM#4Prx>#!lGUv;QOwP_u^rt!kRzy5ly&UPzUyBZ?7wNW3|t*%DyeJxFJ+U7}H zF!t(=b{ulwdGp>zc=20P(QN3I*%(G7H7WE6H&BSktEB{D!LrM4djCgnoD}-=6k}QO zoupBJjDRB9cfUb{Sc9AaeCNDCcZ-V4s}EicuW=%bXm@`l!M5=KJj;l1UsdC{bxDGz zYAfgOCv?u;Ms=3oWBHK8qY&B zP8J3_kZB6B(LZ2`3$*qul%HB8c6v#RM%MQ}(*+}!BImiZ?l!Nrrdk9nRDNn^{r?8# z-wZueG+H=>ia#j6dSh^R;PP}(ZVlF8$8N$kq1~A%{-u%w;hNv|WCdnB)d8Qg5PZ&? ztI5x=DSa#&**54-SQjpD`{7~te|~nW@L3os7rysF9TtLv!+xWQf~4i*6H>H~k_6y{ zcz3ldT?gt@N%@Sbi>Sb`JJeG=XPMa{Y`<9rDcTAR^)?-YjpO$ngCD{uxbUkPqX8>* z99`FCF`2eE`{yb~ABB3JuWWi4cq#Ssr>*0Mck)aj5Bto>7m9x6v*nlmwqHAV4bO^Pjt{NW<;yip&UuD>7J5 zWhR!T1X{ev27~h<5N06B$ao24KpZPvdo{z1Ov4k}{E7sneuQmsylh?NH9^ zUhs)?b$%U2NS9(#SXWV*gpmopdLqv!*b#QPi0%3TM!OK&>$L>Z2#~^&dm{x zI(45!*NAa%(<6dqlraP%_wMDMhDHnoJ>@B1JO=pFVe4ATD2hPyX?rAu96$iz+^cWTd4Nd{LOghb&K#J<<-Ar_Qd00>s_+A(KpC;9aS~!zj&;iN|wUzX! z1#6ZLTe=WEWUM`~JmDJ+11na(s4jw>1`$M@njo+jk509on7xK5Yy}0Si54*c7oQ%ZHfWx~>9xgUQeO-wfxIL5=;+$*b&vhWjjNOa_l(QC zjZ~h`&dzb48JnnheQ$C^b~G=%19Fvdv20zb%WRv(9M~6W{Ikomsw%O8HBLnwgn@8YdwxYA#ixzFU(U zz5N|fyQYSvem^*NjAS+d@_zkm^hh+CZ?37GEwm;2@v`;1`=&m5{nn{ql~405wx(Rq z);WvkG-tEZVzbq3GwV%iys%x{)83rYUb-`s^H<%%KCL0?PNlO}g%fqw7GCv~*xS9< zW&QCftdG+b$Lk@vBCS63_Ml5mvvdN4_md-8f|n0yW^;^}(_X|T0-wCBdggEuhDYwRjJU3x$^=_1E2fdk9vclI zdzu^FCNQ=;y4`Z@td#Uz*~iBwEqzPyg$yD>a5OdeqLlik;9yLu*RSmc4p8MKN~rPi4LJYR)jyaeLsR|@x_f7yyZl9t15g|mL6 zQZAf??5VX>)=En7cIC}Bn)dKTY>_dAcu#{?c`o#?% zrg{E-!m134>YR9KGp9LI5hx_2os}8gdQWT|94SThf)+MH^7SWS7c|OXA7+IknKST2~<9o+4R)j@a;PAz^ z#k6}wsu!0*I$`2`u=zwEw^HNwiWB^<3V&brrd+|}8T}~p8tqy%x_oHZmm1Yy7xb?B zqxNJYjjKwqq+f-Ojd|Xh<2cRP`7Td*_WL|#N2TwAs@H?n5&zQX$QVEK*>;z%r>`Rw z?SIYsQa3xDi;C`z&6PP1Rp|u;Z}15OZ!hmW;Y%N{Sa8fReo9i8v zU)@p2{ITXge*a_kja+WIIJ;D3TK8*K{*}YXZ`MsV-~j13pX&snfSo^THXeix<(Bk* zGvl1O3~!VBPs#$B!da_{g&dzU4@l;y^6+txkB>;W~J3(F|dNPD2@f5LyF6LgTay`?*eZw7~-q6vLjA#btzlq za$8BNoHc6M+U6`k^yKeX^A{LzGzLkgUQxi(n)T!^>x+qv_wvC_F+=gEOUaRrnYl+3)+xB$`|*@)Pfbw?ZeoqPpfu;6ZgBM8#;nW8xOMNe zZfod}xj^u&ThxOG*>PO2b56NgG=q{3o)x(FVONmJ*#-UbX5sFnLh6%!fusAM@}DVQ zrR7m61t~o(3Zdwz3y0Ie5)!mb z8cgZ5S`|Dcr@~{h{Ea;ohA9U($XC#hn8=-Lh9~of$huK`wDUcdRW_Xg+kPp=)fesB z@6!g}XzNw@snhff=zac*o0;wj!V&{NvtmG!f6_Ftwj()U3;WJ;w$h^ML&f1*;@ibQ z+Lfrfg^qG%s0RYZ_M+p=)Fo?V z-a|}4z-NE@Z9k%yt}dsu>ry#tir5^Jijc!R2HH-Vw zpwSXLvC*zJ&Bk*fqFfKzCeH>|WRk$Ewm;~0j+dn3I@8kD3w_sf?BfW)PIn11jN^iSYlo5;xggBHT(<2uY8RZC+vIM{OI#mwii@)stgB;{vUICe~S^^e)8pH)v|*4T?h zPQ?uy5n@gHVuhWD>X21@eNx?helyGtS&0YxRm;)Ciq3Z7tX-*+^cN>1E168T?emQf0jOYy{3_J3))GIzMxN}%xoJ5J6ZKo--Lx*LaGMbkZEBZ2nOb6n z+vO23gxZBM>68rV6oP4l9a%p4F;u=yq~?NId-F z(-zVSNVRJet2yN;q(1qLobfIre z;A1_kK7x%RMTu-9%DjJg=k7u!oCP2IYd6aQMVOD9Si7CyXW)HznTdmS>dZ)_w5~?V zf-Mis)q)7RlKWx!A=bVbjohZO$kg0uh1#_;kOk;bqY(@ts+yODUjG(HrGGspy=Ub? zeRI5pC^{&%eT?C}ygjqHKR*kvMe?^Z+2deRAPz>H=mir2N|Jyb7d;E_p{)T6yIymG z9TY%_G#KmE_|xISS3Yp~^LzkH&_dAvbfyG#?=Qh>k=mS_m)XMr@X8P|q$lR;Xs-^lZv35RD zEg+^lNKEf8ixt#(^aS;cdfqhtJy4(U9eS_5{<)OVQWq}Hp}M@W(K->G=OaDE9BVcb zFL>~k3%vjgE}f)17p#*1xoBC-2Kw^J77~dRziNj2Z`T{G^B1#vw(=!g0(vqeBU!!b z*4QPdnxK6@t&4H-(cz$#uhahBN(6`kvmQToGRkKv*3v*$N}EyuxiqPimogc7bZH=( zN%C{#`>29OTpvmNcOwAtF;tG$`|L$gtXu+XnCDE$isjn-GRd}0QN&XpVAn{TzPZlW z#_!${bND+Fh8`vY@~IYI9psFCPu4q-fSw6)VBM%EE!CH;W6ySdd0$*?VqsxHZoTyk zlF}Tt`^(1Ch;YdXh#NZj3k^^d+~%r5BZFV4oX|O|E5XH)v{wNMI4Ter#Cg~Vb|#>0 zCr45R51N(Gg}{LUB%CgG*z6(era#wPW7Uh0bSs$43+_z#ZMB$`zaozOJL)$+5B&1q zMqoDK@L6QAr$I`&n*~VW`FEp45QJ>e*{EMh-0}HNKA--+T2ZfUYO0S@AAajox(?i* zGII+HihQ~Ff0n}0$vAvO#EFAHFV*$27K!~mrfLc|=$>Cq2uq=yB!@nhphZ0e#bO~T zr@u;oUj<+t!+mSfU51)FgsGRPZ#!#~%OJ3rAHe?v;ECAZuf%+&6HG4$P29B88!qCd z7Wvju*UG*l94mc~m$`Q#SG{}hXxs6;o z&5BNfJ6BAcyNKwJ8P82;Sob=V*uP zX)WpxWNW2c*Ole#KAOx67RIW^LK|E&7$sl*vKd2Yq4#CBUq7F_qIXqc@ilM>ov5fN z^D_Hzkn8`Oy!aWcg9Wxq+8ld-l0=KVqW_8|0$5$ji^UZr_FFovNa=79;>AQ6sjV*{ zfJ69}xMjkAzD@hwZEo>lN3tRtMm3_IAmOo;oU=l#1#8O+B>4-Ai+z^gf1hIh)6Q40 z`-`tc4ux|_C6LKvDgdxt=DYLq*}tI7(x0Ev@PckooBay`TVP|eKh$3d((d7!D~H%O zG>DoR<8^hcq%{F(JC9aJY85(L<}>Vg*7pxc`zv+6z&w%^6U#TvX2}XZV#%sU{n9v~ zs>xScuf+<6s5sa^quxTM!1VO=D5~+43O9?+&d$BD?Z*7;-ZPH@DRHttj^R%`X1S{0 zXFe}!A%f5Su7QEUZ6@Je2E6Iz+13;gcmG1aT2ed1yYJt$Z^iqP;vHcp4u_joJnHN= zfREyVYeIqAvSxYk^6}j>Gkf1E7;L%NUuud%p=NBDBWMs~d!ZR54CLJVqXkqh>5RpR zAPJaj2*+U}DB&vK#rtmKjaF)zcYu`B*PW+V1z-%oXUzjd;4*)xbzwC@1^sQ(1fB|8gv_pQ&n%I@ZPJc!<|-4g^4-P1rFYg^rJrMu}GgIqtitYtFs2G|)o zJO_@7mCr9Na%X3hoI>8**QFya>Jj*Q8)7&K?i4*h{UTYu{jszIpw$Z`5WuCt5j0>fd!#mPB$zG){MAVq1F+B!Pq_)7^%NiIM+kd&1CeZGeQ z@+oUGOpvAl<~1KdP<2WH-y9uHH?V4NXM+uKST}eAp;1G%j}Dhj>LlWUIwT!RJKbubRPj|#vrgeU^%1+m$KGAFv4 z0X(!PEam-=R4@?e4(`!_)rRlJ43H3pY{32jm81^PUD=?3$&=uN_gaG_3dhOuvbU!P ze?&cy=!g4N;PIx*1%b3(16gO%3Te@2qZ94|W`Nw@K2L#rHprp`o;PQurltZq$gT|nOU0g!ZhdtX4{ThA=4SD2Gk;xCqhh6bxTzvXU_ z-9K7w8bVnBI+gbZd+ucH%O;QHwY+=beD9u^j*jA@790! zzA@f-&oLZ&&OUqZwLa_fnV&i5Ugw0XD9K=%^pXgJ*IGi?NBJRh#8CY6(SlM z+RvXq#gvq=dt)dgH;=iXUxsl+7c(S8O3~+Pg?8vfPkqeDJeu)#pL*VbZy&dtr8yGX`H`gg5{txO-1Y}kzEn^r@~kEh!o2(7KH zRR}i9uIxSHbgPWv9xm(KIV52*spoZA;CuP@8zMhHzoqb`$1sg~wazgZ4Q`g-C8-1f z_{;?fINS5~pq>MKlq9-*gv4!_#_eKKd;M%gGz`iv3%gEi4kP;S-@mKuW?w~7@PC`k zEUv+pH>xuJQ20E8HUbRB`w5=7sw&=L(;h1U5mB9Y7EjPbR|tl%SuRN~Y^=|p;QiUy zSOtUSe(~ak@57Q!+|AwD$VsWueFWUU3kczL-X68bZe;~fadUGU2Aq-7gE)^>8i{`X z{Mpqy9;}GuhnHMj1V#b(+`Unx>!fTOZ|2_~&4-4%(_hw3Ep_AZgCXq@i zrb}rscD!0eRe~~|YWNz9p^v}bogT)UI>P3yIxmSBNyPG6Qq^HU452Z3f#k$ zC?a>IW<;d>yY;xzm29~|b7HG1?B-iEbUL**gk@TLJ2aC*KK!t2XDl|k?TwC(I^9ed zK0e&5s;I0SHG%m;;WKj?XBwRInq_PT5?Sn({FGpqSdv8Cn<{rfDQ;;I%AA6YcsCII z*CTF?#s$2{jVUfG3&w-QPzr7y=|bTXXYG2Sc=U1c@gI77rG2j!J%kATiTrLadzAf8 z@hSM6AmG|T-K*Q5)>DTpFsVf-NJ&YnX6lUU7v1% zV-Kw1P{p%Nivp~2ab|?v((7 z{cYCcHFa@iWm0z-j=YV{u9T>uq-3zr&32yJ+qXMsA_sG|@)d12HXLNp{s9KVmba6Y z{?BS_Yv2F*iMKOfU%9-u+8dqbBd@GXOy8mIBWW2C8{3!W*cQGsUMMl`_R^@0LeS$d z7)(ys^II8OFQyBUjaO-<*e1QZKGHb2OJGO`Xde`;mR|1Dg7b2_%l8pTMPtVRq_H-!m zo;>-uF_2^irr!W=Rqb_VvE8(^b8kQQ9gUpFuA{=B*;gy`Io%`Eix=>ck~bH7TUQ!= z(R$f-B_;ilY;BD0%?4nV7{DO#f;wp+lBxu%1X8`R)NGC|C(=ph4T$SgcLKF7EH7Uw zOqJ_BcXM-lW&Jz<>~Kh3QygL1X8 zwZ$VRkCaPb=(OT89WPKc6z*yDzUVSfGC)=wukye5*3j1WIT*0xp=V~sc&?N#W?(?s z)!hw?=#Rd>_2#3t?l!;M;TwKsaI5XHJUnXZfT`3pb8~Z(zsIX81V{jarTzWeEFY1B z;1E!-B|)|I4-CLTR8>`BK&+!fqM<3}?Je-FrpBaxUR6T_F==3<_VN7OHHu1@Y;|?@ z=g5c*0P}RE!fC%yEb>sVvrTbTf`x#``IpuBf|#o-*Xil06G+X33}Y}rZTGBTofcWpj@`Qlbm!TI&; zSCjMYF;MMd{{ABF2Xh;ondR3c&z?O4VI~psWDmt8{Rk#)43J@KazJ{*9twSDVR0io z=DbeCIBym!%)pbT2uqLZ>gof3{nxKQ-ff9K;-w93ax3*&)ci_N7GVM)V|bMXN)gQ9 zC+CO|z3AihNNcYtHy77W1CPaL;P~nM@`Kjy?ygWkX<6Cl(u$`3QW?w5!4%VKoj)G! zw;2n5SBq-8y1U$lR{x8hW1O#+7G`~EPPn0z?%iC^ZZL090vaFvspDKHsNtKqL z|J3Vjh}UoT*T6smhgq+u@Rkgfm*no=o_TjD)~C;(CvvWKmLDJLxwyFg3T+i;`RnH9 z<+UD)n)PA=(EHr}a8ddGsP*cmr7qxhepx6Ml#`+VwYjyGmDNQfPwe#WcwubI!_A(W zki}2H zA?v*E?#)RSeY%lk6fwT@S@hu~)|4l0{-Pl{ndJIrud-*y^^Kun=7P^r>-+nQ=>h7; zzcMfkRUH2_67WzH92)9P#c4bBvbeM~;q>&qvp~i-(;oB`(SWv=`^$OL`uVRF6-pp} z^dJoky*3D8M|>}5E!Uj*{*2~Gd`e8*tgQqHz{JW*R5;EA7A_H#`hWzE@KaD>xz?#I z=fC9*O<2LwXjK}rV24S7Bzy+iL>7#)oc>*p^|5&XB!kYx#3Zxt?s_C3F*Fp}Yb(R! z-P(`v_d8#c`txE%UO2X07i?j)@zeO<>=b6Qo&HLAKHwN|s|T>}3Aim=``tGf)PFPX zgqM?(!;?~d`s$Th+ufhq+h7ZbZ)y_Y_x)RiNyhcl1-+WTjF6Bpyn8h+nw+;E(4dVFV#ByUf8MnR z7&ba%K%>PY$m_@TTwTBKFEpy_vnJhb%ZS=s%_QC)HY<~nk-Y2A)hd4y)=dlg(ZgQ*!@!w22{knvKwYBd z#luPFNs4?f#f0aXo?WHStpRCLGcaJ8oSgiWmbQ2OsRb~}{I)j3I)}vMM{O@P_stl%6fV>*v(XlgPTW@aCDV!oJT4s4&K!d}=20&-*v8n9` zUDnTVUhuF%0<@Hk)~lri&~ru(r1Mk_Jy(&e?d(23J{&y~)y%#G!2iu{PfHnU?YNQw z@_k}zs=v;DzNJ@@DS{4kpoISZH=rC}*-S8wET;x(oY~GVoE0!e6l&W6@Ddqu16)H* zTl+cq=vku;I}?+fnwpw#Z}=kU2(b4n+ips>_=dPOG&O$_8~Lu{{|i8tFMz%M)i7$J z(#aPfx@X=jj(`?{Vok&m`P)%SJYvPw5BM(I#l^+xN)V1We1J>bZ!X0p&vh)hH*eN- zSR59HXM`e&@bQef$N+nQmr_+RGFqWQRldf-67m7rP(Ul zMb7K+15i+m(#hRMdoWWz&tsU@VfQ>X28>MeELSR0$Sft**09}Q7@T*gSyKQ5N?1EB zP)J$35E}MK|2k&%1B-V7k~angFy6CgBbqc25-a*Z0w^+!3B8t(FySII;YkD39^~Ad zn{UFe;Ju1wY+71c&(#oO70@xf(xJuxzGvF}1OXvoci-0m)2-}t1au68nDu;ufljB@ zn;V=Rmzfz;RaNCKx(s^n=fuRDC=LNIW(sVSC;0e&4;kOTHdX*2Q#P=jDmy720A%BE zp^-gB!SkSYx~1a5rBVOy`MC1f%nTF_pW&}iQl3og+DRX^jJf$U$>4RHa9ykA=UtBO+b{-mvd3h)uz_a<;MU#8+iGLZy((g$A?^I|-&CLJT<>nJ=;qW?rhe^SQE1frHd+cU= zq;7A|;%XoxD+^UFvX2GurI5@H_x9~u7_)G9=K-NNW+(Aony>rUu{FUA6vtb3Bch@t zb#=)GEjTwvvSI<2E^TqgfHPjdh99)3nSKBYz0lIN!PES`r;A;Qec53 z5wXJ%K#1^jhqso0rMNLgzz21%?AQ+t4fO=VA^1{hYiI=5+D?0W%Y<()c-q7kA%%5+ z0A)?oRAII|Q4FHTw`B&4R90%YVfGHVisQXxKaP|grl z#%5}o@)nm~jRh8ia%NPuS>GB!F$oY~7zALFzRDjrdKg;mwWwK5)N|;}77qek@Sqk8 ztYWP2B?;g;s~`bgwv-z5Srt-VA;rhX9|(kYuk!7=#Kgviq*H0-324!~ZZ=<_!!hC! zMM!v^ryB!+Ds%YXc`Bsyoun7qm1!}*k(Y-_nuP`ZtlO}#Ff%|)0r?hm-`{qvRP_1L z+k2Cq7#a$= zJufeDNJz-cH}e50EH#)c#8(#ju)S?HVt=+dBw=B}5D^hk<+v=OJn}<^+7Aef5Tl3F zBtY+Dr~NmF(!xNldfB))>a(W}0bO+I&7zw*Y-eXDWLp_Ez9T6ifdEPXbjEczqr&lU zAT7nlxnT4~rqZz0m&%bRt+i_;kwp(Sct{7}v225x!t=}u#U&A-P++2pM8ua*pOsjv zLLUtzsL|aTv8=rO+1z6^?-Ep=wGpu|hVo2kYxuoDhLxRN@UM5TZEQ-WUF;v0BXT{3 zscUAV_+2(#twz0NYyk_4U2)=bf)V9ALHmlF)}V#(pV3()xtue0JVM7E2s&-P{4klM zEOLzqe6S9n&H#3>gJgO7>{(7r%L3=rCQuMK4sF*Kz{z<6%fjDZyYaoKWyeVL`buf(=2V_bw zutJW91&^}7eS4B*=*?8oxJd@-0A%$~iit2d7dRi?ITB%2fC6i1X_x=*-d|-!X)NVz@s3LYsZ^uj;2d3UdepUbW;@ukpNQg z!JyywAss^jTdtLr&LEg0qM_L{Q*Aa@s?DaM zq0!+`4~z$x&Csw@kOsr}GJ|HKqqbXCAWu2G&#m-bhPXiq!Q4DB#NkrQf(;kHweaWc zzJHxSJ+1W+8~w->_Nj7Smwsn%z6z*6ilO(;Gd>FJ1z73wI;4+GekA@4V(UDv$ZK-? z=ht5l++%=kt3W5i#toDR2M`B?S)$a`zQ>*Xed&0r_`~!)(RzD&zP>F&*MAZ+(OA+f zOX_sXPfrKf20-z)ZjSFPHrKc{ry(U&r;&CaQmN$cGTree7Uj7Bl*mH;!v0+QY z#>y%VQ0HFTC(4jh5FI%?p0ov*5#dZZ^0ck^o3%(bp&ET&>h`84-NV)^L*R{`c*V5( zTqVNzf-Qry!Sn`j6)sYpDV!>)ufUmJL%rpDO+asJ_C}L$rQlnGs(^`1fKaC?Zvp9C zI-S%u0=yU3U>YWESey4>jhNx%<8OdfY#u3_2P_p-3Ik|uA}%C7Jw3kw7#^m~HgDX1 zfA{MNsgP&Yw67u)6Vo@(6Vu9+Avc4h3_%ZX3o6IPN9*wdIe^|}`i;MBl^7tO5)$Zv z{{99KxAM9>HTDdq(H0%9grbG+L9?fu4^5yV2s zJJ24~R8?uI1MaFVsfwo@Y~M};)$@#vZNzqMYIE8XIJ=qrgSu_K)c${S{`~plX;=v) z$r~@PM&G-WzE8=?+gBq`c^y79Feye7W5Z%4Gb#e=zloU;I98d|*;jOt-VRZr| zkTtT(%6%X@D!{x^9LL4OgDV{0aVeX!;mc|Q-`oHHdim<6IS|7f_(;jZK0JVI$0sBNef~_q7_oNr4f?Sc zWJHcN88CmsD%&5(QVXt|^I5wugSPL^|6EGo;q6RU!Ytf-1NksTKqmm)aaxRIMh^}u z@c2p+Lwig+0o^(Vg2ohP>*|(2u`yA^kJJ`cXk`6Z)o~ z{r&yNpoCX}uqUU$4r?FaceE3QHYs`7it6HPh}!@s6b8FaJ%rj-a8GwlK~w5x3%HgC z1^0wCX<&K7m1G`=?h)fx?Lglcca309=v3D+ogO=i{J} zKCl)_%=qJ8YqQr=Z#^LpR%2^9h6gcn2$X`8S9zMn2(J?-KLp5 zO?s#2_@;L9!*rX#M-~)j6~{fpErakob60oxO~WbTAWleNiv- z8A2uOH(VI$bXq()V4lRxfxD>2i~#IH#AOFEGDz>@$bi!gY@i^ZptQ(4-$!C8x7MUB z9!eU}2>l4W*4{GiihDgGx+D6>pK-5g0`Vxs9xl}S-q*|~)I5?CChOk-KB612Pmq>=%JT+HSiTO zHXubv_}}L%({DTN=p0v;GnUvLtD8#_P?>h9dqhcls|AlFhk*wd`tN@|;{)bMRtd;Z zf}ilM_uFNGyYZ>~E)3F9BxAeFhPS(@N;L%O0ZN-N%t8;k+ zSVkFT*HLfLXMdbX=XcR<^*h)leY^}dJbaXZY)u-MBpLKJxIGljJG?>hhHxQut?Vbjm>cfh47G^M z?dZIuuTtng2q|S#>@F5S;h|)dQEbKUzUeWMeDqYHULwDUW^JjvLDjz#C;Cj14<84= za>-o7k?;7jWS7G8wA_HvYHX#YwGP5>J=WlECy*a``%P0y94Z$gu4?=${SmkMzR&h} zyHpTCBvdk#>^O`%R`RHU-M6WgM6=fY&Pq2RAW7;^(1FYfzu{6~dw?q>?JCK~yHeXu zB&NBdzjH9Ym4m6CSPQ%?5s2BrkOq+{GJ1o^fTb9UT4mMaUn!02bmd3ESkm($_ej^z zSD^uK)-M;2%2X1}BP_ypZ2S0thlf!t>xUCqoU!fR=ESrAz87E=*G3ICmDf=(Vm|Bm zu~Oo2mdZw;h5Xt+OjU}&l4@NDq$vmc{Z`kbiv zq3I&!7c0??xR6xVzxTF+CyOU!PbE`M#^261a6LyWDe+{*n1m}^@TI7%WD2b5Q5dI3 z{;|$`_P)9~n=HV~k&S}Y!!PMPwy)B$zvuaUq^i~;lv%6=hYc&TwPCFluqLssbW#@L8b_cOJ z$|y#LHZE;;F_*@Czip*CC9b3XFzufn32|!rJMdD6Ll%id6E5^JIs`n^Ht;N29MB6+ z(-b-ll4q|{=0YFXJDI&S)zG6PnR!mOOr8`&&US>MiL|7zzS8(jN|m4};v*@&8Xlp= zkBYp>8P{xh>+sbE8wIj9UJ-=r=X+HqSw7|8*xWNEPl}QC-K@!SyQBWTw?unkdr&3F zO;KqFfhbVSVdQ;#Qsv-CT1QzTX%K{e?=7>6qW;SHV1|q0Wl2x(ec`l-ZN=(fwe(QE zGWF8Dfu1-w&rzkAjVWIRRYK@dw0z&{ny%f9_UiRBasW9iib=*Qx?DVzMIDxcN?kh> zPF@adgH#z2D2^1NeTMf$UliMKQe&O3M-8EouhxxKS+KLpLVjh}HDxoXW}DsO=^w~E z8sf1tqQ7B@uHvZ^^;9)Bi9=_nWgr~GlV(I$(NJC94K^=N;SnNyTRiqt)Vk-fwHCuo zk-FbL>_zpJ4mVZf^%K}^wYI*j{b4$C*eYVDPxM!ZUQJV&nRd59k}gz^Gb?;|VY zcL}wq9Hi{_p6I<;Z@NH^wHi+UW#nvIG6)Uub4RVyQZm6s!lhi6Z71dec6yCEV+**j5^iV4I^x6etj4&{Xwgkvg7*Q(bHGDxOy@X-@7 zRp+p$VQwejVr!~qmr$m$X4lT81~k@|u?*UYnJS=a5H?sSP=kK3agqF2mJ_q&W04a+ zX2}{iPi}q_o|e*r>>^|y$MM6Qe-s67EI>twuE;WAG&TizLg74yhI{iD2uL)(TH}7j z4)R_td~kfLcgE%{r5cy=1WB8@Qo<{ma+y$fbl8MmQatQHX3J);|B}AcgMg~RA6Eex ziOugl>TQhPoF953JbsWyptH?K`e$xG-$ZvkOL5kz-r?KdYDkqBM`BgPCCcrvv#~7; zM1!bIuk4R%-bQ@H9$J1(V6I_j;zepkFFENa4s*`^?e}Hi_~FF!FG{z7)1ONMk0u_2 z1#Mz}1A$C`OSYKwGW!d!Z>_n9QGgXkNF>!$@5rrs9v9?5u{l>KCuOSLl~a^sNs*{y z;?c!GGXwVqG@XM{cr*Q~o}Wu&TF`{2WIK&u=gi~kMoATdc>Tq=2ys;nTiQ&lsOg>50Ya`|XAbD^c&BkhL7I?l=<4 zh_A~O-Ou$RTYDW;k3n}30}-hLcwCZe+f`QqnAS(f@VwG0_ z5+eGh(x4dd^7bCo->b*PQ+!A$wBys5xM3^5e7^(VtRB{xO+zd2qnT9-F12X@iv{@| znWjhUF4BIYXhH!74>cnSM4wlLQgyX0qg;AYxaf0{pxN!1Gja31Ec6wuDU30*Vjem2 z#h8=FxKPuN{0)!@ykJuMcC9b7KTu4$~ozcGThj$M87cmuo8h5$2{}}b|(B{#UMlV_3rEvF(q9ts$@=* zCP-MM?a8k?x-7W|XcK7I!;Q%YeJtv?^uioZ0zaD^Nq2xMOgtMu&6$x`TRjmgH!%OYFX)yJi|kSzU_ zXs~STTi@Cs3+*DYpLI@lwXbK?jZbd4i}wztC%*28+)p*J;yTA>@YOH}^c3L#l#7JT z3U-5;bTW}8HADl%N?&IZ69$A{OobnJwkU8A^f$Z5Nmp&%BNb$*2T}2<_LHNTbKf~_ zO6zSsZFX4itad@DGCz7Ha0&;hyBSm+sC=Cs{PEirvz)RWQ$hh*fg(NX`wyo|xJnK@ z3k8yNOo%L$YkeT&`RW8;H2gI&U@z991d!f>52q#Y?+R~}XyG8(NyvGsA(7)%h?G*2 z`M?VTdlJuWX_1hSU{3vrCaDXR(Lo`2c=G$4x*Cr`G9=B|WxJ@X)<@(=`J(7VS+2G| ziH4N8c{!z9^e|5!cW@ZViqmfS13Efq=_pcpMM(vtn3@7=mqZj}{miJb=>4iTL^6at zo&Y~$4w?9ctv#;u2*v*IA6q(LwmWkW5-fDJwQgJsp6t4@kT;Q7y3CccvoT|HE+X@e zrf}X1le_`VJ}+N=qWAU201lO^`uM|!+%TBhRP{Y-)TP9SqvR@1AjHZ`f#mCQ#T{7@}BxT^y-E;Wty3UX}f&}26Q9vC{0BJCSj ztZ|w1UIZ5x((I244i-Z1IoC~iiglXmAgS1ln5yF8K||rtk8+W?&zb-&8aeima;2)E zOoH2Hg>Gk3Nu#sELeVMhD(1SKigEALSH=4RDixfXZOaO+Xk6At7l0iXmzJby!ZvWZ zX(nibxRw{oM6`k{|C#XfNQ&|m$gC~%D_-oehd9wb*j zKza@gzTE7!hi`6SO#UbC7<4SJ36{r=yTPIn*rm3)h8Q(1e%$hV$BzAmf}Rli*96^= zVqVN3L|APxEbz!2*DMa5K%bLY)_L(IU2bNS_zPU8p|b2gw;Hg0l>wP4U_JHiBK-6a za2QkgW)`v6S4!$MHzl*Z9a1+(%jf56$8ONOxSENkZ|L3YfK|}Z9 zwiZa-6=@)qFxC8fxoeuT689`PUaQudveUfqp~0l37193ML`JUSv8{4^-a!@xB^4lu zPe}=cxLt>-_#BHJPbk9$iT78+M)0S`foPq;4d*0J+H5)=S#lB@mRHp{FJTH5o^+*GbQ zXf_=6IZNkPo8C?sFd-ulXiCkdPBLDfVtm(k7O$CIEvt6*%*tV6B6+wK%{CoppwRzb zB`?C;%AojqakZPT+LW{`42S+@EzbMHM}E^VgO3v_HBM{y9&FxN6MpQnNbr}kZfqbn zO~sqzUYGso6I248;tv_j5n#{6;;q(a*6V!=?-QvjBoW2F61JCOs{j_ZE2*W=J17{X z>3{TK1Qz*`IEx52iWM>ZXE)v{NZweL&26+u-Evovw z1f>+fY{U_b82$53!+sH7T>*1VI!f|T0>^AiqWwc@9dd;Lb2>M-%J1K_*eDc47+@@G zt|%E*OT|!1Ap(hq+W@@8RP??0Zj~r$zEWjci_u!yRCc`QO93M;`et@7)hq@BfzC-S zuU@=(VZo_pITV`9ShVmRZ+k5=K*aBG3Ztq0qZ)G^MOnx1S|kdt-$<}H6-j(zF#2%Z zmXfJ%)-)d8RloeP{Lj80u1tyrVt_I zJ$sVqjUqB{xPSD%Nm%KpoTMbBX+(^)rww&5xBaT$O$RQ~t{XK{$7;ai0A(&jY+AL} zOppL#!c=@9l9*}uvO~`IQO5N6Q0;meGm2i?0b#m+T1E} z`;k}3~Fb1q8x)=@FNKMNl* z_2kfA@Yn~Jw@_>bBc3er$P$mpX@1 z=h~Y?L?CWzz0iMEhU96b*^G4sWfL zluC@9n#(>uV38afd3gCt+VGGowLG%M1RVR{_UYh#5HFG0W8JeTHZdVNP7sWLWly&M zMN?(8Wk0y<*EWc$emQ67%-?QDkMCslRaRrV&E;WdDD~rL;dBX@C*yJz zRZe)g)6_(Vd2ObfD9ddB08LX&f4QDetRS<2%t?Q&%H7GEWK&b)h?r1^nr^MPV;lI+ zY5?jdgWQtbYD|%_q!*rbJ8y@ycYB|sN{ieNN{KS4#!e28xazGIme2P%hzf6M2p-}< zR0#4Fvf!ZErt3E`NVIr-EJS=Pl#iXV6HkH&GSssx(==O|aX|0ES#za(S$mIvkEiN{ zA$J(kARn5S(|~%>7kk@Gv76&LNtK~K;8t_9)%iZBHX=7E9n332WWZyAQ%@ zf2Js>Kv$>RHN{jTz)hVVLYGr$q1m$lTc_QSkeQmYn|uKypraFABVj_{b*q7hVa1Ap zO}`S{VI36Y7$lgedy^Q&)4n)s?{jVX+_SD>TZ>o`k&CIy1k2KpAgk7Xac*16Imrr{ zvvb{Qx7m;O^#~AGMG3SZP_G7vm}f5w zJU~q71nf_J2JQ^tGvqd^{v1L<$w|w;XZwLg-3cbb3h6OE_CS*qh7Te@irRFmd7~ZB z1DhZtBfG3>#|oG^Wj-Tgd5J`2aM4-%lcc+zRiUddzX)bLyAt6sBnZYDnb5NtRkDp2 zhv^?GLWjFU4MGlWGX?pMsF1M#i&u4Nrglv{-a3(s7(7z`#ee$OZdM)I-r9%K->sBZ6I&odly#Spj2n|^s z6_XZ^JtS02HH{71I2oPiAGgf6j(3t$%^{0oCmF&u2&&jq$%Zt!3kY#YA`wY_fEQE6 zQo&?k?8PlY=G2%(1)pRTk|T(Mb-IrsxeeX%xni1FxUj2Rjb~wwazB@Jg~~?a_OKK3 zeJ;3C3bA1q=T0?Y4-w-|%`QP6nc>Y`!}v#P#U> zYF#vh?4Jv0zH((GYDP17WWP#YXw``5$M+Ejs{m!3VMHg;3_+(S$b*;Sem%(z-4=6+ zB>-Pz`5)^Q&fmBjuw-Hf%%CbeH=6T_ZYZACi1$-f-B=2pah6Mz-M$Wci!a(2yPzI^j zcL9=T`SQYPJqquk0y->l_8teF{LSg}hub}OAmsk8+tb6kvJVD}=V8#1aj}Q1rzn2d z)Ahqr7h|5{zazw9C$lkYkt+5hU1Q(;d%jXNC9wFQSH}$qnq#q=xfICl!;Zpi6OBVWMyN^M{ME zov&1oVHEsqA(t*L#R^NVuuj}3!y8n9@R28yZgH~<^Rr?ulV`=?@KD%JuSX*jdJGSS z&Y6;A%)aaR35Uj~qv4}!bI1f}cyaVs@HM}Z+}Ejz-nbClRqglL*3}C^AO$#58y67p z33tmBbvQ%bg`0Xq#NW(WX`N=B!ysV2pB}fqPz|q*znu@DFVde#fv zg@31h0sKs4SvAVG@2ii4Iz)jiHR_9=Qu;-;hki;3Zh{9+WB1qf3vQX~^7nAyp5#g1 zsD}E7AD~U6^R^gHFuOUL%f~;TDdJ;u<#6X3+o_)4AQbL|M3nDWA@A-PWe;OXQTsO7 zV~MHu@B=Ih3hR}aq(L???|~9r1w%EgzdE7}d7F3pcVYRRhV$NcIIGl>$vp79bgy)l z+%4xnH~p6~cy1wJY{t9C1n2Vn!d@+nE8K+Gj*fn^@r>o|ny0;t7XGR0QmKRrC zCom^m%D+NYGz_;%i~_?@RoolnZVsKY826Aok2a4NL$vTBFq0AQX3j*NiWylz2HtiTrvI6d#3h7HrNe8Q}x%H20Dr(-KCcRMXQca z@35H|i@W=Wo;w}*s+V;PGneW&e8s5FXkO5Bz z-u;jc^*kMvNsG-ZYkHvD{7uwZ?DZw4;Bhel_QlEgfba&hPpAc0%AMcwihmvukMvLu zk7FfSu7wV8vfVge5@mJ`cWeFyliS|*`VawA8h3?ElHnC5H_#AzSPZNoa>luA3X#Tfn<}{WFruRXqtR6*d3+W58B8(c~jtL7#3B$pnb{)GNxIM~E z44$uE*>*E@@v{wov{yQEG6?3I*+C2AQa)sO0Rgcxc*@=dS0qsxfRFq_d zQ8%pI#yeeX1CplA;O(YqB*aBZlr(%WD<~vaLNctPo-%)GxU~W!GWRn1eE3#Hks)hd zYMQ15TWsOT5r@QemvgkbTmn&9j+1o`|-@ zvZe4}ghdsNntn^Pxi$-q+|C4jnN0#hZGlBUQ0C4y>fJ_)9Z2BEpFR^Fchw$b`fVsk=6~(h)I*T4N>)}TH}C{i$cozh1x=*gA)N7;D-6@e9LWZUvW4cL zJeN_0V6xRwYUekPP`)->zJVz57d;*c?i5^6(|1Xf&Cfy6NwSD|0(WWU#SUDNl#?0( zUluhMB3z1)bZD#%J?U6h&QIH0WHM(l9ZL&x% zFI95d0EF-zaM|M1NO5Z9dVnw=b#;n@R}GX;^+oEswea?Sa4WFzQ`=o#xnCTNhIMkShg!V$glnYx%dCP5@^*W&q+!Ea|F@ zLyw2Vpzg}mrG86OW{q()juKj>QhCb=!G63oI>beSG{uxq&gVc-<>lC#&b`Ve ziO;As@IL0xO0EvYf#~cS=S)3<{y`g@Hv&HvK>Pvry8uZN9*rusW_@qx<31OU6y@p(m7;L;+CJINI(J)n1x&2A&jrtZ1ZR=fV=$a3snVb9=vmPS92} zAy}4})mw({Q}FS}u6om)Y;Jipj+Qo-*nB#h34%1huLeLXB&O3JSVLGL z1}@?nNOWE9(TtCN%FE1rSr{LHfC+2Og-0{%Tb;Q*qGR zTU<>Pntg}oyR`p9<7Zu;V(pD>?b^n<-&06lQU|Kf*LSL_yG->_7~Sy1wAW1BN=tRF z7~P?xnf1~g)#G&Ps&qJ~661l0#c#6rFtauxS(BYH(6!urVhrrTY)15xYWWsAr4svz ztv%48-owgBS3)?Q2*cDREaxgJ1y?c1{D^91j^mm5{fSu>5dn?GPZcr5jpeeC?^~`5 zv#-^jE4@|g1I{7lm8k2g=*^)-e$ON)Ji8Je5`m=wi{vz}uob&0GS10O+SF)Zzx}*1 z8(WZ7>g`#X58DH?VkBI%qesA$ffh6RvO&at^O5+}$RV;TWi$1ou$}o=`@_B((J+Ls zwp#Rp%ovybZzeXWch6n$hZZ>*$O4j$>;Sgr@5pQOxp>vDcu|u^g~O{2Ew$?K9`)88 zjh@d6*PXMZ|8+_k(O2U3xiapK4cH#H)vUW={_Lm|JNiSC&(>z;o9+lOdF?jLaSdcP zrdTrj+>%kTU+SW6d<_sAZulL?Dui4t)=gD>DA95CiE*^{jB`%wul1gL^}O;3|76T9 z0)zmGX}kb|u4ty=;f`U7zy!KWm%b1RmDB9nV?1D6zP>+Me_#!p1COl1Qwq2a z3gWOP`Y_d|8)mC$OC!Bl`2TPvNReO_wGY*!a2O&Y>A!Z1P5_Mmu>Yg3$#)M2_rLVW z$^QT9k)9P#RS6_N_?V&g0G~+xU#GvaI%iY{V(dD1rO1|6STuEcnBR|E%VW1y~mZf+JpyH zpJ@AG=4^-`VyO9BDni`E=U7D~T!?Q65XOR3_;;0J)tHH2x@e!UG8794t8;ugQ{XZb z+8cfm{hq^8>Z!icFUsDZLE^r;+^Slc!N*lX2OocmR1F^PyDn36)(Gvt+`sHs7&*MX zz|mYhJ2oiT6wG?K_g^fxf4sr(NlSgDh7ve3oo;k5F#l{tp9o)<36bgNOkI?Du|u~v z<-=m>Z&F^x7#Tf6TfVXN=|MKO>5b(86`5skdBIFiA$oe*OI#l5wy5Tn+cC^K2Fx*Im$rdr3&m zo*B+3M^~gH6($(lMXO zBdw=vMvA7TUY9^R56#$2dYv%fAoB@km?P*(aV=>EA z=E0M1ALW!>=L1hyUu3*eR6p_&Gi$pcwppmtj25i39VIjxod{@AR&ox1THIRvwmKA< zRL)Q@ZB8j&Wj-AL)U^7?9%iMA^#*}r+m#@h_d$~D`w=SAKGpr2^%p4`#W^*V!h3!h zztz4hI|ZeGKp>30+?US0Ht&A-Qhw}4pz+rg3NFnrRa^Qz#ekWN1DjEdG|`1L9g^Q8 z*t(?gufuX&Hf>gtt0~8w99|Z*vfZhTb%#BDR=8)Ur^T3$({{#^A>gU%ETv{9y8^4E{F_xk&&rcXqDvk#Po{HNxgnngveGdljny+6r1R6nawek$h` z50z#y%mu@o>;z4;@mF(Jl2S^ElWH|~+qtN4TB-{LR%ywnsIjqWUU!9SIMj$AI1V;w z9ph}8r8J`XNYjcY8ZY`B+*Q_Cgbv%IUz<2#5A04r!lM4pYO|X%i^x!Yw_4;F zvhvLg!KY>#JhuXk2M0Ij_g6mOr)*}@8((B_z0LAF&xr{*H#yYrg4>pPLfikT+U~zq zbEol8ul*mue@a;@YYVbWmN14QS}a)-Gg&euyRs#w?7K*c$Zi-;WSwS=HG{EJD%+gw zOpK)*j&%+~G{XOK{!i|Q_tV?U%ah;CZ!XvNz2-IZd4C>L-=)5qTFA|{E({9IY8qm( zES8R4@iGyR(&v*mz=@>ti^9}A^K^a%E8!;AiuNL4QCu&>*U3Ly>V7Q0_cq>MRln_F zL${(&6OFsp)vdmJ)lN};?P`QohZdR_?WgiOOEI81)+$fQX89kUo2%EGCeSJHkm?qC z#l5THkqXU!8GjZ!TfOrKah5^}*r|t=`OOgY6vm6$`ZZlmMV$H-h2XC3nc&?S z*K7I$5)xgO6oi`OUoTun9jtIz_5Py$QekoA_eVK2aAX@|$DuCY+?+0|^rl~~5!EO@ zXVE**GF+nka8WsmlheF*VM_#4sv!CFQ%vHLQ8V61-rPs4wyRZ!+ETR(ZOBfutPb1=frz%2~>M3 z!Uo5mIwPe|Z1b7;IWv^gF=P+<8#1JbF7nc8>~{i??PPv>m7Q}m58ex1$B6wN+?bcw zp)a|aHPsiBb=rNV;4{3*FFUc-qKpz9eeC)LW#<^eCsH*RlRl}=X&qp9J#l8c*z#3wo{Kf`hBX)s1gO6IcDIS5#JIIMptT(fQg@bGh8t( zFYUh_Sn)F`0o^o^6s(fVC4c&*NO=n;CO^ZT*uTQhrM?vqveQdlA~hsd02 zRjAlKM3b%*F>2cJRPh>;c{AQ@AeD|aS0a&}YUkR`x0p++{;OI^dtA_&6h);R>0V2v zyN)kDQkI=#e(*_{uom4{Tbic2{>~bwiSgj`EX@kluar-t(&41+CnGIE262hz79}+; z3f{k?!g*{AlfLG?6_oig5siP(r z`ej(HJD6W*4sFb|BkrbIvC08*Dd2Ce`2$rGbr%a1iJj~Ex; z^)B1&vzy2cLdv^7HNxt7*qtrcMqz056SvBtL|D{&4sIbl zVWQE`c~|xG?ak%M5D0SS)8>3OSRIx{`TcF@_`+f6P{qFON0Is{@b;jSg;f>+VY%$U z5QDDs;=$i1;z$+<+SW}z1VO8@up_!Rx%4O_!q-YT3DSCcGr3!_ol<27 z9)z4W5rUxi(vf#~1Pdi{FUMj+=gKLTp`cts&X6`78J_|^Wb7wsF82he%tw6@`^ShC zY*E}$n2ILR6_wVjD332*e^nwbm7vQ7qKb449EAtvH<78_Dr*GdUj zW|>8NxxcQWv9aNK($f<=*N{5bWpGo@WAUtT7h2bePCi)IIm=ClqJFG9mkxEYypVrxl?sOhmj?AanIP!>*`|D1%bs^p8sySbF7SQqBk#|h~ zoqL3zk%`|}py})GSvDID?n>dvTJC&d}=y5^*xN99BG+}PabC<{Z-0tB{4ebT7W^mcu z!XhIe)Xl*@T8^bptM<;!DjNqt9jNzGAIfiV!)jScPdDz~j~oK&-~o>da$UV% zQcy!-H4K6M+lvfDkn*Jm)u)+k$u^Ngnjvx*wHHBl(yjKk06P2oEy%BN&Wf)5xcoY~?rafL^i|q+VFl_aBc04g^|R zh>@mCWjoQg{i*xBCBElQZY`<+Y02R#+w3u#n2B zWXl`j_lixmVWAuCv)to44)T>~qVb#VAS2KxS6D0m?UOXgGTVA3#%hz3O~VcW#krXh z7syh-jI%1dW7_xYp=oMP$f7Ku&0O-~po5M@j%kKSfLId9=2}-e-W&_dNI;C-vq+$N z-Gq9ST^BEYY4ySDP=-moEg-WS0&Jctib>iY3#(1?j=m==92uNL1niwAV<^*cNZ9ZuG*lzZ2!-pNSP`2{hp)tOuStJI*J zIXe){8UxM@1+$QxkwFCXo;{$SP&>Jzm_YNYl@~eojW=E<&P}!{!z_-o+gSTd2z<%C zW*YS?QPQ7j9KQO2%^EOLKzyk}J4%Cg3HVe#-RA9tNui#zagU7EU;t`h@%C}FH_fbC zzMNJo+}}O5g8tZLdAgXDdA=j{wCy$1^$Ov9Br2!PnE8Dh2q6;yG%F^NIEEgJ%!_IL zoC(>VC%r!^ixL8XY7oUEf(R^emk0l;-vxsx=ic|TU0G2rw|jZErghIbTJDI?y)`X; zetn#K`}T*1->KR{wrNDKP8@o!5QzeD=spm~t_j~|YT0i^?Xfr;-&+#X;GKuL#PCTOQCQsq=Et+5XZ-#Rj(z3>+ioG zXl#Fb-Z1@2uG;bQD`Q$Li`QDy=7>$hTKn0}nu;(Z-~@D%&Lj6BvRuYaN0hF3%BPqi z04z9EvCKg})>m`aOHaVVDU!M?sc;2SNTY2A)G7#aeBkloy4 zzzO!E_?EAl0|%^^1ZsA6cC`F0UA@=Lz#wiibVXx$Xh`NH=RtJ*fan6D*0R)8KCpNb z+SxZu9zQPoxiVE@*BG#nZeib#sc0rVR4#s~{CeGW7!UT3xI)d1BtP*Kkui?BsuUal<=VOBy=r$kL7&@AP~-vAXP43b_c;gWj;`BJgl2T{HIHsr>7m| zE#JZDo1j+Bya33JQSNmOUc98+-tS+drEDPhoSztE>q@-zhc=%-uIL>r(n46o! zWn`|3;K6t(drmez8exSXo}d(@F3mo)?C5a;FIA+V(?+4#ZVVcno+8$_fxS?kBB9?c z;H!#64FGY&AJCkxB?{o8R@PPTf`6LUjni?sTLYuo-DE`AR^yb*9Z(x#34k-ifV8nl zpw_#du=hYZsCn}1NBkLJx$-z)a_t$K{v2m+Y5BGCOOTt#^U3k?QUDUU?J_q7<$1u< zV?c7e+I<*q7q&UUrKs&oV}LEr;G*T@IzTK1^|yh3D} zWP+%MH3-2LfS_U>zbXXH*M1fU8A3l4o~J454H9qDfQRPW2CDabhG@_?mI**4 zzyaOV*h+;-N%c%O0)wGCfMIQvdGXmra7<)I6%>eT0PTYL_mx%>pg;V6)m6uR=>=nI zZevs8LdCg(VEkgc85j*8Q^3E8hAu0D@zDaNz+BjJus+`O5q$*cDjUE}z<`8wydet| zaor7|cwjth0ORBLfk(>US9j46Xfnm+iV}#) + +For the full copyright and license information, please view the LICENSE +file that was distributed with this source code. +EOF; + +$finder = Symfony\CS\Finder::create() + ->files() + ->name('*.php') + ->exclude('Fixtures') + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') +; + +return Symfony\CS\Config::create() + ->setUsingCache(true) + //->setUsingLinter(false) + ->setRiskyAllowed(true) + ->setRules(array( + '@PSR2' => true, + 'binary_operator_spaces' => true, + 'blank_line_before_return' => true, + 'header_comment' => array('header' => $header), + 'include' => true, + 'long_array_syntax' => true, + 'method_separation' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_blank_lines_between_uses' => true, + 'no_duplicate_semicolons' => true, + 'no_extra_consecutive_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_unused_imports' => true, + 'object_operator_without_whitespace' => true, + 'phpdoc_align' => true, + 'phpdoc_indent' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_package' => true, + 'phpdoc_order' => true, + 'phpdoc_scalar' => true, + 'phpdoc_trim' => true, + 'phpdoc_type_to_var' => true, + 'psr0' => true, + 'single_blank_line_before_namespace' => true, + 'spaces_cast' => true, + 'standardize_not_equals' => true, + 'ternary_operator_spaces' => true, + 'trailing_comma_in_multiline_array' => true, + 'whitespacy_lines' => true, + )) + ->finder($finder) +; diff --git a/src/vendor/monolog/monolog/CHANGELOG.mdown b/src/vendor/monolog/monolog/CHANGELOG.mdown new file mode 100644 index 0000000..76ea0ea --- /dev/null +++ b/src/vendor/monolog/monolog/CHANGELOG.mdown @@ -0,0 +1,328 @@ +### 1.22.0 (2016-11-26) + + * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily + * Added MercurialProcessor to add mercurial revision and branch names to log records + * Added support for AWS SDK v3 in DynamoDbHandler + * Fixed fatal errors occuring when normalizing generators that have been fully consumed + * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix) + * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore + * Fixed SyslogUdpHandler to avoid sending empty frames + * Fixed a few PHP 7.0 and 7.1 compatibility issues + +### 1.21.0 (2016-07-29) + + * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues + * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order + * Added ability to format the main line of text the SlackHandler sends by explictly setting a formatter on the handler + * Added information about SoapFault instances in NormalizerFormatter + * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level + +### 1.20.0 (2016-07-02) + + * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy + * Added StreamHandler::getUrl to retrieve the stream's URL + * Added ability to override addRow/addTitle in HtmlFormatter + * Added the $context to context information when the ErrorHandler handles a regular php error + * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d + * Fixed WhatFailureGroupHandler to work with PHP7 throwables + * Fixed a few minor bugs + +### 1.19.0 (2016-04-12) + + * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed + * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors + * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler + * Fixed HipChatHandler handling of long messages + +### 1.18.2 (2016-04-02) + + * Fixed ElasticaFormatter to use more precise dates + * Fixed GelfMessageFormatter sending too long messages + +### 1.18.1 (2016-03-13) + + * Fixed SlackHandler bug where slack dropped messages randomly + * Fixed RedisHandler issue when using with the PHPRedis extension + * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension + * Fixed BrowserConsoleHandler regression + +### 1.18.0 (2016-03-01) + + * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond + * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames + * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name + * Added FluentdFormatter for the Fluentd unix socket protocol + * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed + * Added support for replacing context sub-keys using `%context.*%` in LineFormatter + * Added support for `payload` context value in RollbarHandler + * Added setRelease to RavenHandler to describe the application version, sent with every log + * Added support for `fingerprint` context value in RavenHandler + * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed + * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()` + * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places + +### 1.17.2 (2015-10-14) + + * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers + * Fixed SlackHandler handling to use slack functionalities better + * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id + * Fixed 5.3 compatibility regression + +### 1.17.1 (2015-08-31) + + * Fixed RollbarHandler triggering PHP notices + +### 1.17.0 (2015-08-30) + + * Added support for `checksum` and `release` context/extra values in RavenHandler + * Added better support for exceptions in RollbarHandler + * Added UidProcessor::getUid + * Added support for showing the resource type in NormalizedFormatter + * Fixed IntrospectionProcessor triggering PHP notices + +### 1.16.0 (2015-08-09) + + * Added IFTTTHandler to notify ifttt.com triggers + * Added Logger::setHandlers() to allow setting/replacing all handlers + * Added $capSize in RedisHandler to cap the log size + * Fixed StreamHandler creation of directory to only trigger when the first log write happens + * Fixed bug in the handling of curl failures + * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler + * Fixed missing fatal errors records with handlers that need to be closed to flush log records + * Fixed TagProcessor::addTags support for associative arrays + +### 1.15.0 (2015-07-12) + + * Added addTags and setTags methods to change a TagProcessor + * Added automatic creation of directories if they are missing for a StreamHandler to open a log file + * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure + * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used + * Fixed HTML/JS escaping in BrowserConsoleHandler + * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only) + +### 1.14.0 (2015-06-19) + + * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library + * Added support for objects implementing __toString in the NormalizerFormatter + * Added support for HipChat's v2 API in HipChatHandler + * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app + * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true) + * Fixed curl errors being silently suppressed + +### 1.13.1 (2015-03-09) + + * Fixed regression in HipChat requiring a new token to be created + +### 1.13.0 (2015-03-05) + + * Added Registry::hasLogger to check for the presence of a logger instance + * Added context.user support to RavenHandler + * Added HipChat API v2 support in the HipChatHandler + * Added NativeMailerHandler::addParameter to pass params to the mail() process + * Added context data to SlackHandler when $includeContextAndExtra is true + * Added ability to customize the Swift_Message per-email in SwiftMailerHandler + * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided + * Fixed serialization of INF and NaN values in Normalizer and LineFormatter + +### 1.12.0 (2014-12-29) + + * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers. + * Added PsrHandler to forward records to another PSR-3 logger + * Added SamplingHandler to wrap around a handler and include only every Nth record + * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now) + * Added exception codes in the output of most formatters + * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line) + * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data + * Added $host to HipChatHandler for users of private instances + * Added $transactionName to NewRelicHandler and support for a transaction_name context value + * Fixed MandrillHandler to avoid outputing API call responses + * Fixed some non-standard behaviors in SyslogUdpHandler + +### 1.11.0 (2014-09-30) + + * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names + * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails + * Added MandrillHandler to send emails via the Mandrillapp.com API + * Added SlackHandler to log records to a Slack.com account + * Added FleepHookHandler to log records to a Fleep.io account + * Added LogglyHandler::addTag to allow adding tags to an existing handler + * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end + * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing + * Added support for PhpAmqpLib in the AmqpHandler + * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs + * Added support for adding extra fields from $_SERVER in the WebProcessor + * Fixed support for non-string values in PrsLogMessageProcessor + * Fixed SwiftMailer messages being sent with the wrong date in long running scripts + * Fixed minor PHP 5.6 compatibility issues + * Fixed BufferHandler::close being called twice + +### 1.10.0 (2014-06-04) + + * Added Logger::getHandlers() and Logger::getProcessors() methods + * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached + * Added support for extra data in NewRelicHandler + * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines + +### 1.9.1 (2014-04-24) + + * Fixed regression in RotatingFileHandler file permissions + * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records + * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative + +### 1.9.0 (2014-04-20) + + * Added LogEntriesHandler to send logs to a LogEntries account + * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler + * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes + * Added support for table formatting in FirePHPHandler via the table context key + * Added a TagProcessor to add tags to records, and support for tags in RavenHandler + * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files + * Added sound support to the PushoverHandler + * Fixed multi-threading support in StreamHandler + * Fixed empty headers issue when ChromePHPHandler received no records + * Fixed default format of the ErrorLogHandler + +### 1.8.0 (2014-03-23) + + * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them + * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output + * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler + * Added FlowdockHandler to send logs to a Flowdock account + * Added RollbarHandler to send logs to a Rollbar account + * Added HtmlFormatter to send prettier log emails with colors for each log level + * Added GitProcessor to add the current branch/commit to extra record data + * Added a Monolog\Registry class to allow easier global access to pre-configured loggers + * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement + * Added support for HHVM + * Added support for Loggly batch uploads + * Added support for tweaking the content type and encoding in NativeMailerHandler + * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor + * Fixed batch request support in GelfHandler + +### 1.7.0 (2013-11-14) + + * Added ElasticSearchHandler to send logs to an Elastic Search server + * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB + * Added SyslogUdpHandler to send logs to a remote syslogd server + * Added LogglyHandler to send logs to a Loggly account + * Added $level to IntrospectionProcessor so it only adds backtraces when needed + * Added $version to LogstashFormatter to allow using the new v1 Logstash format + * Added $appName to NewRelicHandler + * Added configuration of Pushover notification retries/expiry + * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default + * Added chainability to most setters for all handlers + * Fixed RavenHandler batch processing so it takes the message from the record with highest priority + * Fixed HipChatHandler batch processing so it sends all messages at once + * Fixed issues with eAccelerator + * Fixed and improved many small things + +### 1.6.0 (2013-07-29) + + * Added HipChatHandler to send logs to a HipChat chat room + * Added ErrorLogHandler to send logs to PHP's error_log function + * Added NewRelicHandler to send logs to NewRelic's service + * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler + * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel + * Added stack traces output when normalizing exceptions (json output & co) + * Added Monolog\Logger::API constant (currently 1) + * Added support for ChromePHP's v4.0 extension + * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel + * Added support for sending messages to multiple users at once with the PushoverHandler + * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler) + * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now + * Fixed issue in RotatingFileHandler when an open_basedir restriction is active + * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0 + * Fixed SyslogHandler issue when many were used concurrently with different facilities + +### 1.5.0 (2013-04-23) + + * Added ProcessIdProcessor to inject the PID in log records + * Added UidProcessor to inject a unique identifier to all log records of one request/run + * Added support for previous exceptions in the LineFormatter exception serialization + * Added Monolog\Logger::getLevels() to get all available levels + * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle + +### 1.4.1 (2013-04-01) + + * Fixed exception formatting in the LineFormatter to be more minimalistic + * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0 + * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days + * Fixed WebProcessor array access so it checks for data presence + * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors + +### 1.4.0 (2013-02-13) + + * Added RedisHandler to log to Redis via the Predis library or the phpredis extension + * Added ZendMonitorHandler to log to the Zend Server monitor + * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor + * Added `$useSSL` option to the PushoverHandler which is enabled by default + * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously + * Fixed header injection capability in the NativeMailHandler + +### 1.3.1 (2013-01-11) + + * Fixed LogstashFormatter to be usable with stream handlers + * Fixed GelfMessageFormatter levels on Windows + +### 1.3.0 (2013-01-08) + + * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface` + * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance + * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash) + * Added PushoverHandler to send mobile notifications + * Added CouchDBHandler and DoctrineCouchDBHandler + * Added RavenHandler to send data to Sentry servers + * Added support for the new MongoClient class in MongoDBHandler + * Added microsecond precision to log records' timestamps + * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing + the oldest entries + * Fixed normalization of objects with cyclic references + +### 1.2.1 (2012-08-29) + + * Added new $logopts arg to SyslogHandler to provide custom openlog options + * Fixed fatal error in SyslogHandler + +### 1.2.0 (2012-08-18) + + * Added AmqpHandler (for use with AMQP servers) + * Added CubeHandler + * Added NativeMailerHandler::addHeader() to send custom headers in mails + * Added the possibility to specify more than one recipient in NativeMailerHandler + * Added the possibility to specify float timeouts in SocketHandler + * Added NOTICE and EMERGENCY levels to conform with RFC 5424 + * Fixed the log records to use the php default timezone instead of UTC + * Fixed BufferHandler not being flushed properly on PHP fatal errors + * Fixed normalization of exotic resource types + * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog + +### 1.1.0 (2012-04-23) + + * Added Monolog\Logger::isHandling() to check if a handler will + handle the given log level + * Added ChromePHPHandler + * Added MongoDBHandler + * Added GelfHandler (for use with Graylog2 servers) + * Added SocketHandler (for use with syslog-ng for example) + * Added NormalizerFormatter + * Added the possibility to change the activation strategy of the FingersCrossedHandler + * Added possibility to show microseconds in logs + * Added `server` and `referer` to WebProcessor output + +### 1.0.2 (2011-10-24) + + * Fixed bug in IE with large response headers and FirePHPHandler + +### 1.0.1 (2011-08-25) + + * Added MemoryPeakUsageProcessor and MemoryUsageProcessor + * Added Monolog\Logger::getName() to get a logger's channel name + +### 1.0.0 (2011-07-06) + + * Added IntrospectionProcessor to get info from where the logger was called + * Fixed WebProcessor in CLI + +### 1.0.0-RC1 (2011-07-01) + + * Initial release diff --git a/src/vendor/monolog/monolog/LICENSE b/src/vendor/monolog/monolog/LICENSE new file mode 100644 index 0000000..1647321 --- /dev/null +++ b/src/vendor/monolog/monolog/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2016 Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/monolog/monolog/README.mdown b/src/vendor/monolog/monolog/README.mdown new file mode 100644 index 0000000..7d8ade5 --- /dev/null +++ b/src/vendor/monolog/monolog/README.mdown @@ -0,0 +1,95 @@ +# Monolog - Logging for PHP [![Build Status](https://img.shields.io/travis/Seldaek/monolog.svg)](https://travis-ci.org/Seldaek/monolog) + +[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) +[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) +[![Reference Status](https://www.versioneye.com/php/monolog:monolog/reference_badge.svg)](https://www.versioneye.com/php/monolog:monolog/references) + + +Monolog sends your logs to files, sockets, inboxes, databases and various +web services. See the complete list of handlers below. Special handlers +allow you to build advanced logging strategies. + +This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +interface that you can type-hint against in your own libraries to keep +a maximum of interoperability. You can also use it in your applications to +make sure you can always use another compatible logger at a later time. +As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels. +Internally Monolog still uses its own level scheme since it predates PSR-3. + +## Installation + +Install the latest version with + +```bash +$ composer require monolog/monolog +``` + +## Basic Usage + +```php +pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); + +// add records to the log +$log->addWarning('Foo'); +$log->addError('Bar'); +``` + +## Documentation + +- [Usage Instructions](doc/01-usage.md) +- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md) +- [Utility classes](doc/03-utilities.md) +- [Extending Monolog](doc/04-extending.md) + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +## About + +### Requirements + +- Monolog works with PHP 5.3 or above, and is also tested to work with HHVM. + +### Submitting bugs and feature requests + +Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues) + +### Framework Integrations + +- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) + can be used very easily with Monolog since it implements the interface. +- [Symfony2](http://symfony.com) comes out of the box with Monolog. +- [Silex](http://silex.sensiolabs.org/) comes out of the box with Monolog. +- [Laravel 4 & 5](http://laravel.com/) come out of the box with Monolog. +- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog. +- [PPI](http://www.ppi.io/) comes out of the box with Monolog. +- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. +- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer. +- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog. +- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog. +- [Nette Framework](http://nette.org/en/) can be used with Monolog via [Kdyby/Monolog](https://github.com/Kdyby/Monolog) extension. +- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog. + +### Author + +Jordi Boggiano - -
+See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) which participated in this project. + +### License + +Monolog is licensed under the MIT License - see the `LICENSE` file for details + +### Acknowledgements + +This library is heavily inspired by Python's [Logbook](http://packages.python.org/Logbook/) +library, although most concepts have been adjusted to fit to the PHP world. diff --git a/src/vendor/monolog/monolog/composer.json b/src/vendor/monolog/monolog/composer.json new file mode 100644 index 0000000..f74b7b9 --- /dev/null +++ b/src/vendor/monolog/monolog/composer.json @@ -0,0 +1,66 @@ +{ + "name": "monolog/monolog", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "keywords": ["log", "logging", "psr-3"], + "homepage": "http://github.com/Seldaek/monolog", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "graylog2/gelf-php": "~1.0", + "sentry/sentry": "^0.13", + "ruflin/elastica": ">=0.90 <3.0", + "doctrine/couchdb": "~1.0@dev", + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "php-amqplib/php-amqplib": "~2.4", + "swiftmailer/swiftmailer": "~5.3", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit-mock-objects": "2.3.0", + "jakub-onderka/php-parallel-lint": "0.9" + }, + "_": "phpunit/phpunit-mock-objects required in 2.3.0 due to https://github.com/sebastianbergmann/phpunit-mock-objects/issues/223 - needs hhvm 3.8+ on travis", + "suggest": { + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "sentry/sentry": "Allow sending log messages to a Sentry server", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "php-console/php-console": "Allow sending log messages to Google Chrome" + }, + "autoload": { + "psr-4": {"Monolog\\": "src/Monolog"} + }, + "autoload-dev": { + "psr-4": {"Monolog\\": "tests/Monolog"} + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "scripts": { + "test": [ + "parallel-lint . --exclude vendor", + "phpunit" + ] + } +} diff --git a/src/vendor/monolog/monolog/doc/01-usage.md b/src/vendor/monolog/monolog/doc/01-usage.md new file mode 100644 index 0000000..8e2551f --- /dev/null +++ b/src/vendor/monolog/monolog/doc/01-usage.md @@ -0,0 +1,231 @@ +# Using Monolog + +- [Installation](#installation) +- [Core Concepts](#core-concepts) +- [Log Levels](#log-levels) +- [Configuring a logger](#configuring-a-logger) +- [Adding extra data in the records](#adding-extra-data-in-the-records) +- [Leveraging channels](#leveraging-channels) +- [Customizing the log format](#customizing-the-log-format) + +## Installation + +Monolog is available on Packagist ([monolog/monolog](http://packagist.org/packages/monolog/monolog)) +and as such installable via [Composer](http://getcomposer.org/). + +```bash +composer require monolog/monolog +``` + +If you do not use Composer, you can grab the code from GitHub, and use any +PSR-0 compatible autoloader (e.g. the [Symfony2 ClassLoader component](https://github.com/symfony/ClassLoader)) +to load Monolog classes. + +## Core Concepts + +Every `Logger` instance has a channel (name) and a stack of handlers. Whenever +you add a record to the logger, it traverses the handler stack. Each handler +decides whether it fully handled the record, and if so, the propagation of the +record ends there. + +This allows for flexible logging setups, for example having a `StreamHandler` at +the bottom of the stack that will log anything to disk, and on top of that add +a `MailHandler` that will send emails only when an error message is logged. +Handlers also have a `$bubble` property which defines whether they block the +record or not if they handled it. In this example, setting the `MailHandler`'s +`$bubble` argument to false means that records handled by the `MailHandler` will +not propagate to the `StreamHandler` anymore. + +You can create many `Logger`s, each defining a channel (e.g.: db, request, +router, ..) and each of them combining various handlers, which can be shared +or not. The channel is reflected in the logs and allows you to easily see or +filter records. + +Each Handler also has a Formatter, a default one with settings that make sense +will be created if you don't set one. The formatters normalize and format +incoming records so that they can be used by the handlers to output useful +information. + +Custom severity levels are not available. Only the eight +[RFC 5424](http://tools.ietf.org/html/rfc5424) levels (debug, info, notice, +warning, error, critical, alert, emergency) are present for basic filtering +purposes, but for sorting and other use cases that would require +flexibility, you should add Processors to the Logger that can add extra +information (tags, user ip, ..) to the records before they are handled. + +## Log Levels + +Monolog supports the logging levels described by [RFC 5424](http://tools.ietf.org/html/rfc5424). + +- **DEBUG** (100): Detailed debug information. + +- **INFO** (200): Interesting events. Examples: User logs in, SQL logs. + +- **NOTICE** (250): Normal but significant events. + +- **WARNING** (300): Exceptional occurrences that are not errors. Examples: + Use of deprecated APIs, poor use of an API, undesirable things that are not + necessarily wrong. + +- **ERROR** (400): Runtime errors that do not require immediate action but + should typically be logged and monitored. + +- **CRITICAL** (500): Critical conditions. Example: Application component + unavailable, unexpected exception. + +- **ALERT** (550): Action must be taken immediately. Example: Entire website + down, database unavailable, etc. This should trigger the SMS alerts and wake + you up. + +- **EMERGENCY** (600): Emergency: system is unusable. + +## Configuring a logger + +Here is a basic setup to log to a file and to firephp on the DEBUG level: + +```php +pushHandler(new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG)); +$logger->pushHandler(new FirePHPHandler()); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); +``` + +Let's explain it. The first step is to create the logger instance which will +be used in your code. The argument is a channel name, which is useful when +you use several loggers (see below for more details about it). + +The logger itself does not know how to handle a record. It delegates it to +some handlers. The code above registers two handlers in the stack to allow +handling records in two different ways. + +Note that the FirePHPHandler is called first as it is added on top of the +stack. This allows you to temporarily add a logger with bubbling disabled if +you want to override other configured loggers. + +> If you use Monolog standalone and are looking for an easy way to +> configure many handlers, the [theorchard/monolog-cascade](https://github.com/theorchard/monolog-cascade) +> can help you build complex logging configs via PHP arrays, yaml or json configs. + +## Adding extra data in the records + +Monolog provides two different ways to add extra informations along the simple +textual message. + +### Using the logging context + +The first way is the context, allowing to pass an array of data along the +record: + +```php +addInfo('Adding a new user', array('username' => 'Seldaek')); +``` + +Simple handlers (like the StreamHandler for instance) will simply format +the array to a string but richer handlers can take advantage of the context +(FirePHP is able to display arrays in pretty way for instance). + +### Using processors + +The second way is to add extra data for all records by using a processor. +Processors can be any callable. They will get the record as parameter and +must return it after having eventually changed the `extra` part of it. Let's +write a processor adding some dummy data in the record: + +```php +pushProcessor(function ($record) { + $record['extra']['dummy'] = 'Hello world!'; + + return $record; +}); +``` + +Monolog provides some built-in processors that can be used in your project. +Look at the [dedicated chapter](https://github.com/Seldaek/monolog/blob/master/doc/02-handlers-formatters-processors.md#processors) for the list. + +> Tip: processors can also be registered on a specific handler instead of + the logger to apply only for this handler. + +## Leveraging channels + +Channels are a great way to identify to which part of the application a record +is related. This is useful in big applications (and is leveraged by +MonologBundle in Symfony2). + +Picture two loggers sharing a handler that writes to a single log file. +Channels would allow you to identify the logger that issued every record. +You can easily grep through the log files filtering this or that channel. + +```php +pushHandler($stream); +$logger->pushHandler($firephp); + +// Create a logger for the security-related stuff with a different channel +$securityLogger = new Logger('security'); +$securityLogger->pushHandler($stream); +$securityLogger->pushHandler($firephp); + +// Or clone the first one to only change the channel +$securityLogger = $logger->withName('security'); +``` + +## Customizing the log format + +In Monolog it's easy to customize the format of the logs written into files, +sockets, mails, databases and other handlers. Most of the handlers use the + +```php +$record['formatted'] +``` + +value to be automatically put into the log device. This value depends on the +formatter settings. You can choose between predefined formatter classes or +write your own (e.g. a multiline text file for human-readable output). + +To configure a predefined formatter class, just set it as the handler's field: + +```php +// the default date format is "Y-m-d H:i:s" +$dateFormat = "Y n j, g:i a"; +// the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n" +$output = "%datetime% > %level_name% > %message% %context% %extra%\n"; +// finally, create a formatter +$formatter = new LineFormatter($output, $dateFormat); + +// Create a handler +$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG); +$stream->setFormatter($formatter); +// bind it to a logger object +$securityLogger = new Logger('security'); +$securityLogger->pushHandler($stream); +``` + +You may also reuse the same formatter between multiple handlers and share those +handlers between multiple loggers. + +[Handlers, Formatters and Processors](02-handlers-formatters-processors.md) → diff --git a/src/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md b/src/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md new file mode 100644 index 0000000..bea968a --- /dev/null +++ b/src/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md @@ -0,0 +1,157 @@ +# Handlers, Formatters and Processors + +- [Handlers](#handlers) + - [Log to files and syslog](#log-to-files-and-syslog) + - [Send alerts and emails](#send-alerts-and-emails) + - [Log specific servers and networked logging](#log-specific-servers-and-networked-logging) + - [Logging in development](#logging-in-development) + - [Log to databases](#log-to-databases) + - [Wrappers / Special Handlers](#wrappers--special-handlers) +- [Formatters](#formatters) +- [Processors](#processors) +- [Third Party Packages](#third-party-packages) + +## Handlers + +### Log to files and syslog + +- _StreamHandler_: Logs records into any PHP stream, use this for log files. +- _RotatingFileHandler_: Logs records to a file and creates one logfile per day. + It will also delete files older than `$maxFiles`. You should use + [logrotate](http://linuxcommand.org/man_pages/logrotate8.html) for high profile + setups though, this is just meant as a quick and dirty solution. +- _SyslogHandler_: Logs records to the syslog. +- _ErrorLogHandler_: Logs records to PHP's + [`error_log()`](http://docs.php.net/manual/en/function.error-log.php) function. + +### Send alerts and emails + +- _NativeMailerHandler_: Sends emails using PHP's + [`mail()`](http://php.net/manual/en/function.mail.php) function. +- _SwiftMailerHandler_: Sends emails using a [`Swift_Mailer`](http://swiftmailer.org/) instance. +- _PushoverHandler_: Sends mobile notifications via the [Pushover](https://www.pushover.net/) API. +- _HipChatHandler_: Logs records to a [HipChat](http://hipchat.com) chat room using its API. +- _FlowdockHandler_: Logs records to a [Flowdock](https://www.flowdock.com/) account. +- _SlackHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slack API. +- _SlackbotHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slackbot incoming hook. +- _SlackWebhookHandler_: Logs records to a [Slack](https://www.slack.com/) account using Slack Webhooks. +- _MandrillHandler_: Sends emails via the Mandrill API using a [`Swift_Message`](http://swiftmailer.org/) instance. +- _FleepHookHandler_: Logs records to a [Fleep](https://fleep.io/) conversation using Webhooks. +- _IFTTTHandler_: Notifies an [IFTTT](https://ifttt.com/maker) trigger with the log channel, level name and message. + +### Log specific servers and networked logging + +- _SocketHandler_: Logs records to [sockets](http://php.net/fsockopen), use this + for UNIX and TCP sockets. See an [example](sockets.md). +- _AmqpHandler_: Logs records to an [amqp](http://www.amqp.org/) compatible + server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+). +- _GelfHandler_: Logs records to a [Graylog2](http://www.graylog2.org) server. +- _CubeHandler_: Logs records to a [Cube](http://square.github.com/cube/) server. +- _RavenHandler_: Logs records to a [Sentry](http://getsentry.com/) server using + [raven](https://packagist.org/packages/raven/raven). +- _ZendMonitorHandler_: Logs records to the Zend Monitor present in Zend Server. +- _NewRelicHandler_: Logs records to a [NewRelic](http://newrelic.com/) application. +- _LogglyHandler_: Logs records to a [Loggly](http://www.loggly.com/) account. +- _RollbarHandler_: Logs records to a [Rollbar](https://rollbar.com/) account. +- _SyslogUdpHandler_: Logs records to a remote [Syslogd](http://www.rsyslog.com/) server. +- _LogEntriesHandler_: Logs records to a [LogEntries](http://logentries.com/) account. + +### Logging in development + +- _FirePHPHandler_: Handler for [FirePHP](http://www.firephp.org/), providing + inline `console` messages within [FireBug](http://getfirebug.com/). +- _ChromePHPHandler_: Handler for [ChromePHP](http://www.chromephp.com/), providing + inline `console` messages within Chrome. +- _BrowserConsoleHandler_: Handler to send logs to browser's Javascript `console` with + no browser extension required. Most browsers supporting `console` API are supported. +- _PHPConsoleHandler_: Handler for [PHP Console](https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef), providing + inline `console` and notification popup messages within Chrome. + +### Log to databases + +- _RedisHandler_: Logs records to a [redis](http://redis.io) server. +- _MongoDBHandler_: Handler to write records in MongoDB via a + [Mongo](http://pecl.php.net/package/mongo) extension connection. +- _CouchDBHandler_: Logs records to a CouchDB server. +- _DoctrineCouchDBHandler_: Logs records to a CouchDB server via the Doctrine CouchDB ODM. +- _ElasticSearchHandler_: Logs records to an Elastic Search server. +- _DynamoDbHandler_: Logs records to a DynamoDB table with the [AWS SDK](https://github.com/aws/aws-sdk-php). + +### Wrappers / Special Handlers + +- _FingersCrossedHandler_: A very interesting wrapper. It takes a logger as + parameter and will accumulate log records of all levels until a record + exceeds the defined severity level. At which point it delivers all records, + including those of lower severity, to the handler it wraps. This means that + until an error actually happens you will not see anything in your logs, but + when it happens you will have the full information, including debug and info + records. This provides you with all the information you need, but only when + you need it. +- _DeduplicationHandler_: Useful if you are sending notifications or emails + when critical errors occur. It takes a logger as parameter and will + accumulate log records of all levels until the end of the request (or + `flush()` is called). At that point it delivers all records to the handler + it wraps, but only if the records are unique over a given time period + (60seconds by default). If the records are duplicates they are simply + discarded. The main use of this is in case of critical failure like if your + database is unreachable for example all your requests will fail and that + can result in a lot of notifications being sent. Adding this handler reduces + the amount of notifications to a manageable level. +- _WhatFailureGroupHandler_: This handler extends the _GroupHandler_ ignoring + exceptions raised by each child handler. This allows you to ignore issues + where a remote tcp connection may have died but you do not want your entire + application to crash and may wish to continue to log to other handlers. +- _BufferHandler_: This handler will buffer all the log records it receives + until `close()` is called at which point it will call `handleBatch()` on the + handler it wraps with all the log messages at once. This is very useful to + send an email with all records at once for example instead of having one mail + for every log record. +- _GroupHandler_: This handler groups other handlers. Every record received is + sent to all the handlers it is configured with. +- _FilterHandler_: This handler only lets records of the given levels through + to the wrapped handler. +- _SamplingHandler_: Wraps around another handler and lets you sample records + if you only want to store some of them. +- _NullHandler_: Any record it can handle will be thrown away. This can be used + to put on top of an existing handler stack to disable it temporarily. +- _PsrHandler_: Can be used to forward log records to an existing PSR-3 logger +- _TestHandler_: Used for testing, it records everything that is sent to it and + has accessors to read out the information. +- _HandlerWrapper_: A simple handler wrapper you can inherit from to create + your own wrappers easily. + +## Formatters + +- _LineFormatter_: Formats a log record into a one-line string. +- _HtmlFormatter_: Used to format log records into a human readable html table, mainly suitable for emails. +- _NormalizerFormatter_: Normalizes objects/resources down to strings so a record can easily be serialized/encoded. +- _ScalarFormatter_: Used to format log records into an associative array of scalar values. +- _JsonFormatter_: Encodes a log record into json. +- _WildfireFormatter_: Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler. +- _ChromePHPFormatter_: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler. +- _GelfMessageFormatter_: Used to format log records into Gelf message instances, only useful for the GelfHandler. +- _LogstashFormatter_: Used to format log records into [logstash](http://logstash.net/) event json, useful for any handler listed under inputs [here](http://logstash.net/docs/latest). +- _ElasticaFormatter_: Used to format log records into an Elastica\Document object, only useful for the ElasticSearchHandler. +- _LogglyFormatter_: Used to format log records into Loggly messages, only useful for the LogglyHandler. +- _FlowdockFormatter_: Used to format log records into Flowdock messages, only useful for the FlowdockHandler. +- _MongoDBFormatter_: Converts \DateTime instances to \MongoDate and objects recursively to arrays, only useful with the MongoDBHandler. + +## Processors + +- _PsrLogMessageProcessor_: Processes a log record's message according to PSR-3 rules, replacing `{foo}` with the value from `$context['foo']`. +- _IntrospectionProcessor_: Adds the line/file/class/method from which the log call originated. +- _WebProcessor_: Adds the current request URI, request method and client IP to a log record. +- _MemoryUsageProcessor_: Adds the current memory usage to a log record. +- _MemoryPeakUsageProcessor_: Adds the peak memory usage to a log record. +- _ProcessIdProcessor_: Adds the process id to a log record. +- _UidProcessor_: Adds a unique identifier to a log record. +- _GitProcessor_: Adds the current git branch and commit to a log record. +- _TagProcessor_: Adds an array of predefined tags to a log record. + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +← [Usage](01-usage.md) | [Utility classes](03-utilities.md) → diff --git a/src/vendor/monolog/monolog/doc/03-utilities.md b/src/vendor/monolog/monolog/doc/03-utilities.md new file mode 100644 index 0000000..c62aa41 --- /dev/null +++ b/src/vendor/monolog/monolog/doc/03-utilities.md @@ -0,0 +1,13 @@ +# Utilities + +- _Registry_: The `Monolog\Registry` class lets you configure global loggers that you + can then statically access from anywhere. It is not really a best practice but can + help in some older codebases or for ease of use. +- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register + a Logger instance as an exception handler, error handler or fatal error handler. +- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log + level is reached. +- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain + log level is reached, depending on which channel received the log record. + +← [Handlers, Formatters and Processors](02-handlers-formatters-processors.md) | [Extending Monolog](04-extending.md) → diff --git a/src/vendor/monolog/monolog/doc/04-extending.md b/src/vendor/monolog/monolog/doc/04-extending.md new file mode 100644 index 0000000..ebd9104 --- /dev/null +++ b/src/vendor/monolog/monolog/doc/04-extending.md @@ -0,0 +1,76 @@ +# Extending Monolog + +Monolog is fully extensible, allowing you to adapt your logger to your needs. + +## Writing your own handler + +Monolog provides many built-in handlers. But if the one you need does not +exist, you can write it and use it in your logger. The only requirement is +to implement `Monolog\Handler\HandlerInterface`. + +Let's write a PDOHandler to log records to a database. We will extend the +abstract class provided by Monolog to keep things DRY. + +```php +pdo = $pdo; + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + if (!$this->initialized) { + $this->initialize(); + } + + $this->statement->execute(array( + 'channel' => $record['channel'], + 'level' => $record['level'], + 'message' => $record['formatted'], + 'time' => $record['datetime']->format('U'), + )); + } + + private function initialize() + { + $this->pdo->exec( + 'CREATE TABLE IF NOT EXISTS monolog ' + .'(channel VARCHAR(255), level INTEGER, message LONGTEXT, time INTEGER UNSIGNED)' + ); + $this->statement = $this->pdo->prepare( + 'INSERT INTO monolog (channel, level, message, time) VALUES (:channel, :level, :message, :time)' + ); + + $this->initialized = true; + } +} +``` + +You can now use this handler in your logger: + +```php +pushHandler(new PDOHandler(new PDO('sqlite:logs.sqlite'))); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); +``` + +The `Monolog\Handler\AbstractProcessingHandler` class provides most of the +logic needed for the handler, including the use of processors and the formatting +of the record (which is why we use ``$record['formatted']`` instead of ``$record['message']``). + +← [Utility classes](03-utilities.md) diff --git a/src/vendor/monolog/monolog/doc/sockets.md b/src/vendor/monolog/monolog/doc/sockets.md new file mode 100644 index 0000000..ea9cf0e --- /dev/null +++ b/src/vendor/monolog/monolog/doc/sockets.md @@ -0,0 +1,39 @@ +Sockets Handler +=============== + +This handler allows you to write your logs to sockets using [fsockopen](http://php.net/fsockopen) +or [pfsockopen](http://php.net/pfsockopen). + +Persistent sockets are mainly useful in web environments where you gain some performance not closing/opening +the connections between requests. + +You can use a `unix://` prefix to access unix sockets and `udp://` to open UDP sockets instead of the default TCP. + +Basic Example +------------- + +```php +setPersistent(true); + +// Now add the handler +$logger->pushHandler($handler, Logger::DEBUG); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); + +``` + +In this example, using syslog-ng, you should see the log on the log server: + + cweb1 [2012-02-26 00:12:03] my_logger.INFO: My logger is now ready [] [] + diff --git a/src/vendor/monolog/monolog/phpunit.xml.dist b/src/vendor/monolog/monolog/phpunit.xml.dist new file mode 100644 index 0000000..20d82b6 --- /dev/null +++ b/src/vendor/monolog/monolog/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + + tests/Monolog/ + + + + + + src/Monolog/ + + + + + + + diff --git a/src/vendor/monolog/monolog/src/Monolog/ErrorHandler.php b/src/vendor/monolog/monolog/src/Monolog/ErrorHandler.php new file mode 100644 index 0000000..0152298 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/ErrorHandler.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Monolog\Handler\AbstractHandler; + +/** + * Monolog error handler + * + * A facility to enable logging of runtime errors, exceptions and fatal errors. + * + * Quick setup: ErrorHandler::register($logger); + * + * @author Jordi Boggiano + */ +class ErrorHandler +{ + private $logger; + + private $previousExceptionHandler; + private $uncaughtExceptionLevel; + + private $previousErrorHandler; + private $errorLevelMap; + private $handleOnlyReportedErrors; + + private $hasFatalErrorHandler; + private $fatalLevel; + private $reservedMemory; + private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Registers a new ErrorHandler for a given Logger + * + * By default it will handle errors, exceptions and fatal errors + * + * @param LoggerInterface $logger + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling + * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling + * @return ErrorHandler + */ + public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null) + { + $handler = new static($logger); + if ($errorLevelMap !== false) { + $handler->registerErrorHandler($errorLevelMap); + } + if ($exceptionLevel !== false) { + $handler->registerExceptionHandler($exceptionLevel); + } + if ($fatalLevel !== false) { + $handler->registerFatalHandler($fatalLevel); + } + + return $handler; + } + + public function registerExceptionHandler($level = null, $callPrevious = true) + { + $prev = set_exception_handler(array($this, 'handleException')); + $this->uncaughtExceptionLevel = $level; + if ($callPrevious && $prev) { + $this->previousExceptionHandler = $prev; + } + } + + public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true) + { + $prev = set_error_handler(array($this, 'handleError'), $errorTypes); + $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); + if ($callPrevious) { + $this->previousErrorHandler = $prev ?: true; + } + + $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; + } + + public function registerFatalHandler($level = null, $reservedMemorySize = 20) + { + register_shutdown_function(array($this, 'handleFatalError')); + + $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); + $this->fatalLevel = $level; + $this->hasFatalErrorHandler = true; + } + + protected function defaultErrorLevelMap() + { + return array( + E_ERROR => LogLevel::CRITICAL, + E_WARNING => LogLevel::WARNING, + E_PARSE => LogLevel::ALERT, + E_NOTICE => LogLevel::NOTICE, + E_CORE_ERROR => LogLevel::CRITICAL, + E_CORE_WARNING => LogLevel::WARNING, + E_COMPILE_ERROR => LogLevel::ALERT, + E_COMPILE_WARNING => LogLevel::WARNING, + E_USER_ERROR => LogLevel::ERROR, + E_USER_WARNING => LogLevel::WARNING, + E_USER_NOTICE => LogLevel::NOTICE, + E_STRICT => LogLevel::NOTICE, + E_RECOVERABLE_ERROR => LogLevel::ERROR, + E_DEPRECATED => LogLevel::NOTICE, + E_USER_DEPRECATED => LogLevel::NOTICE, + ); + } + + /** + * @private + */ + public function handleException($e) + { + $this->logger->log( + $this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel, + sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), + array('exception' => $e) + ); + + if ($this->previousExceptionHandler) { + call_user_func($this->previousExceptionHandler, $e); + } + + exit(255); + } + + /** + * @private + */ + public function handleError($code, $message, $file = '', $line = 0, $context = array()) + { + if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { + return; + } + + // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries + if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) { + $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line)); + } + + if ($this->previousErrorHandler === true) { + return false; + } elseif ($this->previousErrorHandler) { + return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context); + } + } + + /** + * @private + */ + public function handleFatalError() + { + $this->reservedMemory = null; + + $lastError = error_get_last(); + if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { + $this->logger->log( + $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel, + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line']) + ); + + if ($this->logger instanceof Logger) { + foreach ($this->logger->getHandlers() as $handler) { + if ($handler instanceof AbstractHandler) { + $handler->close(); + } + } + } + } + } + + private static function codeToString($code) + { + switch ($code) { + case E_ERROR: + return 'E_ERROR'; + case E_WARNING: + return 'E_WARNING'; + case E_PARSE: + return 'E_PARSE'; + case E_NOTICE: + return 'E_NOTICE'; + case E_CORE_ERROR: + return 'E_CORE_ERROR'; + case E_CORE_WARNING: + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: + return 'E_USER_ERROR'; + case E_USER_WARNING: + return 'E_USER_WARNING'; + case E_USER_NOTICE: + return 'E_USER_NOTICE'; + case E_STRICT: + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: + return 'E_USER_DEPRECATED'; + } + + return 'Unknown PHP error'; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php new file mode 100644 index 0000000..9beda1e --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats a log message according to the ChromePHP array format + * + * @author Christophe Coevoet + */ +class ChromePHPFormatter implements FormatterInterface +{ + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'log', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warn', + Logger::ERROR => 'error', + Logger::CRITICAL => 'error', + Logger::ALERT => 'error', + Logger::EMERGENCY => 'error', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $backtrace = 'unknown'; + if (isset($record['extra']['file'], $record['extra']['line'])) { + $backtrace = $record['extra']['file'].' : '.$record['extra']['line']; + unset($record['extra']['file'], $record['extra']['line']); + } + + $message = array('message' => $record['message']); + if ($record['context']) { + $message['context'] = $record['context']; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + } + if (count($message) === 1) { + $message = reset($message); + } + + return array( + $record['channel'], + $message, + $backtrace, + $this->logLevels[$record['level']], + ); + } + + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php new file mode 100644 index 0000000..4c556cf --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Elastica\Document; + +/** + * Format a log message into an Elastica Document + * + * @author Jelle Vink + */ +class ElasticaFormatter extends NormalizerFormatter +{ + /** + * @var string Elastic search index name + */ + protected $index; + + /** + * @var string Elastic search document type + */ + protected $type; + + /** + * @param string $index Elastic Search index name + * @param string $type Elastic Search document type + */ + public function __construct($index, $type) + { + // elasticsearch requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->index = $index; + $this->type = $type; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + /** + * Getter index + * @return string + */ + public function getIndex() + { + return $this->index; + } + + /** + * Getter type + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Convert a log message into an Elastica Document + * + * @param array $record Log message + * @return Document + */ + protected function getDocument($record) + { + $document = new Document(); + $document->setData($record); + $document->setType($this->type); + $document->setIndex($this->index); + + return $document; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php new file mode 100644 index 0000000..5094af3 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * formats the record to be used in the FlowdockHandler + * + * @author Dominik Liebler + */ +class FlowdockFormatter implements FormatterInterface +{ + /** + * @var string + */ + private $source; + + /** + * @var string + */ + private $sourceEmail; + + /** + * @param string $source + * @param string $sourceEmail + */ + public function __construct($source, $sourceEmail) + { + $this->source = $source; + $this->sourceEmail = $sourceEmail; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $tags = array( + '#logs', + '#' . strtolower($record['level_name']), + '#' . $record['channel'], + ); + + foreach ($record['extra'] as $value) { + $tags[] = '#' . $value; + } + + $subject = sprintf( + 'in %s: %s - %s', + $this->source, + $record['level_name'], + $this->getShortMessage($record['message']) + ); + + $record['flowdock'] = array( + 'source' => $this->source, + 'from_address' => $this->sourceEmail, + 'subject' => $subject, + 'content' => $record['message'], + 'tags' => $tags, + 'project' => $this->source, + ); + + return $record; + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } + + /** + * @param string $message + * + * @return string + */ + public function getShortMessage($message) + { + static $hasMbString; + + if (null === $hasMbString) { + $hasMbString = function_exists('mb_strlen'); + } + + $maxLength = 45; + + if ($hasMbString) { + if (mb_strlen($message, 'UTF-8') > $maxLength) { + $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...'; + } + } else { + if (strlen($message) > $maxLength) { + $message = substr($message, 0, $maxLength - 4) . ' ...'; + } + } + + return $message; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php new file mode 100644 index 0000000..02632bb --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.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 Monolog\Formatter; + +/** + * Class FluentdFormatter + * + * Serializes a log message to Fluentd unix socket protocol + * + * Fluentd config: + * + * + * type unix + * path /var/run/td-agent/td-agent.sock + * + * + * Monolog setup: + * + * $logger = new Monolog\Logger('fluent.tag'); + * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock'); + * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter()); + * $logger->pushHandler($fluentHandler); + * + * @author Andrius Putna + */ +class FluentdFormatter implements FormatterInterface +{ + /** + * @var bool $levelTag should message level be a part of the fluentd tag + */ + protected $levelTag = false; + + public function __construct($levelTag = false) + { + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter'); + } + + $this->levelTag = (bool) $levelTag; + } + + public function isUsingLevelsInTag() + { + return $this->levelTag; + } + + public function format(array $record) + { + $tag = $record['channel']; + if ($this->levelTag) { + $tag .= '.' . strtolower($record['level_name']); + } + + $message = array( + 'message' => $record['message'], + 'extra' => $record['extra'], + ); + + if (!$this->levelTag) { + $message['level'] = $record['level']; + $message['level_name'] = $record['level_name']; + } + + return json_encode(array($tag, $record['datetime']->getTimestamp(), $message)); + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 0000000..b5de751 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records); +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php new file mode 100644 index 0000000..64e7665 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Gelf\Message; + +/** + * Serializes a log message to GELF + * @see http://www.graylog2.org/about/gelf + * + * @author Matt Lehner + */ +class GelfMessageFormatter extends NormalizerFormatter +{ + const MAX_LENGTH = 32766; + + /** + * @var string the name of the system for the Gelf log message + */ + protected $systemName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * Translates Monolog log levels to Graylog2 log priorities. + */ + private $logLevels = array( + Logger::DEBUG => 7, + Logger::INFO => 6, + Logger::NOTICE => 5, + Logger::WARNING => 4, + Logger::ERROR => 3, + Logger::CRITICAL => 2, + Logger::ALERT => 1, + Logger::EMERGENCY => 0, + ); + + public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_') + { + parent::__construct('U.u'); + + $this->systemName = $systemName ?: gethostname(); + + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + if (!isset($record['datetime'], $record['message'], $record['level'])) { + throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given'); + } + + $message = new Message(); + $message + ->setTimestamp($record['datetime']) + ->setShortMessage((string) $record['message']) + ->setHost($this->systemName) + ->setLevel($this->logLevels[$record['level']]); + + // start count with message length + system name length + 200 for padding / metadata + $len = 200 + strlen((string) $record['message']) + strlen($this->systemName); + + if ($len > self::MAX_LENGTH) { + $message->setShortMessage(substr($record['message'], 0, self::MAX_LENGTH - 200)); + + return $message; + } + + if (isset($record['channel'])) { + $message->setFacility($record['channel']); + $len += strlen($record['channel']); + } + if (isset($record['extra']['line'])) { + $message->setLine($record['extra']['line']); + $len += 10; + unset($record['extra']['line']); + } + if (isset($record['extra']['file'])) { + $message->setFile($record['extra']['file']); + $len += strlen($record['extra']['file']); + unset($record['extra']['file']); + } + + foreach ($record['extra'] as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len += strlen($this->extraPrefix . $key . $val); + if ($len > self::MAX_LENGTH) { + $message->setAdditional($this->extraPrefix . $key, substr($val, 0, self::MAX_LENGTH - $len)); + break; + } + $message->setAdditional($this->extraPrefix . $key, $val); + } + + foreach ($record['context'] as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len += strlen($this->contextPrefix . $key . $val); + if ($len > self::MAX_LENGTH) { + $message->setAdditional($this->contextPrefix . $key, substr($val, 0, self::MAX_LENGTH - $len)); + break; + } + $message->setAdditional($this->contextPrefix . $key, $val); + } + + if (null === $message->getFile() && isset($record['context']['exception']['file'])) { + if (preg_match("/^(.+):([0-9]+)$/", $record['context']['exception']['file'], $matches)) { + $message->setFile($matches[1]); + $message->setLine($matches[2]); + } + } + + return $message; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php new file mode 100644 index 0000000..3eec95f --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.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 Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats incoming records into an HTML table + * + * This is especially useful for html email logging + * + * @author Tiago Brito + */ +class HtmlFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to html color priorities. + */ + protected $logLevels = array( + Logger::DEBUG => '#cccccc', + Logger::INFO => '#468847', + Logger::NOTICE => '#3a87ad', + Logger::WARNING => '#c09853', + Logger::ERROR => '#f0ad4e', + Logger::CRITICAL => '#FF7708', + Logger::ALERT => '#C12A19', + Logger::EMERGENCY => '#000000', + ); + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + parent::__construct($dateFormat); + } + + /** + * Creates an HTML table row + * + * @param string $th Row header content + * @param string $td Row standard cell content + * @param bool $escapeTd false if td content must not be html escaped + * @return string + */ + protected function addRow($th, $td = ' ', $escapeTd = true) + { + $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); + if ($escapeTd) { + $td = '
'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'
'; + } + + return "\n$th:\n".$td."\n"; + } + + /** + * Create a HTML h1 tag + * + * @param string $title Text to be in the h1 + * @param int $level Error level + * @return string + */ + protected function addTitle($title, $level) + { + $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); + + return '

'.$title.'

'; + } + + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record) + { + $output = $this->addTitle($record['level_name'], $record['level']); + $output .= ''; + + $output .= $this->addRow('Message', (string) $record['message']); + $output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat)); + $output .= $this->addRow('Channel', $record['channel']); + if ($record['context']) { + $embeddedTable = '
'; + foreach ($record['context'] as $key => $value) { + $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + } + $embeddedTable .= '
'; + $output .= $this->addRow('Context', $embeddedTable, false); + } + if ($record['extra']) { + $embeddedTable = ''; + foreach ($record['extra'] as $key => $value) { + $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + } + $embeddedTable .= '
'; + $output .= $this->addRow('Extra', $embeddedTable, false); + } + + return $output.''; + } + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + protected function convertToString($data) + { + if (null === $data || is_scalar($data)) { + return (string) $data; + } + + $data = $this->normalize($data); + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return str_replace('\\/', '/', json_encode($data)); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php new file mode 100644 index 0000000..4b2be77 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.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 Monolog\Formatter; + +use Exception; +use Throwable; + +/** + * Encodes whatever record data is passed to it as json + * + * This can be useful to log to databases or remote APIs + * + * @author Jordi Boggiano + */ +class JsonFormatter extends NormalizerFormatter +{ + const BATCH_MODE_JSON = 1; + const BATCH_MODE_NEWLINES = 2; + + protected $batchMode; + protected $appendNewline; + /** + * @var bool + */ + protected $includeStacktraces = false; + + /** + * @param int $batchMode + */ + public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true) + { + $this->batchMode = $batchMode; + $this->appendNewline = $appendNewline; + } + + /** + * The batch mode option configures the formatting style for + * multiple records. By default, multiple records will be + * formatted as a JSON-encoded array. However, for + * compatibility with some API endpoints, alternative styles + * are available. + * + * @return int + */ + public function getBatchMode() + { + return $this->batchMode; + } + + /** + * True if newlines are appended to every formatted record + * + * @return bool + */ + public function isAppendingNewlines() + { + return $this->appendNewline; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : ''); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + switch ($this->batchMode) { + case static::BATCH_MODE_NEWLINES: + return $this->formatBatchNewlines($records); + + case static::BATCH_MODE_JSON: + default: + return $this->formatBatchJson($records); + } + } + + /** + * @param bool $include + */ + public function includeStacktraces($include = true) + { + $this->includeStacktraces = $include; + } + + /** + * Return a JSON-encoded array of records. + * + * @param array $records + * @return string + */ + protected function formatBatchJson(array $records) + { + return $this->toJson($this->normalize($records), true); + } + + /** + * Use new lines to separate records instead of a + * JSON-encoded array. + * + * @param array $records + * @return string + */ + protected function formatBatchNewlines(array $records) + { + $instance = $this; + + $oldNewline = $this->appendNewline; + $this->appendNewline = false; + array_walk($records, function (&$value, $key) use ($instance) { + $value = $instance->format($value); + }); + $this->appendNewline = $oldNewline; + + return implode("\n", $records); + } + + /** + * Normalizes given $data. + * + * @param mixed $data + * + * @return mixed + */ + protected function normalize($data) + { + if (is_array($data) || $data instanceof \Traversable) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ >= 1000) { + $normalized['...'] = 'Over 1000 items, aborting normalization'; + break; + } + $normalized[$key] = $this->normalize($value); + } + + return $normalized; + } + + if ($data instanceof Exception || $data instanceof Throwable) { + return $this->normalizeException($data); + } + + return $data; + } + + /** + * Normalizes given exception with or without its own stack trace based on + * `includeStacktraces` property. + * + * @param Exception|Throwable $e + * + * @return array + */ + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); + } + + $data = array( + 'class' => get_class($e), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + if ($this->includeStacktraces) { + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } elseif (isset($frame['function']) && $frame['function'] === '{closure}') { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $frame['function']; + } else { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $this->normalize($frame); + } + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php new file mode 100644 index 0000000..8ffe601 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.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 Monolog\Formatter; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter extends NormalizerFormatter +{ + const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + + protected $format; + protected $allowInlineLineBreaks; + protected $ignoreEmptyContextAndExtra; + protected $includeStacktraces; + + /** + * @param string $format The format of the message + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries + * @param bool $ignoreEmptyContextAndExtra + */ + public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false) + { + $this->format = $format ?: static::SIMPLE_FORMAT; + $this->allowInlineLineBreaks = $allowInlineLineBreaks; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + parent::__construct($dateFormat); + } + + public function includeStacktraces($include = true) + { + $this->includeStacktraces = $include; + if ($this->includeStacktraces) { + $this->allowInlineLineBreaks = true; + } + } + + public function allowInlineLineBreaks($allow = true) + { + $this->allowInlineLineBreaks = $allow; + } + + public function ignoreEmptyContextAndExtra($ignore = true) + { + $this->ignoreEmptyContextAndExtra = $ignore; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $vars = parent::format($record); + + $output = $this->format; + + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output); + unset($vars['extra'][$var]); + } + } + + + foreach ($vars['context'] as $var => $val) { + if (false !== strpos($output, '%context.'.$var.'%')) { + $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); + unset($vars['context'][$var]); + } + } + + if ($this->ignoreEmptyContextAndExtra) { + if (empty($vars['context'])) { + unset($vars['context']); + $output = str_replace('%context%', '', $output); + } + + if (empty($vars['extra'])) { + unset($vars['extra']); + $output = str_replace('%extra%', '', $output); + } + } + + foreach ($vars as $var => $val) { + if (false !== strpos($output, '%'.$var.'%')) { + $output = str_replace('%'.$var.'%', $this->stringify($val), $output); + } + } + + // remove leftover %extra.xxx% and %context.xxx% if any + if (false !== strpos($output, '%')) { + $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + } + + return $output; + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + public function stringify($value) + { + return $this->replaceNewlines($this->convertToString($value)); + } + + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof \Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); + } + + $previousText = ''; + if ($previous = $e->getPrevious()) { + do { + $previousText .= ', '.get_class($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine(); + } while ($previous = $previous->getPrevious()); + } + + $str = '[object] ('.get_class($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')'; + if ($this->includeStacktraces) { + $str .= "\n[stacktrace]\n".$e->getTraceAsString(); + } + + return $str; + } + + protected function convertToString($data) + { + if (null === $data || is_bool($data)) { + return var_export($data, true); + } + + if (is_scalar($data)) { + return (string) $data; + } + + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return $this->toJson($data, true); + } + + return str_replace('\\/', '/', @json_encode($data)); + } + + protected function replaceNewlines($str) + { + if ($this->allowInlineLineBreaks) { + return $str; + } + + return str_replace(array("\r\n", "\r", "\n"), ' ', $str); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php new file mode 100644 index 0000000..401859b --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Encodes message information into JSON in a format compatible with Loggly. + * + * @author Adam Pancutt + */ +class LogglyFormatter extends JsonFormatter +{ + /** + * Overrides the default batch mode to new lines for compatibility with the + * Loggly bulk API. + * + * @param int $batchMode + */ + public function __construct($batchMode = self::BATCH_MODE_NEWLINES, $appendNewline = false) + { + parent::__construct($batchMode, $appendNewline); + } + + /** + * Appends the 'timestamp' parameter for indexing by Loggly. + * + * @see https://www.loggly.com/docs/automated-parsing/#json + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function format(array $record) + { + if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTime)) { + $record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO"); + // TODO 2.0 unset the 'datetime' parameter, retained for BC + } + + return parent::format($record); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php new file mode 100644 index 0000000..8f83bec --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Serializes a log message to Logstash Event Format + * + * @see http://logstash.net/ + * @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb + * + * @author Tim Mower + */ +class LogstashFormatter extends NormalizerFormatter +{ + const V0 = 0; + const V1 = 1; + + /** + * @var string the name of the system for the Logstash log message, used to fill the @source field + */ + protected $systemName; + + /** + * @var string an application name for the Logstash log message, used to fill the @type field + */ + protected $applicationName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * @var int logstash format version to use + */ + protected $version; + + /** + * @param string $applicationName the application that sends the data, used as the "type" field of logstash + * @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine + * @param string $extraPrefix prefix for extra keys inside logstash "fields" + * @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_ + * @param int $version the logstash format version to use, defaults to 0 + */ + public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0) + { + // logstash requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->systemName = $systemName ?: gethostname(); + $this->applicationName = $applicationName; + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + $this->version = $version; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + if ($this->version === self::V1) { + $message = $this->formatV1($record); + } else { + $message = $this->formatV0($record); + } + + return $this->toJson($message) . "\n"; + } + + protected function formatV0(array $record) + { + if (empty($record['datetime'])) { + $record['datetime'] = gmdate('c'); + } + $message = array( + '@timestamp' => $record['datetime'], + '@source' => $this->systemName, + '@fields' => array(), + ); + if (isset($record['message'])) { + $message['@message'] = $record['message']; + } + if (isset($record['channel'])) { + $message['@tags'] = array($record['channel']); + $message['@fields']['channel'] = $record['channel']; + } + if (isset($record['level'])) { + $message['@fields']['level'] = $record['level']; + } + if ($this->applicationName) { + $message['@type'] = $this->applicationName; + } + if (isset($record['extra']['server'])) { + $message['@source_host'] = $record['extra']['server']; + } + if (isset($record['extra']['url'])) { + $message['@source_path'] = $record['extra']['url']; + } + if (!empty($record['extra'])) { + foreach ($record['extra'] as $key => $val) { + $message['@fields'][$this->extraPrefix . $key] = $val; + } + } + if (!empty($record['context'])) { + foreach ($record['context'] as $key => $val) { + $message['@fields'][$this->contextPrefix . $key] = $val; + } + } + + return $message; + } + + protected function formatV1(array $record) + { + if (empty($record['datetime'])) { + $record['datetime'] = gmdate('c'); + } + $message = array( + '@timestamp' => $record['datetime'], + '@version' => 1, + 'host' => $this->systemName, + ); + if (isset($record['message'])) { + $message['message'] = $record['message']; + } + if (isset($record['channel'])) { + $message['type'] = $record['channel']; + $message['channel'] = $record['channel']; + } + if (isset($record['level_name'])) { + $message['level'] = $record['level_name']; + } + if ($this->applicationName) { + $message['type'] = $this->applicationName; + } + if (!empty($record['extra'])) { + foreach ($record['extra'] as $key => $val) { + $message[$this->extraPrefix . $key] = $val; + } + } + if (!empty($record['context'])) { + foreach ($record['context'] as $key => $val) { + $message[$this->contextPrefix . $key] = $val; + } + } + + return $message; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php new file mode 100644 index 0000000..eb067bb --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.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 Monolog\Formatter; + +/** + * Formats a record for use with the MongoDBHandler. + * + * @author Florian Plattner + */ +class MongoDBFormatter implements FormatterInterface +{ + private $exceptionTraceAsString; + private $maxNestingLevel; + + /** + * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2 + * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings + */ + public function __construct($maxNestingLevel = 3, $exceptionTraceAsString = true) + { + $this->maxNestingLevel = max($maxNestingLevel, 0); + $this->exceptionTraceAsString = (bool) $exceptionTraceAsString; + } + + /** + * {@inheritDoc} + */ + public function format(array $record) + { + return $this->formatArray($record); + } + + /** + * {@inheritDoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + protected function formatArray(array $record, $nestingLevel = 0) + { + if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) { + foreach ($record as $name => $value) { + if ($value instanceof \DateTime) { + $record[$name] = $this->formatDate($value, $nestingLevel + 1); + } elseif ($value instanceof \Exception) { + $record[$name] = $this->formatException($value, $nestingLevel + 1); + } elseif (is_array($value)) { + $record[$name] = $this->formatArray($value, $nestingLevel + 1); + } elseif (is_object($value)) { + $record[$name] = $this->formatObject($value, $nestingLevel + 1); + } + } + } else { + $record = '[...]'; + } + + return $record; + } + + protected function formatObject($value, $nestingLevel) + { + $objectVars = get_object_vars($value); + $objectVars['class'] = get_class($value); + + return $this->formatArray($objectVars, $nestingLevel); + } + + protected function formatException(\Exception $exception, $nestingLevel) + { + $formattedException = array( + 'class' => get_class($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + ); + + if ($this->exceptionTraceAsString === true) { + $formattedException['trace'] = $exception->getTraceAsString(); + } else { + $formattedException['trace'] = $exception->getTrace(); + } + + return $this->formatArray($formattedException, $nestingLevel); + } + + protected function formatDate(\DateTime $value, $nestingLevel) + { + return new \MongoDate($value->getTimestamp()); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 0000000..d441488 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.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 Monolog\Formatter; + +use Exception; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + const SIMPLE_DATE = "Y-m-d H:i:s"; + + protected $dateFormat; + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); + } + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->normalize($record); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + protected function normalize($data) + { + if (null === $data || is_scalar($data)) { + if (is_float($data)) { + if (is_infinite($data)) { + return ($data > 0 ? '' : '-') . 'INF'; + } + if (is_nan($data)) { + return 'NaN'; + } + } + + return $data; + } + + if (is_array($data)) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ >= 1000) { + $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; + break; + } + $normalized[$key] = $this->normalize($value); + } + + return $normalized; + } + + if ($data instanceof \DateTime) { + return $data->format($this->dateFormat); + } + + if (is_object($data)) { + // TODO 2.0 only check for Throwable + if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) { + return $this->normalizeException($data); + } + + // non-serializable objects that implement __toString stringified + if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) { + $value = $data->__toString(); + } else { + // the rest is json-serialized in some way + $value = $this->toJson($data, true); + } + + return sprintf("[object] (%s: %s)", get_class($data), $value); + } + + if (is_resource($data)) { + return sprintf('[resource] (%s)', get_resource_type($data)); + } + + return '[unknown('.gettype($data).')]'; + } + + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); + } + + $data = array( + 'class' => get_class($e), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $data['faultcode'] = $e->faultcode; + } + + if (isset($e->faultactor)) { + $data['faultactor'] = $e->faultactor; + } + + if (isset($e->detail)) { + $data['detail'] = $e->detail; + } + } + + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } elseif (isset($frame['function']) && $frame['function'] === '{closure}') { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $frame['function']; + } else { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $this->toJson($this->normalize($frame), true); + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param bool $ignoreErrors + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string + */ + protected function toJson($data, $ignoreErrors = false) + { + // suppress json_encode errors since it's twitchy with some inputs + if ($ignoreErrors) { + return @$this->jsonEncode($data); + } + + $json = $this->jsonEncode($data); + + if ($json === false) { + $json = $this->handleJsonError(json_last_error(), $data); + } + + return $json; + } + + /** + * @param mixed $data + * @return string JSON encoded data or null on failure + */ + private function jsonEncode($data) + { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return json_encode($data); + } + + /** + * Handle a json_encode failure. + * + * If the failure is due to invalid string encoding, try to clean the + * input and encode again. If the second encoding attempt fails, the + * inital error is not encoding related or the input can't be cleaned then + * raise a descriptive exception. + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException if failure can't be corrected + * @return string JSON encoded data after error correction + */ + private function handleJsonError($code, $data) + { + if ($code !== JSON_ERROR_UTF8) { + $this->throwEncodeError($code, $data); + } + + if (is_string($data)) { + $this->detectAndCleanUtf8($data); + } elseif (is_array($data)) { + array_walk_recursive($data, array($this, 'detectAndCleanUtf8')); + } else { + $this->throwEncodeError($code, $data); + } + + $json = $this->jsonEncode($data); + + if ($json === false) { + $this->throwEncodeError(json_last_error(), $data); + } + + return $json; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException + */ + private function throwEncodeError($code, $data) + { + switch ($code) { + case JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + + throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); + } + + /** + * Detect invalid UTF-8 string characters and convert to valid UTF-8. + * + * Valid UTF-8 input will be left unmodified, but strings containing + * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed + * original encoding of ISO-8859-15. This conversion may result in + * incorrect output if the actual encoding was not ISO-8859-15, but it + * will be clean UTF-8 output and will not rely on expensive and fragile + * detection algorithms. + * + * Function converts the input in place in the passed variable so that it + * can be used as a callback for array_walk_recursive. + * + * @param mixed &$data Input to check and convert if needed + * @private + */ + public function detectAndCleanUtf8(&$data) + { + if (is_string($data) && !preg_match('//u', $data)) { + $data = preg_replace_callback( + '/[\x80-\xFF]+/', + function ($m) { return utf8_encode($m[0]); }, + $data + ); + $data = str_replace( + array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'), + array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'), + $data + ); + } + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php new file mode 100644 index 0000000..5d345d5 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Formats data into an associative array of scalar values. + * Objects and arrays will be JSON encoded. + * + * @author Andrew Lawson + */ +class ScalarFormatter extends NormalizerFormatter +{ + /** + * {@inheritdoc} + */ + public function format(array $record) + { + foreach ($record as $key => $value) { + $record[$key] = $this->normalizeValue($value); + } + + return $record; + } + + /** + * @param mixed $value + * @return mixed + */ + protected function normalizeValue($value) + { + $normalized = $this->normalize($value); + + if (is_array($normalized) || is_object($normalized)) { + return $this->toJson($normalized, true); + } + + return $normalized; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/src/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 0000000..654710a --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + * @author Christophe Coevoet + * @author Kirill chEbba Chebunin + */ +class WildfireFormatter extends NormalizerFormatter +{ + const TABLE = 'table'; + + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'LOG', + Logger::INFO => 'INFO', + Logger::NOTICE => 'INFO', + Logger::WARNING => 'WARN', + Logger::ERROR => 'ERROR', + Logger::CRITICAL => 'ERROR', + Logger::ALERT => 'ERROR', + Logger::EMERGENCY => 'ERROR', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $file = $line = ''; + if (isset($record['extra']['file'])) { + $file = $record['extra']['file']; + unset($record['extra']['file']); + } + if (isset($record['extra']['line'])) { + $line = $record['extra']['line']; + unset($record['extra']['line']); + } + + $record = $this->normalize($record); + $message = array('message' => $record['message']); + $handleError = false; + if ($record['context']) { + $message['context'] = $record['context']; + $handleError = true; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + $handleError = true; + } + if (count($message) === 1) { + $message = reset($message); + } + + if (isset($record['context'][self::TABLE])) { + $type = 'TABLE'; + $label = $record['channel'] .': '. $record['message']; + $message = $record['context'][self::TABLE]; + } else { + $type = $this->logLevels[$record['level']]; + $label = $record['channel']; + } + + // Create JSON object describing the appearance of the message in the console + $json = $this->toJson(array( + array( + 'Type' => $type, + 'File' => $file, + 'Line' => $line, + 'Label' => $label, + ), + $message, + ), $handleError); + + // The message itself is a serialization of the above JSON object + it's length + return sprintf( + '%s|%s|', + strlen($json), + $json + ); + } + + public function formatBatch(array $records) + { + throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); + } + + protected function normalize($data) + { + if (is_object($data) && !$data instanceof \DateTime) { + return $data; + } + + return parent::normalize($data); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php new file mode 100644 index 0000000..758a425 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Base Handler class providing the Handler structure + * + * @author Jordi Boggiano + */ +abstract class AbstractHandler implements HandlerInterface +{ + protected $level = Logger::DEBUG; + protected $bubble = true; + + /** + * @var FormatterInterface + */ + protected $formatter; + protected $processors = array(); + + /** + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + $this->setLevel($level); + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $record['level'] >= $this->level; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * Closes the handler. + * + * This will be called automatically when the object is destroyed + */ + public function close() + { + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param int|string $level Level or level name + * @return self + */ + public function setLevel($level) + { + $this->level = Logger::toMonologLevel($level); + + return $this; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + * + * @return int + */ + public function getLevel() + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param Boolean $bubble true means that this handler allows bubbling. + * false means that bubbling is not permitted. + * @return self + */ + public function setBubble($bubble) + { + $this->bubble = $bubble; + + return $this; + } + + /** + * Gets the bubbling behavior. + * + * @return Boolean true means that this handler allows bubbling. + * false means that bubbling is not permitted. + */ + public function getBubble() + { + return $this->bubble; + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Exception $e) { + // do nothing + } catch (\Throwable $e) { + // do nothing + } + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 0000000..6f18f72 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.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 Monolog\Handler; + +/** + * Base Handler class providing the Handler structure + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +abstract class AbstractProcessingHandler extends AbstractHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + $record = $this->processRecord($record); + + $record['formatted'] = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + abstract protected function write(array $record); + + /** + * Processes a record. + * + * @param array $record + * @return array + */ + protected function processRecord(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + return $record; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php new file mode 100644 index 0000000..e2b2832 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * Common syslog functionality + */ +abstract class AbstractSyslogHandler extends AbstractProcessingHandler +{ + protected $facility; + + /** + * Translates Monolog log levels to syslog log priorities. + */ + protected $logLevels = array( + Logger::DEBUG => LOG_DEBUG, + Logger::INFO => LOG_INFO, + Logger::NOTICE => LOG_NOTICE, + Logger::WARNING => LOG_WARNING, + Logger::ERROR => LOG_ERR, + Logger::CRITICAL => LOG_CRIT, + Logger::ALERT => LOG_ALERT, + Logger::EMERGENCY => LOG_EMERG, + ); + + /** + * List of valid log facility names. + */ + protected $facilities = array( + 'auth' => LOG_AUTH, + 'authpriv' => LOG_AUTHPRIV, + 'cron' => LOG_CRON, + 'daemon' => LOG_DAEMON, + 'kern' => LOG_KERN, + 'lpr' => LOG_LPR, + 'mail' => LOG_MAIL, + 'news' => LOG_NEWS, + 'syslog' => LOG_SYSLOG, + 'user' => LOG_USER, + 'uucp' => LOG_UUCP, + ); + + /** + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->facilities['local0'] = LOG_LOCAL0; + $this->facilities['local1'] = LOG_LOCAL1; + $this->facilities['local2'] = LOG_LOCAL2; + $this->facilities['local3'] = LOG_LOCAL3; + $this->facilities['local4'] = LOG_LOCAL4; + $this->facilities['local5'] = LOG_LOCAL5; + $this->facilities['local6'] = LOG_LOCAL6; + $this->facilities['local7'] = LOG_LOCAL7; + } else { + $this->facilities['local0'] = 128; // LOG_LOCAL0 + $this->facilities['local1'] = 136; // LOG_LOCAL1 + $this->facilities['local2'] = 144; // LOG_LOCAL2 + $this->facilities['local3'] = 152; // LOG_LOCAL3 + $this->facilities['local4'] = 160; // LOG_LOCAL4 + $this->facilities['local5'] = 168; // LOG_LOCAL5 + $this->facilities['local6'] = 176; // LOG_LOCAL6 + $this->facilities['local7'] = 184; // LOG_LOCAL7 + } + + // convert textual description of facility to syslog constant + if (array_key_exists(strtolower($facility), $this->facilities)) { + $facility = $this->facilities[strtolower($facility)]; + } elseif (!in_array($facility, array_values($this->facilities), true)) { + throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); + } + + $this->facility = $facility; + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php new file mode 100644 index 0000000..e5a46bc --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\JsonFormatter; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Channel\AMQPChannel; +use AMQPExchange; + +class AmqpHandler extends AbstractProcessingHandler +{ + /** + * @var AMQPExchange|AMQPChannel $exchange + */ + protected $exchange; + + /** + * @var string + */ + protected $exchangeName; + + /** + * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use + * @param string $exchangeName + * @param int $level + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true) + { + if ($exchange instanceof AMQPExchange) { + $exchange->setName($exchangeName); + } elseif ($exchange instanceof AMQPChannel) { + $this->exchangeName = $exchangeName; + } else { + throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required'); + } + $this->exchange = $exchange; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $data = $record["formatted"]; + $routingKey = $this->getRoutingKey($record); + + if ($this->exchange instanceof AMQPExchange) { + $this->exchange->publish( + $data, + $routingKey, + 0, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ) + ); + } else { + $this->exchange->basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $routingKey + ); + } + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records) + { + if ($this->exchange instanceof AMQPExchange) { + parent::handleBatch($records); + + return; + } + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + $record = $this->processRecord($record); + $data = $this->getFormatter()->format($record); + + $this->exchange->batch_basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $this->getRoutingKey($record) + ); + } + + $this->exchange->publish_batch(); + } + + /** + * Gets the routing key for the AMQP exchange + * + * @param array $record + * @return string + */ + protected function getRoutingKey(array $record) + { + $routingKey = sprintf( + '%s.%s', + // TODO 2.0 remove substr call + substr($record['level_name'], 0, 4), + $record['channel'] + ); + + return strtolower($routingKey); + } + + /** + * @param string $data + * @return AMQPMessage + */ + private function createAmqpMessage($data) + { + return new AMQPMessage( + (string) $data, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ) + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php new file mode 100644 index 0000000..b3a21bd --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.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 Monolog\Handler; + +use Monolog\Formatter\LineFormatter; + +/** + * Handler sending logs to browser's javascript console with no browser extension required + * + * @author Olivier Poitrey + */ +class BrowserConsoleHandler extends AbstractProcessingHandler +{ + protected static $initialized = false; + protected static $records = array(); + + /** + * {@inheritDoc} + * + * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format. + * + * Example of formatted string: + * + * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + // Accumulate records + self::$records[] = $record; + + // Register shutdown handler if not already done + if (!self::$initialized) { + self::$initialized = true; + $this->registerShutdownFunction(); + } + } + + /** + * Convert records to javascript console commands and send it to the browser. + * This method is automatically called on PHP shutdown if output is HTML or Javascript. + */ + public static function send() + { + $format = self::getResponseFormat(); + if ($format === 'unknown') { + return; + } + + if (count(self::$records)) { + if ($format === 'html') { + self::writeOutput(''); + } elseif ($format === 'js') { + self::writeOutput(self::generateScript()); + } + self::reset(); + } + } + + /** + * Forget all logged records + */ + public static function reset() + { + self::$records = array(); + } + + /** + * Wrapper for register_shutdown_function to allow overriding + */ + protected function registerShutdownFunction() + { + if (PHP_SAPI !== 'cli') { + register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); + } + } + + /** + * Wrapper for echo to allow overriding + * + * @param string $str + */ + protected static function writeOutput($str) + { + echo $str; + } + + /** + * Checks the format of the response + * + * If Content-Type is set to application/javascript or text/javascript -> js + * If Content-Type is set to text/html, or is unset -> html + * If Content-Type is anything else -> unknown + * + * @return string One of 'js', 'html' or 'unknown' + */ + protected static function getResponseFormat() + { + // Check content type + foreach (headers_list() as $header) { + if (stripos($header, 'content-type:') === 0) { + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { + return 'js'; + } + if (stripos($header, 'text/html') === false) { + return 'unknown'; + } + break; + } + } + + return 'html'; + } + + private static function generateScript() + { + $script = array(); + foreach (self::$records as $record) { + $context = self::dump('Context', $record['context']); + $extra = self::dump('Extra', $record['extra']); + + if (empty($context) && empty($extra)) { + $script[] = self::call_array('log', self::handleStyles($record['formatted'])); + } else { + $script = array_merge($script, + array(self::call_array('groupCollapsed', self::handleStyles($record['formatted']))), + $context, + $extra, + array(self::call('groupEnd')) + ); + } + } + + return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; + } + + private static function handleStyles($formatted) + { + $args = array(self::quote('font-weight: normal')); + $format = '%c' . $formatted; + preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + foreach (array_reverse($matches) as $match) { + $args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0])); + $args[] = '"font-weight: normal"'; + + $pos = $match[0][1]; + $format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0])); + } + + array_unshift($args, self::quote($format)); + + return $args; + } + + private static function handleCustomStyles($style, $string) + { + static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'); + static $labels = array(); + + return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) { + if (trim($m[1]) === 'autolabel') { + // Format the string as a label with consistent auto assigned background color + if (!isset($labels[$string])) { + $labels[$string] = $colors[count($labels) % count($colors)]; + } + $color = $labels[$string]; + + return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px"; + } + + return $m[1]; + }, $style); + } + + private static function dump($title, array $dict) + { + $script = array(); + $dict = array_filter($dict); + if (empty($dict)) { + return $script; + } + $script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title)); + foreach ($dict as $key => $value) { + $value = json_encode($value); + if (empty($value)) { + $value = self::quote(''); + } + $script[] = self::call('log', self::quote('%s: %o'), self::quote($key), $value); + } + + return $script; + } + + private static function quote($arg) + { + return '"' . addcslashes($arg, "\"\n\\") . '"'; + } + + private static function call() + { + $args = func_get_args(); + $method = array_shift($args); + + return self::call_array($method, $args); + } + + private static function call_array($method, array $args) + { + return 'c.' . $method . '(' . implode(', ', $args) . ');'; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php new file mode 100644 index 0000000..72f8953 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Buffers all records until closing the handler and then pass them as batch. + * + * This is useful for a MailHandler to send only one mail per request instead of + * sending one per log message. + * + * @author Christophe Coevoet + */ +class BufferHandler extends AbstractHandler +{ + protected $handler; + protected $bufferSize = 0; + protected $bufferLimit; + protected $flushOnOverflow; + protected $buffer = array(); + protected $initialized = false; + + /** + * @param HandlerInterface $handler Handler. + * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded + */ + public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false) + { + parent::__construct($level, $bubble); + $this->handler = $handler; + $this->bufferLimit = (int) $bufferLimit; + $this->flushOnOverflow = $flushOnOverflow; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { + if ($this->flushOnOverflow) { + $this->flush(); + } else { + array_shift($this->buffer); + $this->bufferSize--; + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->buffer[] = $record; + $this->bufferSize++; + + return false === $this->bubble; + } + + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $this->handler->handleBatch($this->buffer); + $this->clear(); + } + + public function __destruct() + { + // suppress the parent behavior since we already have register_shutdown_function() + // to call close(), and the reference contained there will prevent this from being + // GC'd until the end of the request + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + */ + public function clear() + { + $this->bufferSize = 0; + $this->buffer = array(); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php new file mode 100644 index 0000000..b00fa84 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ChromePHPFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) + * + * This also works out of the box with Firefox 43+ + * + * @author Christophe Coevoet + */ +class ChromePHPHandler extends AbstractProcessingHandler +{ + /** + * Version of the extension + */ + const VERSION = '4.0'; + + /** + * Header name + */ + const HEADER_NAME = 'X-ChromeLogger-Data'; + + /** + * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) + */ + const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + + protected static $initialized = false; + + /** + * Tracks whether we sent too much data + * + * Chrome limits the headers to 256KB, so when we sent 240KB we stop sending + * + * @var Boolean + */ + protected static $overflowed = false; + + protected static $json = array( + 'version' => self::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array(), + ); + + protected static $sendHeaders = true; + + /** + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler'); + } + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $messages = $this->getFormatter()->formatBatch($messages); + self::$json['rows'] = array_merge(self::$json['rows'], $messages); + $this->send(); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new ChromePHPFormatter(); + } + + /** + * Creates & sends header for a record + * + * @see sendHeader() + * @see send() + * @param array $record + */ + protected function write(array $record) + { + self::$json['rows'][] = $record['formatted']; + + $this->send(); + } + + /** + * Sends the log header + * + * @see sendHeader() + */ + protected function send() + { + if (self::$overflowed || !self::$sendHeaders) { + return; + } + + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + } + + $json = @json_encode(self::$json); + $data = base64_encode(utf8_encode($json)); + if (strlen($data) > 240 * 1024) { + self::$overflowed = true; + + $record = array( + 'message' => 'Incomplete logs, chrome header size limit reached', + 'context' => array(), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'monolog', + 'datetime' => new \DateTime(), + 'extra' => array(), + ); + self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); + $json = @json_encode(self::$json); + $data = base64_encode(utf8_encode($json)); + } + + if (trim($data) !== '') { + $this->sendHeader(self::HEADER_NAME, $data); + } + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return Boolean + */ + protected function headersAccepted() + { + if (empty($_SERVER['HTTP_USER_AGENT'])) { + return false; + } + + return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php new file mode 100644 index 0000000..cc98697 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\JsonFormatter; +use Monolog\Logger; + +/** + * CouchDB handler + * + * @author Markus Bachmann + */ +class CouchDBHandler extends AbstractProcessingHandler +{ + private $options; + + public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true) + { + $this->options = array_merge(array( + 'host' => 'localhost', + 'port' => 5984, + 'dbname' => 'logger', + 'username' => null, + 'password' => null, + ), $options); + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $basicAuth = null; + if ($this->options['username']) { + $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']); + } + + $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; + $context = stream_context_create(array( + 'http' => array( + 'method' => 'POST', + 'content' => $record['formatted'], + 'ignore_errors' => true, + 'max_redirects' => 0, + 'header' => 'Content-type: application/json', + ), + )); + + if (false === @file_get_contents($url, null, $context)) { + throw new \RuntimeException(sprintf('Could not connect to %s', $url)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php new file mode 100644 index 0000000..96b3ca0 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Logs to Cube. + * + * @link http://square.github.com/cube/ + * @author Wan Chen + */ +class CubeHandler extends AbstractProcessingHandler +{ + private $udpConnection; + private $httpConnection; + private $scheme; + private $host; + private $port; + private $acceptedSchemes = array('http', 'udp'); + + /** + * Create a Cube handler + * + * @throws \UnexpectedValueException when given url is not a valid url. + * A valid url must consist of three parts : protocol://host:port + * Only valid protocols used by Cube are http and udp + */ + public function __construct($url, $level = Logger::DEBUG, $bubble = true) + { + $urlInfo = parse_url($url); + + if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { + throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); + } + + if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) { + throw new \UnexpectedValueException( + 'Invalid protocol (' . $urlInfo['scheme'] . ').' + . ' Valid options are ' . implode(', ', $this->acceptedSchemes)); + } + + $this->scheme = $urlInfo['scheme']; + $this->host = $urlInfo['host']; + $this->port = $urlInfo['port']; + + parent::__construct($level, $bubble); + } + + /** + * Establish a connection to an UDP socket + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when there is no socket extension + */ + protected function connectUdp() + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); + } + + $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (!$this->udpConnection) { + throw new \LogicException('Unable to create a socket'); + } + + if (!socket_connect($this->udpConnection, $this->host, $this->port)) { + throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); + } + } + + /** + * Establish a connection to a http server + * @throws \LogicException when no curl extension + */ + protected function connectHttp() + { + if (!extension_loaded('curl')) { + throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler'); + } + + $this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + + if (!$this->httpConnection) { + throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); + } + + curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $date = $record['datetime']; + + $data = array('time' => $date->format('Y-m-d\TH:i:s.uO')); + unset($record['datetime']); + + if (isset($record['context']['type'])) { + $data['type'] = $record['context']['type']; + unset($record['context']['type']); + } else { + $data['type'] = $record['channel']; + } + + $data['data'] = $record['context']; + $data['data']['level'] = $record['level']; + + if ($this->scheme === 'http') { + $this->writeHttp(json_encode($data)); + } else { + $this->writeUdp(json_encode($data)); + } + } + + private function writeUdp($data) + { + if (!$this->udpConnection) { + $this->connectUdp(); + } + + socket_send($this->udpConnection, $data, strlen($data), 0); + } + + private function writeHttp($data) + { + if (!$this->httpConnection) { + $this->connectHttp(); + } + + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array( + 'Content-Type: application/json', + 'Content-Length: ' . strlen('['.$data.']'), + )); + + Curl\Util::execute($this->httpConnection, 5, false); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/src/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php new file mode 100644 index 0000000..48d30b3 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Curl; + +class Util +{ + private static $retriableErrorCodes = array( + CURLE_COULDNT_RESOLVE_HOST, + CURLE_COULDNT_CONNECT, + CURLE_HTTP_NOT_FOUND, + CURLE_READ_ERROR, + CURLE_OPERATION_TIMEOUTED, + CURLE_HTTP_POST_ERROR, + CURLE_SSL_CONNECT_ERROR, + ); + + /** + * Executes a CURL request with optional retries and exception on failure + * + * @param resource $ch curl handler + * @throws \RuntimeException + */ + public static function execute($ch, $retries = 5, $closeAfterDone = true) + { + while ($retries--) { + if (curl_exec($ch) === false) { + $curlErrno = curl_errno($ch); + + if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) { + $curlError = curl_error($ch); + + if ($closeAfterDone) { + curl_close($ch); + } + + throw new \RuntimeException(sprintf('Curl error (code %s): %s', $curlErrno, $curlError)); + } + + continue; + } + + if ($closeAfterDone) { + curl_close($ch); + } + break; + } + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php new file mode 100644 index 0000000..7778c22 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Simple handler wrapper that deduplicates log records across multiple requests + * + * It also includes the BufferHandler functionality and will buffer + * all messages until the end of the request or flush() is called. + * + * This works by storing all log records' messages above $deduplicationLevel + * to the file specified by $deduplicationStore. When further logs come in at the end of the + * request (or when flush() is called), all those above $deduplicationLevel are checked + * against the existing stored logs. If they match and the timestamps in the stored log is + * not older than $time seconds, the new log record is discarded. If no log record is new, the + * whole data set is discarded. + * + * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers + * that send messages to people, to avoid spamming with the same message over and over in case of + * a major component failure like a database server being down which makes all requests fail in the + * same way. + * + * @author Jordi Boggiano + */ +class DeduplicationHandler extends BufferHandler +{ + /** + * @var string + */ + protected $deduplicationStore; + + /** + * @var int + */ + protected $deduplicationLevel; + + /** + * @var int + */ + protected $time; + + /** + * @var bool + */ + private $gc = false; + + /** + * @param HandlerInterface $handler Handler. + * @param string $deduplicationStore The file/path where the deduplication log should be kept + * @param int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes + * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true) + { + parent::__construct($handler, 0, Logger::DEBUG, $bubble, false); + + $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore; + $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel); + $this->time = $time; + } + + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $passthru = null; + + foreach ($this->buffer as $record) { + if ($record['level'] >= $this->deduplicationLevel) { + + $passthru = $passthru || !$this->isDuplicate($record); + if ($passthru) { + $this->appendRecord($record); + } + } + } + + // default of null is valid as well as if no record matches duplicationLevel we just pass through + if ($passthru === true || $passthru === null) { + $this->handler->handleBatch($this->buffer); + } + + $this->clear(); + + if ($this->gc) { + $this->collectLogs(); + } + } + + private function isDuplicate(array $record) + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($store)) { + return false; + } + + $yesterday = time() - 86400; + $timestampValidity = $record['datetime']->getTimestamp() - $this->time; + $expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']); + + for ($i = count($store) - 1; $i >= 0; $i--) { + list($timestamp, $level, $message) = explode(':', $store[$i], 3); + + if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) { + return true; + } + + if ($timestamp < $yesterday) { + $this->gc = true; + } + } + + return false; + } + + private function collectLogs() + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $handle = fopen($this->deduplicationStore, 'rw+'); + flock($handle, LOCK_EX); + $validLogs = array(); + + $timestampValidity = time() - $this->time; + + while (!feof($handle)) { + $log = fgets($handle); + if (substr($log, 0, 10) >= $timestampValidity) { + $validLogs[] = $log; + } + } + + ftruncate($handle, 0); + rewind($handle); + foreach ($validLogs as $log) { + fwrite($handle, $log); + } + + flock($handle, LOCK_UN); + fclose($handle); + + $this->gc = false; + } + + private function appendRecord(array $record) + { + file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php new file mode 100644 index 0000000..b91ffec --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; +use Doctrine\CouchDB\CouchDBClient; + +/** + * CouchDB handler for Doctrine CouchDB ODM + * + * @author Markus Bachmann + */ +class DoctrineCouchDBHandler extends AbstractProcessingHandler +{ + private $client; + + public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubble = true) + { + $this->client = $client; + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $this->client->postDocument($record['formatted']); + } + + protected function getDefaultFormatter() + { + return new NormalizerFormatter; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php new file mode 100644 index 0000000..ad1011d --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Common\Aws; +use Aws\DynamoDb\DynamoDbClient; +use Aws\DynamoDb\Marshaler; +use Monolog\Formatter\ScalarFormatter; +use Monolog\Logger; + +/** + * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/) + * + * @link https://github.com/aws/aws-sdk-php/ + * @author Andrew Lawson + */ +class DynamoDbHandler extends AbstractProcessingHandler +{ + const DATE_FORMAT = 'Y-m-d\TH:i:s.uO'; + + /** + * @var DynamoDbClient + */ + protected $client; + + /** + * @var string + */ + protected $table; + + /** + * @var int + */ + protected $version; + + /** + * @var Marshaler + */ + protected $marshaler; + + /** + * @param DynamoDbClient $client + * @param string $table + * @param int $level + * @param bool $bubble + */ + public function __construct(DynamoDbClient $client, $table, $level = Logger::DEBUG, $bubble = true) + { + if (defined('Aws\Common\Aws::VERSION') && version_compare(Aws::VERSION, '3.0', '>=')) { + $this->version = 3; + $this->marshaler = new Marshaler; + } else { + $this->version = 2; + } + + $this->client = $client; + $this->table = $table; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $filtered = $this->filterEmptyFields($record['formatted']); + if ($this->version === 3) { + $formatted = $this->marshaler->marshalItem($filtered); + } else { + $formatted = $this->client->formatAttributes($filtered); + } + + $this->client->putItem(array( + 'TableName' => $this->table, + 'Item' => $formatted, + )); + } + + /** + * @param array $record + * @return array + */ + protected function filterEmptyFields(array $record) + { + return array_filter($record, function ($value) { + return !empty($value) || false === $value || 0 === $value; + }); + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new ScalarFormatter(self::DATE_FORMAT); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php new file mode 100644 index 0000000..8196740 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Exception\ExceptionInterface; + +/** + * Elastic Search handler + * + * Usage example: + * + * $client = new \Elastica\Client(); + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', + * ); + * $handler = new ElasticSearchHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Jelle Vink + */ +class ElasticSearchHandler extends AbstractProcessingHandler +{ + /** + * @var Client + */ + protected $client; + + /** + * @var array Handler config options + */ + protected $options = array(); + + /** + * @param Client $client Elastica Client object + * @param array $options Handler configuration + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + array( + 'index' => 'monolog', // Elastic index name + 'type' => 'record', // Elastic document type + 'ignore_error' => false, // Suppress Elastica exceptions + ), + $options + ); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $this->bulkSend(array($record['formatted'])); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + if ($formatter instanceof ElasticaFormatter) { + return parent::setFormatter($formatter); + } + throw new \InvalidArgumentException('ElasticSearchHandler is only compatible with ElasticaFormatter'); + } + + /** + * Getter options + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new ElasticaFormatter($this->options['index'], $this->options['type']); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * @param array $documents + * @throws \RuntimeException + */ + protected function bulkSend(array $documents) + { + try { + $this->client->addDocuments($documents); + } catch (ExceptionInterface $e) { + if (!$this->options['ignore_error']) { + throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + } + } + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php new file mode 100644 index 0000000..1447a58 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Stores to PHP error_log() handler. + * + * @author Elan Ruusamäe + */ +class ErrorLogHandler extends AbstractProcessingHandler +{ + const OPERATING_SYSTEM = 0; + const SAPI = 4; + + protected $messageType; + protected $expandNewlines; + + /** + * @param int $messageType Says where the error should go. + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries + */ + public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true, $expandNewlines = false) + { + parent::__construct($level, $bubble); + + if (false === in_array($messageType, self::getAvailableTypes())) { + $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true)); + throw new \InvalidArgumentException($message); + } + + $this->messageType = $messageType; + $this->expandNewlines = $expandNewlines; + } + + /** + * @return array With all available types + */ + public static function getAvailableTypes() + { + return array( + self::OPERATING_SYSTEM, + self::SAPI, + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%'); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if ($this->expandNewlines) { + $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); + foreach ($lines as $line) { + error_log($line, $this->messageType); + } + } else { + error_log((string) $record['formatted'], $this->messageType); + } + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php new file mode 100644 index 0000000..2a0f7fd --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Simple handler wrapper that filters records based on a list of levels + * + * It can be configured with an exact list of levels to allow, or a min/max level. + * + * @author Hennadiy Verkh + * @author Jordi Boggiano + */ +class FilterHandler extends AbstractHandler +{ + /** + * Handler or factory callable($record, $this) + * + * @var callable|\Monolog\Handler\HandlerInterface + */ + protected $handler; + + /** + * Minimum level for logs that are passed to handler + * + * @var int[] + */ + protected $acceptedLevels; + + /** + * Whether the messages that are handled can bubble up the stack or not + * + * @var Boolean + */ + protected $bubble; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $this). + * @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided + * @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true) + { + $this->handler = $handler; + $this->bubble = $bubble; + $this->setAcceptedLevels($minLevelOrList, $maxLevel); + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * @return array + */ + public function getAcceptedLevels() + { + return array_flip($this->acceptedLevels); + } + + /** + * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided + * @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array + */ + public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY) + { + if (is_array($minLevelOrList)) { + $acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList); + } else { + $minLevelOrList = Logger::toMonologLevel($minLevelOrList); + $maxLevel = Logger::toMonologLevel($maxLevel); + $acceptedLevels = array_values(array_filter(Logger::getLevels(), function ($level) use ($minLevelOrList, $maxLevel) { + return $level >= $minLevelOrList && $level <= $maxLevel; + })); + } + $this->acceptedLevels = array_flip($acceptedLevels); + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return isset($this->acceptedLevels[$record['level']]); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + // The same logic as in FingersCrossedHandler + if (!$this->handler instanceof HandlerInterface) { + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->handler->handle($record); + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $filtered = array(); + foreach ($records as $record) { + if ($this->isHandling($record)) { + $filtered[] = $record; + } + } + + $this->handler->handleBatch($filtered); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php new file mode 100644 index 0000000..c3e42ef --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +/** + * Interface for activation strategies for the FingersCrossedHandler. + * + * @author Johannes M. Schmitt + */ +interface ActivationStrategyInterface +{ + /** + * Returns whether the given record activates the handler. + * + * @param array $record + * @return Boolean + */ + public function isHandlerActivated(array $record); +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php new file mode 100644 index 0000000..2a2a64d --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Logger; + +/** + * Channel and Error level based monolog activation strategy. Allows to trigger activation + * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except + * for records of the 'sql' channel; those should trigger activation on level 'WARN'. + * + * Example: + * + * + * $activationStrategy = new ChannelLevelActivationStrategy( + * Logger::CRITICAL, + * array( + * 'request' => Logger::ALERT, + * 'sensitive' => Logger::ERROR, + * ) + * ); + * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); + * + * + * @author Mike Meessen + */ +class ChannelLevelActivationStrategy implements ActivationStrategyInterface +{ + private $defaultActionLevel; + private $channelToActionLevel; + + /** + * @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $channelToActionLevel An array that maps channel names to action levels. + */ + public function __construct($defaultActionLevel, $channelToActionLevel = array()) + { + $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); + $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel); + } + + public function isHandlerActivated(array $record) + { + if (isset($this->channelToActionLevel[$record['channel']])) { + return $record['level'] >= $this->channelToActionLevel[$record['channel']]; + } + + return $record['level'] >= $this->defaultActionLevel; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php new file mode 100644 index 0000000..6e63085 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Logger; + +/** + * Error level based activation strategy. + * + * @author Johannes M. Schmitt + */ +class ErrorLevelActivationStrategy implements ActivationStrategyInterface +{ + private $actionLevel; + + public function __construct($actionLevel) + { + $this->actionLevel = Logger::toMonologLevel($actionLevel); + } + + public function isHandlerActivated(array $record) + { + return $record['level'] >= $this->actionLevel; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php new file mode 100644 index 0000000..d1dcaac --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Monolog\Logger; + +/** + * Buffers all records until a certain level is reached + * + * The advantage of this approach is that you don't get any clutter in your log files. + * Only requests which actually trigger an error (or whatever your actionLevel is) will be + * in the logs, but they will contain all records, not only those above the level threshold. + * + * You can find the various activation strategies in the + * Monolog\Handler\FingersCrossed\ namespace. + * + * @author Jordi Boggiano + */ +class FingersCrossedHandler extends AbstractHandler +{ + protected $handler; + protected $activationStrategy; + protected $buffering = true; + protected $bufferSize; + protected $buffer = array(); + protected $stopBuffering; + protected $passthruLevel; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). + * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $stopBuffering Whether the handler should stop buffering after being triggered (default true) + * @param int $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + */ + public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null) + { + if (null === $activationStrategy) { + $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); + } + + // convert simple int activationStrategy to an object + if (!$activationStrategy instanceof ActivationStrategyInterface) { + $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); + } + + $this->handler = $handler; + $this->activationStrategy = $activationStrategy; + $this->bufferSize = $bufferSize; + $this->bubble = $bubble; + $this->stopBuffering = $stopBuffering; + + if ($passthruLevel !== null) { + $this->passthruLevel = Logger::toMonologLevel($passthruLevel); + } + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return true; + } + + /** + * Manually activate this logger regardless of the activation strategy + */ + public function activate() + { + if ($this->stopBuffering) { + $this->buffering = false; + } + if (!$this->handler instanceof HandlerInterface) { + $record = end($this->buffer) ?: null; + + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + $this->handler->handleBatch($this->buffer); + $this->buffer = array(); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + if ($this->buffering) { + $this->buffer[] = $record; + if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + if ($this->activationStrategy->isHandlerActivated($record)) { + $this->activate(); + } + } else { + $this->handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function close() + { + if (null !== $this->passthruLevel) { + $level = $this->passthruLevel; + $this->buffer = array_filter($this->buffer, function ($record) use ($level) { + return $record['level'] >= $level; + }); + if (count($this->buffer) > 0) { + $this->handler->handleBatch($this->buffer); + $this->buffer = array(); + } + } + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. + */ + public function reset() + { + $this->buffering = true; + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + * + * It also resets the handler to its initial buffering state. + */ + public function clear() + { + $this->buffer = array(); + $this->reset(); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 0000000..fee4795 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\WildfireFormatter; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + */ +class FirePHPHandler extends AbstractProcessingHandler +{ + /** + * WildFire JSON header message format + */ + const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + /** + * FirePHP structure for parsing messages & their presentation + */ + const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ + const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ + const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + */ + protected static $initialized = false; + + /** + * Shared static message index between potentially multiple handlers + * @var int + */ + protected static $messageIndex = 1; + + protected static $sendHeaders = true; + + /** + * Base header creation function used by init headers & record headers + * + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * @return array Complete header string ready for the client as key and message as value + */ + protected function createHeader(array $meta, $message) + { + $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); + + return array($header => $message); + } + + /** + * Creates message header from record + * + * @see createHeader() + * @param array $record + * @return string + */ + protected function createRecordHeader(array $record) + { + // Wildfire is extensible to support multiple protocols & plugins in a single request, + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + array(1, 1, 1, self::$messageIndex++), + $record['formatted'] + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new WildfireFormatter(); + } + + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * @return array + */ + protected function getInitHeaders() + { + // Initial payload consists of required headers for Wildfire + return array_merge( + $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), + $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), + $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) + ); + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + * @param array $record + */ + protected function write(array $record) + { + if (!self::$sendHeaders) { + return; + } + + // WildFire-specific headers must be sent prior to any messages + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); + } + } + + $header = $this->createRecordHeader($record); + if (trim(current($header)) !== '') { + $this->sendHeader(key($header), current($header)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return Boolean + */ + protected function headersAccepted() + { + if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } + + return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php new file mode 100644 index 0000000..c43c013 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Sends logs to Fleep.io using Webhook integrations + * + * You'll need a Fleep.io account to use this handler. + * + * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation + * @author Ando Roots + */ +class FleepHookHandler extends SocketHandler +{ + const FLEEP_HOST = 'fleep.io'; + + const FLEEP_HOOK_URI = '/hook/'; + + /** + * @var string Webhook token (specifies the conversation where logs are sent) + */ + protected $token; + + /** + * Construct a new Fleep.io Handler. + * + * For instructions on how to create a new web hook in your conversations + * see https://fleep.io/integrations/webhooks/ + * + * @param string $token Webhook token + * @param bool|int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @throws MissingExtensionException + */ + public function __construct($token, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); + } + + $this->token = $token; + + $connectionString = 'ssl://' . self::FLEEP_HOST . ':443'; + parent::__construct($connectionString, $level, $bubble); + } + + /** + * Returns the default formatter to use with this handler + * + * Overloaded to remove empty context and extra arrays from the end of the log message. + * + * @return LineFormatter + */ + protected function getDefaultFormatter() + { + return new LineFormatter(null, null, true, true); + } + + /** + * Handles a log record + * + * @param array $record + */ + public function write(array $record) + { + parent::write($record); + $this->closeSocket(); + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; + $header .= "Host: " . self::FLEEP_HOST . "\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = array( + 'message' => $record['formatted'], + ); + + return http_build_query($dataArray); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php new file mode 100644 index 0000000..dd9a361 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FlowdockFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Sends notifications through the Flowdock push API + * + * This must be configured with a FlowdockFormatter instance via setFormatter() + * + * Notes: + * API token - Flowdock API token + * + * @author Dominik Liebler + * @see https://www.flowdock.com/api/push + */ +class FlowdockHandler extends SocketHandler +{ + /** + * @var string + */ + protected $apiToken; + + /** + * @param string $apiToken + * @param bool|int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @throws MissingExtensionException if OpenSSL is missing + */ + public function __construct($apiToken, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); + } + + parent::__construct('ssl://api.flowdock.com:443', $level, $bubble); + $this->apiToken = $apiToken; + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + if (!$formatter instanceof FlowdockFormatter) { + throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + return parent::setFormatter($formatter); + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + + $this->closeSocket(); + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + return json_encode($record['formatted']['flowdock']); + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; + $header .= "Host: api.flowdock.com\r\n"; + $header .= "Content-Type: application/json\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php new file mode 100644 index 0000000..d3847d8 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\IMessagePublisher; +use Gelf\PublisherInterface; +use Gelf\Publisher; +use InvalidArgumentException; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +/** + * Handler to send messages to a Graylog2 (http://www.graylog2.org) server + * + * @author Matt Lehner + * @author Benjamin Zikarsky + */ +class GelfHandler extends AbstractProcessingHandler +{ + /** + * @var Publisher the publisher object that sends the message to the server + */ + protected $publisher; + + /** + * @param PublisherInterface|IMessagePublisher|Publisher $publisher a publisher object + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($publisher, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$publisher instanceof Publisher && !$publisher instanceof IMessagePublisher && !$publisher instanceof PublisherInterface) { + throw new InvalidArgumentException('Invalid publisher, expected a Gelf\Publisher, Gelf\IMessagePublisher or Gelf\PublisherInterface instance'); + } + + $this->publisher = $publisher; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->publisher = null; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->publisher->publish($record['formatted']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new GelfMessageFormatter(); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php new file mode 100644 index 0000000..663f5a9 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.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 Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Forwards records to multiple handlers + * + * @author Lenar Lõhmus + */ +class GroupHandler extends AbstractHandler +{ + protected $handlers; + + /** + * @param array $handlers Array of Handlers. + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(array $handlers, $bubble = true) + { + foreach ($handlers as $handler) { + if (!$handler instanceof HandlerInterface) { + throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); + } + } + + $this->handlers = $handlers; + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + foreach ($this->handlers as $handler) { + $handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + if ($this->processors) { + $processed = array(); + foreach ($records as $record) { + foreach ($this->processors as $processor) { + $processed[] = call_user_func($processor, $record); + } + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + $handler->handleBatch($records); + } + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + foreach ($this->handlers as $handler) { + $handler->setFormatter($formatter); + } + + return $this; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/src/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php new file mode 100644 index 0000000..d920c4b --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface that all Monolog Handlers must implement + * + * @author Jordi Boggiano + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * Handlers should still check the record levels within handle(), returning false in isHandling() + * is no guarantee that handle() will not be called, and isHandling() might not be called + * for a given record. + * + * @param array $record Partial log record containing only a level key + * + * @return Boolean + */ + public function isHandling(array $record); + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @param array $record The record to handle + * @return Boolean true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + */ + public function handle(array $record); + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle (an array of record arrays) + */ + public function handleBatch(array $records); + + /** + * Adds a processor in the stack. + * + * @param callable $callback + * @return self + */ + public function pushProcessor($callback); + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor(); + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return self + */ + public function setFormatter(FormatterInterface $formatter); + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(); +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/src/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php new file mode 100644 index 0000000..56bc270 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.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 Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * This simple wrapper class can be used to extend handlers functionality. + * + * Example: A filtering handle. Inherit from this class, override isHandling() like this + * + * public function isHandling(array $record) + * { + * if ($record meets certain conditions) { + * return false; + * } + * return $this->handler->isHandling($record); + * } + * + * @author Alexey Karapetov + */ +class HandlerWrapper implements HandlerInterface +{ + /** + * @var HandlerInterface + */ + protected $handler; + + /** + * HandlerWrapper constructor. + * @param HandlerInterface $handler + */ + public function __construct(HandlerInterface $handler) + { + $this->handler = $handler; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $this->handler->isHandling($record); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + return $this->handler->handle($record); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + return $this->handler->handleBatch($records); + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + $this->handler->pushProcessor($callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + return $this->handler->popProcessor(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->handler->setFormatter($formatter); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->handler->getFormatter(); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php new file mode 100644 index 0000000..73049f3 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php @@ -0,0 +1,350 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the hipchat api to a hipchat room + * + * Notes: + * API token - HipChat API token + * Room - HipChat Room Id or name, where messages are sent + * Name - Name used to send the message (from) + * notify - Should the message trigger a notification in the clients + * version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2) + * + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandler extends SocketHandler +{ + /** + * Use API version 1 + */ + const API_V1 = 'v1'; + + /** + * Use API version v2 + */ + const API_V2 = 'v2'; + + /** + * The maximum allowed length for the name used in the "from" field. + */ + const MAXIMUM_NAME_LENGTH = 15; + + /** + * The maximum allowed length for the message. + */ + const MAXIMUM_MESSAGE_LENGTH = 9500; + + /** + * @var string + */ + private $token; + + /** + * @var string + */ + private $room; + + /** + * @var string + */ + private $name; + + /** + * @var bool + */ + private $notify; + + /** + * @var string + */ + private $format; + + /** + * @var string + */ + private $host; + + /** + * @var string + */ + private $version; + + /** + * @param string $token HipChat API Token + * @param string $room The room that should be alerted of the message (Id or Name) + * @param string $name Name used in the "from" field. + * @param bool $notify Trigger a notification in clients or not + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useSSL Whether to connect via SSL. + * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) + * @param string $host The HipChat server hostname. + * @param string $version The HipChat API version (default HipChatHandler::API_V1) + */ + public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1) + { + if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) { + throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.'); + } + + $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->name = $name; + $this->notify = $notify; + $this->room = $room; + $this->format = $format; + $this->host = $host; + $this->version = $version; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = array( + 'notify' => $this->version == self::API_V1 ? + ($this->notify ? 1 : 0) : + ($this->notify ? 'true' : 'false'), + 'message' => $record['formatted'], + 'message_format' => $this->format, + 'color' => $this->getAlertColor($record['level']), + ); + + if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) { + if (function_exists('mb_substr')) { + $dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; + } else { + $dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; + } + } + + // if we are using the legacy API then we need to send some additional information + if ($this->version == self::API_V1) { + $dataArray['room_id'] = $this->room; + } + + // append the sender name if it is set + // always append it if we use the v1 api (it is required in v1) + if ($this->version == self::API_V1 || $this->name !== null) { + $dataArray['from'] = (string) $this->name; + } + + return http_build_query($dataArray); + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + if ($this->version == self::API_V1) { + $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n"; + } else { + // needed for rooms with special (spaces, etc) characters in the name + $room = rawurlencode($this->room); + $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n"; + } + + $header .= "Host: {$this->host}\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Assigns a color to each level of log records. + * + * @param int $level + * @return string + */ + protected function getAlertColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return 'red'; + case $level >= Logger::WARNING: + return 'yellow'; + case $level >= Logger::INFO: + return 'green'; + case $level == Logger::DEBUG: + return 'gray'; + default: + return 'yellow'; + } + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + $this->closeSocket(); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + if (count($records) == 0) { + return true; + } + + $batchRecords = $this->combineRecords($records); + + $handled = false; + foreach ($batchRecords as $batchRecord) { + if ($this->isHandling($batchRecord)) { + $this->write($batchRecord); + $handled = true; + } + } + + if (!$handled) { + return false; + } + + return false === $this->bubble; + } + + /** + * Combines multiple records into one. Error level of the combined record + * will be the highest level from the given records. Datetime will be taken + * from the first record. + * + * @param $records + * @return array + */ + private function combineRecords($records) + { + $batchRecord = null; + $batchRecords = array(); + $messages = array(); + $formattedMessages = array(); + $level = 0; + $levelName = null; + $datetime = null; + + foreach ($records as $record) { + $record = $this->processRecord($record); + + if ($record['level'] > $level) { + $level = $record['level']; + $levelName = $record['level_name']; + } + + if (null === $datetime) { + $datetime = $record['datetime']; + } + + $messages[] = $record['message']; + $messageStr = implode(PHP_EOL, $messages); + $formattedMessages[] = $this->getFormatter()->format($record); + $formattedMessageStr = implode('', $formattedMessages); + + $batchRecord = array( + 'message' => $messageStr, + 'formatted' => $formattedMessageStr, + 'context' => array(), + 'extra' => array(), + ); + + if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) { + // Pop the last message and implode the remaining messages + $lastMessage = array_pop($messages); + $lastFormattedMessage = array_pop($formattedMessages); + $batchRecord['message'] = implode(PHP_EOL, $messages); + $batchRecord['formatted'] = implode('', $formattedMessages); + + $batchRecords[] = $batchRecord; + $messages = array($lastMessage); + $formattedMessages = array($lastFormattedMessage); + + $batchRecord = null; + } + } + + if (null !== $batchRecord) { + $batchRecords[] = $batchRecord; + } + + // Set the max level and datetime for all records + foreach ($batchRecords as &$batchRecord) { + $batchRecord = array_merge( + $batchRecord, + array( + 'level' => $level, + 'level_name' => $levelName, + 'datetime' => $datetime, + ) + ); + } + + return $batchRecords; + } + + /** + * Validates the length of a string. + * + * If the `mb_strlen()` function is available, it will use that, as HipChat + * allows UTF-8 characters. Otherwise, it will fall back to `strlen()`. + * + * Note that this might cause false failures in the specific case of using + * a valid name with less than 16 characters, but 16 or more bytes, on a + * system where `mb_strlen()` is unavailable. + * + * @param string $str + * @param int $length + * + * @return bool + */ + private function validateStringLength($str, $length) + { + if (function_exists('mb_strlen')) { + return (mb_strlen($str) <= $length); + } + + return (strlen($str) <= $length); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php new file mode 100644 index 0000000..d60a3c8 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * IFTTTHandler uses cURL to trigger IFTTT Maker actions + * + * Register a secret key and trigger/event name at https://ifttt.com/maker + * + * value1 will be the channel from monolog's Logger constructor, + * value2 will be the level name (ERROR, WARNING, ..) + * value3 will be the log record's message + * + * @author Nehal Patel + */ +class IFTTTHandler extends AbstractProcessingHandler +{ + private $eventName; + private $secretKey; + + /** + * @param string $eventName The name of the IFTTT Maker event that should be triggered + * @param string $secretKey A valid IFTTT secret key + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bubble = true) + { + $this->eventName = $eventName; + $this->secretKey = $secretKey; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + public function write(array $record) + { + $postData = array( + "value1" => $record["channel"], + "value2" => $record["level_name"], + "value3" => $record["message"], + ); + $postString = json_encode($postData); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + "Content-Type: application/json", + )); + + Curl\Util::execute($ch); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php new file mode 100644 index 0000000..494c605 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandler extends SocketHandler +{ + /** + * @var string + */ + protected $logToken; + + /** + * @param string $token Log token supplied by LogEntries + * @param bool $useSSL Whether or not SSL encryption should be used. + * @param int $level The minimum logging level to trigger this handler + * @param bool $bubble Whether or not messages that are handled should bubble up the stack. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct($token, $useSSL = true, $level = Logger::DEBUG, $bubble = true) + { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler'); + } + + $endpoint = $useSSL ? 'ssl://data.logentries.com:443' : 'data.logentries.com:80'; + parent::__construct($endpoint, $level, $bubble); + $this->logToken = $token; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + return $this->logToken . ' ' . $record['formatted']; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php new file mode 100644 index 0000000..bcd62e1 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LogglyFormatter; + +/** + * Sends errors to Loggly. + * + * @author Przemek Sobstel + * @author Adam Pancutt + * @author Gregory Barchard + */ +class LogglyHandler extends AbstractProcessingHandler +{ + const HOST = 'logs-01.loggly.com'; + const ENDPOINT_SINGLE = 'inputs'; + const ENDPOINT_BATCH = 'bulk'; + + protected $token; + + protected $tag = array(); + + public function __construct($token, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('curl')) { + throw new \LogicException('The curl extension is needed to use the LogglyHandler'); + } + + $this->token = $token; + + parent::__construct($level, $bubble); + } + + public function setTag($tag) + { + $tag = !empty($tag) ? $tag : array(); + $this->tag = is_array($tag) ? $tag : array($tag); + } + + public function addTag($tag) + { + if (!empty($tag)) { + $tag = is_array($tag) ? $tag : array($tag); + $this->tag = array_unique(array_merge($this->tag, $tag)); + } + } + + protected function write(array $record) + { + $this->send($record["formatted"], self::ENDPOINT_SINGLE); + } + + public function handleBatch(array $records) + { + $level = $this->level; + + $records = array_filter($records, function ($record) use ($level) { + return ($record['level'] >= $level); + }); + + if ($records) { + $this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH); + } + } + + protected function send($data, $endpoint) + { + $url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token); + + $headers = array('Content-Type: application/json'); + + if (!empty($this->tag)) { + $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); + } + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + Curl\Util::execute($ch); + } + + protected function getDefaultFormatter() + { + return new LogglyFormatter(); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php new file mode 100644 index 0000000..9e23283 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base class for all mail handlers + * + * @author Gyula Sallai + */ +abstract class MailHandler extends AbstractProcessingHandler +{ + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); + } + } + + /** + * Send a mail with the given content + * + * @param string $content formatted email body to be sent + * @param array $records the array of log records that formed this content + */ + abstract protected function send($content, array $records); + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->send((string) $record['formatted'], array($record)); + } + + protected function getHighestRecord(array $records) + { + $highestRecord = null; + foreach ($records as $record) { + if ($highestRecord === null || $highestRecord['level'] < $record['level']) { + $highestRecord = $record; + } + } + + return $highestRecord; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php new file mode 100644 index 0000000..ab95924 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * MandrillHandler uses cURL to send the emails to the Mandrill API + * + * @author Adam Nicholson + */ +class MandrillHandler extends MailHandler +{ + protected $message; + protected $apiKey; + + /** + * @param string $apiKey A valid Mandrill API key + * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$message instanceof \Swift_Message && is_callable($message)) { + $message = call_user_func($message); + } + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); + } + $this->message = $message; + $this->apiKey = $apiKey; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $message = clone $this->message; + $message->setBody($content); + $message->setDate(time()); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + 'key' => $this->apiKey, + 'raw_message' => (string) $message, + 'async' => false, + ))); + + Curl\Util::execute($ch); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/src/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php new file mode 100644 index 0000000..4724a7e --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Exception can be thrown if an extension for an handler is missing + * + * @author Christian Bergau + */ +class MissingExtensionException extends \Exception +{ +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php new file mode 100644 index 0000000..56fe755 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; + +/** + * Logs to a MongoDB database. + * + * usage example: + * + * $log = new Logger('application'); + * $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod"); + * $log->pushHandler($mongodb); + * + * @author Thomas Tourlourat + */ +class MongoDBHandler extends AbstractProcessingHandler +{ + protected $mongoCollection; + + public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true) + { + if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) { + throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required'); + } + + $this->mongoCollection = $mongo->selectCollection($database, $collection); + + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + if ($this->mongoCollection instanceof \MongoDB\Collection) { + $this->mongoCollection->insertOne($record["formatted"]); + } else { + $this->mongoCollection->save($record["formatted"]); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new NormalizerFormatter(); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php new file mode 100644 index 0000000..d7807fd --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * NativeMailerHandler uses the mail() function to send the emails + * + * @author Christophe Coevoet + * @author Mark Garrett + */ +class NativeMailerHandler extends MailHandler +{ + /** + * The email addresses to which the message will be sent + * @var array + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * Optional headers for the message + * @var array + */ + protected $headers = array(); + + /** + * Optional parameters for the message + * @var array + */ + protected $parameters = array(); + + /** + * The wordwrap length for the message + * @var int + */ + protected $maxColumnWidth; + + /** + * The Content-type for the message + * @var string + */ + protected $contentType = 'text/plain'; + + /** + * The encoding for the message + * @var string + */ + protected $encoding = 'utf-8'; + + /** + * @param string|array $to The receiver of the mail + * @param string $subject The subject of the mail + * @param string $from The sender of the mail + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $maxColumnWidth The maximum column width that the message lines will have + */ + public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70) + { + parent::__construct($level, $bubble); + $this->to = is_array($to) ? $to : array($to); + $this->subject = $subject; + $this->addHeader(sprintf('From: %s', $from)); + $this->maxColumnWidth = $maxColumnWidth; + } + + /** + * Add headers to the message + * + * @param string|array $headers Custom added headers + * @return self + */ + public function addHeader($headers) + { + foreach ((array) $headers as $header) { + if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { + throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); + } + $this->headers[] = $header; + } + + return $this; + } + + /** + * Add parameters to the message + * + * @param string|array $parameters Custom added parameters + * @return self + */ + public function addParameter($parameters) + { + $this->parameters = array_merge($this->parameters, (array) $parameters); + + return $this; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $content = wordwrap($content, $this->maxColumnWidth); + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); + $headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n"; + if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'MIME-Version: 1.0' . "\r\n"; + } + + $subject = $this->subject; + if ($records) { + $subjectFormatter = new LineFormatter($this->subject); + $subject = $subjectFormatter->format($this->getHighestRecord($records)); + } + + $parameters = implode(' ', $this->parameters); + foreach ($this->to as $to) { + mail($to, $subject, $content, $headers, $parameters); + } + } + + /** + * @return string $contentType + */ + public function getContentType() + { + return $this->contentType; + } + + /** + * @return string $encoding + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML + * messages. + * @return self + */ + public function setContentType($contentType) + { + if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { + throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); + } + + $this->contentType = $contentType; + + return $this; + } + + /** + * @param string $encoding + * @return self + */ + public function setEncoding($encoding) + { + if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { + throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); + } + + $this->encoding = $encoding; + + return $this; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php new file mode 100644 index 0000000..6718e9e --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; + +/** + * Class to record a log on a NewRelic application. + * Enabling New Relic High Security mode may prevent capture of useful information. + * + * @see https://docs.newrelic.com/docs/agents/php-agent + * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security + */ +class NewRelicHandler extends AbstractProcessingHandler +{ + /** + * Name of the New Relic application that will receive logs from this handler. + * + * @var string + */ + protected $appName; + + /** + * Name of the current transaction + * + * @var string + */ + protected $transactionName; + + /** + * Some context and extra data is passed into the handler as arrays of values. Do we send them as is + * (useful if we are using the API), or explode them for display on the NewRelic RPM website? + * + * @var bool + */ + protected $explodeArrays; + + /** + * {@inheritDoc} + * + * @param string $appName + * @param bool $explodeArrays + * @param string $transactionName + */ + public function __construct( + $level = Logger::ERROR, + $bubble = true, + $appName = null, + $explodeArrays = false, + $transactionName = null + ) { + parent::__construct($level, $bubble); + + $this->appName = $appName; + $this->explodeArrays = $explodeArrays; + $this->transactionName = $transactionName; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + if (!$this->isNewRelicEnabled()) { + throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); + } + + if ($appName = $this->getAppName($record['context'])) { + $this->setNewRelicAppName($appName); + } + + if ($transactionName = $this->getTransactionName($record['context'])) { + $this->setNewRelicTransactionName($transactionName); + unset($record['formatted']['context']['transaction_name']); + } + + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + newrelic_notice_error($record['message'], $record['context']['exception']); + unset($record['formatted']['context']['exception']); + } else { + newrelic_notice_error($record['message']); + } + + if (isset($record['formatted']['context']) && is_array($record['formatted']['context'])) { + foreach ($record['formatted']['context'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('context_' . $key, $parameter); + } + } + } + + if (isset($record['formatted']['extra']) && is_array($record['formatted']['extra'])) { + foreach ($record['formatted']['extra'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('extra_' . $key, $parameter); + } + } + } + } + + /** + * Checks whether the NewRelic extension is enabled in the system. + * + * @return bool + */ + protected function isNewRelicEnabled() + { + return extension_loaded('newrelic'); + } + + /** + * Returns the appname where this log should be sent. Each log can override the default appname, set in this + * handler's constructor, by providing the appname in it's context. + * + * @param array $context + * @return null|string + */ + protected function getAppName(array $context) + { + if (isset($context['appname'])) { + return $context['appname']; + } + + return $this->appName; + } + + /** + * Returns the name of the current transaction. Each log can override the default transaction name, set in this + * handler's constructor, by providing the transaction_name in it's context + * + * @param array $context + * + * @return null|string + */ + protected function getTransactionName(array $context) + { + if (isset($context['transaction_name'])) { + return $context['transaction_name']; + } + + return $this->transactionName; + } + + /** + * Sets the NewRelic application that should receive this log. + * + * @param string $appName + */ + protected function setNewRelicAppName($appName) + { + newrelic_set_appname($appName); + } + + /** + * Overwrites the name of the current transaction + * + * @param string $transactionName + */ + protected function setNewRelicTransactionName($transactionName) + { + newrelic_name_transaction($transactionName); + } + + /** + * @param string $key + * @param mixed $value + */ + protected function setNewRelicParameter($key, $value) + { + if (null === $value || is_scalar($value)) { + newrelic_add_custom_parameter($key, $value); + } else { + newrelic_add_custom_parameter($key, @json_encode($value)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new NormalizerFormatter(); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php new file mode 100644 index 0000000..4b84588 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Blackhole + * + * Any record it can handle will be thrown away. This can be used + * to put on top of an existing stack to override it temporarily. + * + * @author Jordi Boggiano + */ +class NullHandler extends AbstractHandler +{ + /** + * @param int $level The minimum logging level at which this handler will be triggered + */ + public function __construct($level = Logger::DEBUG) + { + parent::__construct($level, false); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + return true; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php new file mode 100644 index 0000000..1f2076a --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Exception; +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use PhpConsole\Connector; +use PhpConsole\Handler; +use PhpConsole\Helper; + +/** + * Monolog handler for Google Chrome extension "PHP Console" + * + * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely + * + * Usage: + * 1. Install Google Chrome extension https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef + * 2. See overview https://github.com/barbushin/php-console#overview + * 3. Install PHP Console library https://github.com/barbushin/php-console#installation + * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) + * + * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); + * \Monolog\ErrorHandler::register($logger); + * echo $undefinedVar; + * $logger->addDebug('SELECT * FROM users', array('db', 'time' => 0.012)); + * PC::debug($_SERVER); // PHP Console debugger for any type of vars + * + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + */ +class PHPConsoleHandler extends AbstractProcessingHandler +{ + private $options = array( + 'enabled' => true, // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with... + 'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled + 'useOwnErrorsHandler' => false, // bool Enable errors handling + 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling + 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths + 'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s') + 'serverEncoding' => null, // string|null Server internal encoding + 'headersLimit' => null, // int|null Set headers size limit for your web-server + 'password' => null, // string|null Protect PHP Console connection by password + 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed + 'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) + 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings + 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level + 'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number + 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item + 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON + 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug + 'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) + ); + + /** @var Connector */ + private $connector; + + /** + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @param int $level + * @param bool $bubble + * @throws Exception + */ + public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true) + { + if (!class_exists('PhpConsole\Connector')) { + throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + parent::__construct($level, $bubble); + $this->options = $this->initOptions($options); + $this->connector = $this->initConnector($connector); + } + + private function initOptions(array $options) + { + $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); + if ($wrongOptions) { + throw new Exception('Unknown options: ' . implode(', ', $wrongOptions)); + } + + return array_replace($this->options, $options); + } + + private function initConnector(Connector $connector = null) + { + if (!$connector) { + if ($this->options['dataStorage']) { + Connector::setPostponeStorage($this->options['dataStorage']); + } + $connector = Connector::getInstance(); + } + + if ($this->options['registerHelper'] && !Helper::isRegistered()) { + Helper::register(); + } + + if ($this->options['enabled'] && $connector->isActiveClient()) { + if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { + $handler = Handler::getInstance(); + $handler->setHandleErrors($this->options['useOwnErrorsHandler']); + $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); + $handler->start(); + } + if ($this->options['sourcesBasePath']) { + $connector->setSourcesBasePath($this->options['sourcesBasePath']); + } + if ($this->options['serverEncoding']) { + $connector->setServerEncoding($this->options['serverEncoding']); + } + if ($this->options['password']) { + $connector->setPassword($this->options['password']); + } + if ($this->options['enableSslOnlyMode']) { + $connector->enableSslOnlyMode(); + } + if ($this->options['ipMasks']) { + $connector->setAllowedIpMasks($this->options['ipMasks']); + } + if ($this->options['headersLimit']) { + $connector->setHeadersLimit($this->options['headersLimit']); + } + if ($this->options['detectDumpTraceAndSource']) { + $connector->getDebugDispatcher()->detectTraceAndSource = true; + } + $dumper = $connector->getDumper(); + $dumper->levelLimit = $this->options['dumperLevelLimit']; + $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit']; + $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit']; + $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit']; + $dumper->detectCallbacks = $this->options['dumperDetectCallbacks']; + if ($this->options['enableEvalListener']) { + $connector->startEvalRequestsListener(); + } + } + + return $connector; + } + + public function getConnector() + { + return $this->connector; + } + + public function getOptions() + { + return $this->options; + } + + public function handle(array $record) + { + if ($this->options['enabled'] && $this->connector->isActiveClient()) { + return parent::handle($record); + } + + return !$this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + protected function write(array $record) + { + if ($record['level'] < Logger::NOTICE) { + $this->handleDebugRecord($record); + } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) { + $this->handleExceptionRecord($record); + } else { + $this->handleErrorRecord($record); + } + } + + private function handleDebugRecord(array $record) + { + $tags = $this->getRecordTags($record); + $message = $record['message']; + if ($record['context']) { + $message .= ' ' . json_encode($this->connector->getDumper()->dump(array_filter($record['context']))); + } + $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); + } + + private function handleExceptionRecord(array $record) + { + $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']); + } + + private function handleErrorRecord(array $record) + { + $context = $record['context']; + + $this->connector->getErrorsDispatcher()->dispatchError( + isset($context['code']) ? $context['code'] : null, + isset($context['message']) ? $context['message'] : $record['message'], + isset($context['file']) ? $context['file'] : null, + isset($context['line']) ? $context['line'] : null, + $this->options['classesPartialsTraceIgnore'] + ); + } + + private function getRecordTags(array &$record) + { + $tags = null; + if (!empty($record['context'])) { + $context = & $record['context']; + foreach ($this->options['debugTagsKeysInContext'] as $key) { + if (!empty($context[$key])) { + $tags = $context[$key]; + if ($key === 0) { + array_shift($context); + } else { + unset($context[$key]); + } + break; + } + } + } + + return $tags ?: strtolower($record['level_name']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('%message%'); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php new file mode 100644 index 0000000..1ae8584 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Psr\Log\LoggerInterface; + +/** + * Proxies log messages to an existing PSR-3 compliant logger. + * + * @author Michael Moussa + */ +class PsrHandler extends AbstractHandler +{ + /** + * PSR-3 compliant logger + * + * @var LoggerInterface + */ + protected $logger; + + /** + * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->logger = $logger; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + + return false === $this->bubble; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php new file mode 100644 index 0000000..bba7200 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the pushover api to mobile phones + * + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandler extends SocketHandler +{ + private $token; + private $users; + private $title; + private $user; + private $retry; + private $expire; + + private $highPriorityLevel; + private $emergencyLevel; + private $useFormattedMessage = false; + + /** + * All parameters that can be sent to Pushover + * @see https://pushover.net/api + * @var array + */ + private $parameterNames = array( + 'token' => true, + 'user' => true, + 'message' => true, + 'device' => true, + 'title' => true, + 'url' => true, + 'url_title' => true, + 'priority' => true, + 'timestamp' => true, + 'sound' => true, + 'retry' => true, + 'expire' => true, + 'callback' => true, + ); + + /** + * Sounds the api supports by default + * @see https://pushover.net/api#sounds + * @var array + */ + private $sounds = array( + 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', + 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', + 'persistent', 'echo', 'updown', 'none', + ); + + /** + * @param string $token Pushover api token + * @param string|array $users Pushover user id or array of ids the message will be sent to + * @param string $title Title sent to the Pushover API + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $useSSL Whether to connect via SSL. Required when pushing messages to users that are not + * the pushover.net app owner. OpenSSL is required for this option. + * @param int $highPriorityLevel The minimum logging level at which this handler will start + * sending "high priority" requests to the Pushover API + * @param int $emergencyLevel The minimum logging level at which this handler will start + * sending "emergency" requests to the Pushover API + * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user. + * @param int $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds). + */ + public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200) + { + $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->users = (array) $users; + $this->title = $title ?: gethostname(); + $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); + $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); + $this->retry = $retry; + $this->expire = $expire; + } + + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + private function buildContent($record) + { + // Pushover has a limit of 512 characters on title and message combined. + $maxMessageLength = 512 - strlen($this->title); + + $message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message']; + $message = substr($message, 0, $maxMessageLength); + + $timestamp = $record['datetime']->getTimestamp(); + + $dataArray = array( + 'token' => $this->token, + 'user' => $this->user, + 'message' => $message, + 'title' => $this->title, + 'timestamp' => $timestamp, + ); + + if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { + $dataArray['priority'] = 2; + $dataArray['retry'] = $this->retry; + $dataArray['expire'] = $this->expire; + } elseif (isset($record['level']) && $record['level'] >= $this->highPriorityLevel) { + $dataArray['priority'] = 1; + } + + // First determine the available parameters + $context = array_intersect_key($record['context'], $this->parameterNames); + $extra = array_intersect_key($record['extra'], $this->parameterNames); + + // Least important info should be merged with subsequent info + $dataArray = array_merge($extra, $context, $dataArray); + + // Only pass sounds that are supported by the API + if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds)) { + unset($dataArray['sound']); + } + + return http_build_query($dataArray); + } + + private function buildHeader($content) + { + $header = "POST /1/messages.json HTTP/1.1\r\n"; + $header .= "Host: api.pushover.net\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + protected function write(array $record) + { + foreach ($this->users as $user) { + $this->user = $user; + + parent::write($record); + $this->closeSocket(); + } + + $this->user = null; + } + + public function setHighPriorityLevel($value) + { + $this->highPriorityLevel = $value; + } + + public function setEmergencyLevel($value) + { + $this->emergencyLevel = $value; + } + + /** + * Use the formatted message? + * @param bool $value + */ + public function useFormattedMessage($value) + { + $this->useFormattedMessage = (boolean) $value; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php new file mode 100644 index 0000000..d3f9ed8 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php @@ -0,0 +1,231 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Raven_Client; + +/** + * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server + * using raven-php (https://github.com/getsentry/raven-php) + * + * @author Marc Abramowitz + */ +class RavenHandler extends AbstractProcessingHandler +{ + /** + * Translates Monolog log levels to Raven log levels. + */ + private $logLevels = array( + Logger::DEBUG => Raven_Client::DEBUG, + Logger::INFO => Raven_Client::INFO, + Logger::NOTICE => Raven_Client::INFO, + Logger::WARNING => Raven_Client::WARNING, + Logger::ERROR => Raven_Client::ERROR, + Logger::CRITICAL => Raven_Client::FATAL, + Logger::ALERT => Raven_Client::FATAL, + Logger::EMERGENCY => Raven_Client::FATAL, + ); + + /** + * @var string should represent the current version of the calling + * software. Can be any string (git commit, version number) + */ + private $release; + + /** + * @var Raven_Client the client object that sends the message to the server + */ + protected $ravenClient; + + /** + * @var LineFormatter The formatter to use for the logs generated via handleBatch() + */ + protected $batchFormatter; + + /** + * @param Raven_Client $ravenClient + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->ravenClient = $ravenClient; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $level = $this->level; + + // filter records based on their level + $records = array_filter($records, function ($record) use ($level) { + return $record['level'] >= $level; + }); + + if (!$records) { + return; + } + + // the record with the highest severity is the "main" one + $record = array_reduce($records, function ($highest, $record) { + if ($record['level'] > $highest['level']) { + return $record; + } + + return $highest; + }); + + // the other ones are added as a context item + $logs = array(); + foreach ($records as $r) { + $logs[] = $this->processRecord($r); + } + + if ($logs) { + $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); + } + + $this->handle($record); + } + + /** + * Sets the formatter for the logs generated by handleBatch(). + * + * @param FormatterInterface $formatter + */ + public function setBatchFormatter(FormatterInterface $formatter) + { + $this->batchFormatter = $formatter; + } + + /** + * Gets the formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + public function getBatchFormatter() + { + if (!$this->batchFormatter) { + $this->batchFormatter = $this->getDefaultBatchFormatter(); + } + + return $this->batchFormatter; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $previousUserContext = false; + $options = array(); + $options['level'] = $this->logLevels[$record['level']]; + $options['tags'] = array(); + if (!empty($record['extra']['tags'])) { + $options['tags'] = array_merge($options['tags'], $record['extra']['tags']); + unset($record['extra']['tags']); + } + if (!empty($record['context']['tags'])) { + $options['tags'] = array_merge($options['tags'], $record['context']['tags']); + unset($record['context']['tags']); + } + if (!empty($record['context']['fingerprint'])) { + $options['fingerprint'] = $record['context']['fingerprint']; + unset($record['context']['fingerprint']); + } + if (!empty($record['context']['logger'])) { + $options['logger'] = $record['context']['logger']; + unset($record['context']['logger']); + } else { + $options['logger'] = $record['channel']; + } + foreach ($this->getExtraParameters() as $key) { + foreach (array('extra', 'context') as $source) { + if (!empty($record[$source][$key])) { + $options[$key] = $record[$source][$key]; + unset($record[$source][$key]); + } + } + } + if (!empty($record['context'])) { + $options['extra']['context'] = $record['context']; + if (!empty($record['context']['user'])) { + $previousUserContext = $this->ravenClient->context->user; + $this->ravenClient->user_context($record['context']['user']); + unset($options['extra']['context']['user']); + } + } + if (!empty($record['extra'])) { + $options['extra']['extra'] = $record['extra']; + } + + if (!empty($this->release) && !isset($options['release'])) { + $options['release'] = $this->release; + } + + if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { + $options['extra']['message'] = $record['formatted']; + $this->ravenClient->captureException($record['context']['exception'], $options); + } else { + $this->ravenClient->captureMessage($record['formatted'], array(), $options); + } + + if ($previousUserContext !== false) { + $this->ravenClient->user_context($previousUserContext); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[%channel%] %message%'); + } + + /** + * Gets the default formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + protected function getDefaultBatchFormatter() + { + return new LineFormatter(); + } + + /** + * Gets extra parameters supported by Raven that can be found in "extra" and "context" + * + * @return array + */ + protected function getExtraParameters() + { + return array('checksum', 'release'); + } + + /** + * @param string $value + */ + public function setRelease($value) + { + $this->release = $value; + + return $this; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php new file mode 100644 index 0000000..590f996 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.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 Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Logs to a Redis key using rpush + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod"); + * $log->pushHandler($redis); + * + * @author Thomas Tourlourat + */ +class RedisHandler extends AbstractProcessingHandler +{ + private $redisClient; + private $redisKey; + protected $capSize; + + /** + * @param \Predis\Client|\Redis $redis The redis instance + * @param string $key The key name to push records to + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $capSize Number of entries to limit list size to + */ + public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->redisKey = $key; + $this->capSize = $capSize; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + if ($this->capSize) { + $this->writeCapped($record); + } else { + $this->redisClient->rpush($this->redisKey, $record["formatted"]); + } + } + + /** + * Write and cap the collection + * Writes the record to the redis list and caps its + * + * @param array $record associative record array + * @return void + */ + protected function writeCapped(array $record) + { + if ($this->redisClient instanceof \Redis) { + $this->redisClient->multi() + ->rpush($this->redisKey, $record["formatted"]) + ->ltrim($this->redisKey, -$this->capSize, -1) + ->exec(); + } else { + $redisKey = $this->redisKey; + $capSize = $this->capSize; + $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) { + $tx->rpush($redisKey, $record["formatted"]); + $tx->ltrim($redisKey, -$capSize, -1); + }); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php new file mode 100644 index 0000000..0d9de1a --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use RollbarNotifier; +use Exception; +use Monolog\Logger; + +/** + * Sends errors to Rollbar + * + * If the context data contains a `payload` key, that is used as an array + * of payload options to RollbarNotifier's report_message/report_exception methods. + * + * Rollbar's context info will contain the context + extra keys from the log record + * merged, and then on top of that a few keys: + * + * - level (rollbar level name) + * - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) + * - channel + * - datetime (unix timestamp) + * + * @author Paul Statezny + */ +class RollbarHandler extends AbstractProcessingHandler +{ + /** + * Rollbar notifier + * + * @var RollbarNotifier + */ + protected $rollbarNotifier; + + protected $levelMap = array( + Logger::DEBUG => 'debug', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warning', + Logger::ERROR => 'error', + Logger::CRITICAL => 'critical', + Logger::ALERT => 'critical', + Logger::EMERGENCY => 'critical', + ); + + /** + * Records whether any log records have been added since the last flush of the rollbar notifier + * + * @var bool + */ + private $hasRecords = false; + + protected $initialized = false; + + /** + * @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true) + { + $this->rollbarNotifier = $rollbarNotifier; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + $context = $record['context']; + $payload = array(); + if (isset($context['payload'])) { + $payload = $context['payload']; + unset($context['payload']); + } + $context = array_merge($context, $record['extra'], array( + 'level' => $this->levelMap[$record['level']], + 'monolog_level' => $record['level_name'], + 'channel' => $record['channel'], + 'datetime' => $record['datetime']->format('U'), + )); + + if (isset($context['exception']) && $context['exception'] instanceof Exception) { + $exception = $context['exception']; + unset($context['exception']); + + $this->rollbarNotifier->report_exception($exception, $context, $payload); + } else { + $this->rollbarNotifier->report_message( + $record['message'], + $context['level'], + $context, + $payload + ); + } + + $this->hasRecords = true; + } + + public function flush() + { + if ($this->hasRecords) { + $this->rollbarNotifier->flush(); + $this->hasRecords = false; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 0000000..3b60b3d --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores logs to files that are rotated every day and a limited number of files are kept. + * + * This rotation is only intended to be used as a workaround. Using logrotate to + * handle the rotation is strongly encouraged when you can use it. + * + * @author Christophe Coevoet + * @author Jordi Boggiano + */ +class RotatingFileHandler extends StreamHandler +{ + const FILE_PER_DAY = 'Y-m-d'; + const FILE_PER_MONTH = 'Y-m'; + const FILE_PER_YEAR = 'Y'; + + protected $filename; + protected $maxFiles; + protected $mustRotate; + protected $nextRotation; + protected $filenameFormat; + protected $dateFormat; + + /** + * @param string $filename + * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param Boolean $useLocking Try to lock log file before doing any writes + */ + public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + $this->filename = $filename; + $this->maxFiles = (int) $maxFiles; + $this->nextRotation = new \DateTime('tomorrow'); + $this->filenameFormat = '{filename}-{date}'; + $this->dateFormat = 'Y-m-d'; + + parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + public function setFilenameFormat($filenameFormat, $dateFormat) + { + if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + trigger_error( + 'Invalid date format - format must be one of '. + 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. + 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. + 'date formats using slashes, underscores and/or dots instead of dashes.', + E_USER_DEPRECATED + ); + } + if (substr_count($filenameFormat, '{date}') === 0) { + trigger_error( + 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.', + E_USER_DEPRECATED + ); + } + $this->filenameFormat = $filenameFormat; + $this->dateFormat = $dateFormat; + $this->url = $this->getTimedFilename(); + $this->close(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + // on the first record written, if the log is new, we should rotate (once per day) + if (null === $this->mustRotate) { + $this->mustRotate = !file_exists($this->url); + } + + if ($this->nextRotation < $record['datetime']) { + $this->mustRotate = true; + $this->close(); + } + + parent::write($record); + } + + /** + * Rotates the files. + */ + protected function rotate() + { + // update filename + $this->url = $this->getTimedFilename(); + $this->nextRotation = new \DateTime('tomorrow'); + + // skip GC of old logs if files are unlimited + if (0 === $this->maxFiles) { + return; + } + + $logFiles = glob($this->getGlobPattern()); + if ($this->maxFiles >= count($logFiles)) { + // no files to remove + return; + } + + // Sorting the files by name to remove the older ones + usort($logFiles, function ($a, $b) { + return strcmp($b, $a); + }); + + foreach (array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + // suppress errors here as unlink() might fail if two processes + // are cleaning up/rotating at the same time + set_error_handler(function ($errno, $errstr, $errfile, $errline) {}); + unlink($file); + restore_error_handler(); + } + } + + $this->mustRotate = false; + } + + protected function getTimedFilename() + { + $fileInfo = pathinfo($this->filename); + $timedFilename = str_replace( + array('{filename}', '{date}'), + array($fileInfo['filename'], date($this->dateFormat)), + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + + if (!empty($fileInfo['extension'])) { + $timedFilename .= '.'.$fileInfo['extension']; + } + + return $timedFilename; + } + + protected function getGlobPattern() + { + $fileInfo = pathinfo($this->filename); + $glob = str_replace( + array('{filename}', '{date}'), + array($fileInfo['filename'], '*'), + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + if (!empty($fileInfo['extension'])) { + $glob .= '.'.$fileInfo['extension']; + } + + return $glob; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php new file mode 100644 index 0000000..9509ae3 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Sampling handler + * + * A sampled event stream can be useful for logging high frequency events in + * a production environment where you only need an idea of what is happening + * and are not concerned with capturing every occurrence. Since the decision to + * handle or not handle a particular event is determined randomly, the + * resulting sampled log is not guaranteed to contain 1/N of the events that + * occurred in the application, but based on the Law of large numbers, it will + * tend to be close to this ratio with a large number of attempts. + * + * @author Bryan Davis + * @author Kunal Mehta + */ +class SamplingHandler extends AbstractHandler +{ + /** + * @var callable|HandlerInterface $handler + */ + protected $handler; + + /** + * @var int $factor + */ + protected $factor; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). + * @param int $factor Sample factor + */ + public function __construct($handler, $factor) + { + parent::__construct(); + $this->handler = $handler; + $this->factor = $factor; + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + public function isHandling(array $record) + { + return $this->handler->isHandling($record); + } + + public function handle(array $record) + { + if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { + // The same logic as in FingersCrossedHandler + if (!$this->handler instanceof HandlerInterface) { + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->handler->handle($record); + } + + return false === $this->bubble; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/src/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php new file mode 100644 index 0000000..cb0b871 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php @@ -0,0 +1,248 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Slack record utility helping to log to Slack webhooks or API. + * + * @author Greg Kedzierski + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @see https://api.slack.com/docs/message-attachments + */ +class SlackRecord +{ + const COLOR_DANGER = 'danger'; + + const COLOR_WARNING = 'warning'; + + const COLOR_GOOD = 'good'; + + const COLOR_DEFAULT = '#e3e4e6'; + + /** + * Slack channel (encoded ID or name) + * @var string|null + */ + private $channel; + + /** + * Name of a bot + * @var string + */ + private $username; + + /** + * Emoji icon name + * @var string + */ + private $iconEmoji; + + /** + * Whether the message should be added to Slack as attachment (plain text otherwise) + * @var bool + */ + private $useAttachment; + + /** + * Whether the the context/extra messages added to Slack as attachments are in a short style + * @var bool + */ + private $useShortAttachment; + + /** + * Whether the attachment should include context and extra data + * @var bool + */ + private $includeContextAndExtra; + + /** + * @var FormatterInterface + */ + private $formatter; + + /** + * @var LineFormatter + */ + private $lineFormatter; + + public function __construct($channel = null, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeContextAndExtra = false, FormatterInterface $formatter = null) + { + $this->channel = $channel; + $this->username = $username; + $this->iconEmoji = trim($iconEmoji, ':'); + $this->useAttachment = $useAttachment; + $this->useShortAttachment = $useShortAttachment; + $this->includeContextAndExtra = $includeContextAndExtra; + $this->formatter = $formatter; + + if ($this->includeContextAndExtra) { + $this->lineFormatter = new LineFormatter(); + } + } + + public function getSlackData(array $record) + { + $dataArray = array( + 'username' => $this->username, + 'text' => '', + ); + + if ($this->channel) { + $dataArray['channel'] = $this->channel; + } + + if ($this->formatter) { + $message = $this->formatter->format($record); + } else { + $message = $record['message']; + } + + if ($this->useAttachment) { + $attachment = array( + 'fallback' => $message, + 'text' => $message, + 'color' => $this->getAttachmentColor($record['level']), + 'fields' => array(), + ); + + if ($this->useShortAttachment) { + $attachment['title'] = $record['level_name']; + } else { + $attachment['title'] = 'Message'; + $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name'], true); + } + + if ($this->includeContextAndExtra) { + foreach (array('extra', 'context') as $key) { + if (empty($record[$key])) { + continue; + } + + if ($this->useShortAttachment) { + $attachment['fields'][] = $this->generateAttachmentField( + ucfirst($key), + $this->stringify($record[$key]), + true + ); + } else { + // Add all extra fields as individual fields in attachment + $attachment['fields'] = array_merge( + $attachment['fields'], + $this->generateAttachmentFields($record[$key]) + ); + } + } + } + + $dataArray['attachments'] = array($attachment); + } else { + $dataArray['text'] = $message; + } + + if ($this->iconEmoji) { + $dataArray['icon_emoji'] = ":{$this->iconEmoji}:"; + } + + return $dataArray; + } + + /** + * Returned a Slack message attachment color associated with + * provided level. + * + * @param int $level + * @return string + */ + public function getAttachmentColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return self::COLOR_DANGER; + case $level >= Logger::WARNING: + return self::COLOR_WARNING; + case $level >= Logger::INFO: + return self::COLOR_GOOD; + default: + return self::COLOR_DEFAULT; + } + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param array $fields + * @return string|null + */ + public function stringify($fields) + { + if (!$this->lineFormatter) { + return null; + } + + $string = ''; + foreach ($fields as $var => $val) { + $string .= $var.': '.$this->lineFormatter->stringify($val)." | "; + } + + $string = rtrim($string, " |"); + + return $string; + } + + /** + * Sets the formatter + * + * @param FormatterInterface $formatter + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * Generates attachment field + * + * @param string $title + * @param string|array $value + * @param bool $short + * @return array + */ + private function generateAttachmentField($title, $value, $short) + { + return array( + 'title' => $title, + 'value' => is_array($value) ? $this->lineFormatter->stringify($value) : $value, + 'short' => $short + ); + } + + /** + * Generates a collection of attachment fields from array + * + * @param array $data + * @return array + */ + private function generateAttachmentFields(array $data) + { + $fields = array(); + foreach ($data as $key => $value) { + $fields[] = $this->generateAttachmentField($key, $value, false); + } + + return $fields; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php new file mode 100644 index 0000000..c2cca0c --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack API + * + * @author Greg Kedzierski + * @see https://api.slack.com/ + */ +class SlackHandler extends SocketHandler +{ + /** + * Slack API token + * @var string + */ + private $token; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $token Slack API token + * @param string $channel Slack channel (encoded ID or name) + * @param string $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @throws MissingExtensionException If no OpenSSL PHP extension configured + */ + public function __construct($token, $channel, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); + } + + parent::__construct('ssl://slack.com:443', $level, $bubble); + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $this->formatter + ); + + $this->token = $token; + } + + public function getSlackRecord() + { + return $this->slackRecord; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = $this->prepareContentData($record); + + return http_build_query($dataArray); + } + + /** + * Prepares content data + * + * @param array $record + * @return array + */ + protected function prepareContentData($record) + { + $dataArray = $this->slackRecord->getSlackData($record); + $dataArray['token'] = $this->token; + + if (!empty($dataArray['attachments'])) { + $dataArray['attachments'] = json_encode($dataArray['attachments']); + } + + return $dataArray; + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; + $header .= "Host: slack.com\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + $res = $this->getResource(); + if (is_resource($res)) { + @fread($res, 2048); + } + $this->closeSocket(); + } + + /** + * Returned a Slack message attachment color associated with + * provided level. + * + * @param int $level + * @return string + * @deprecated Use underlying SlackRecord instead + */ + protected function getAttachmentColor($level) + { + trigger_error( + 'SlackHandler::getAttachmentColor() is deprecated. Use underlying SlackRecord instead.', + E_USER_DEPRECATED + ); + + return $this->slackRecord->getAttachmentColor($level); + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param array $fields + * @return string + * @deprecated Use underlying SlackRecord instead + */ + protected function stringify($fields) + { + trigger_error( + 'SlackHandler::stringify() is deprecated. Use underlying SlackRecord instead.', + E_USER_DEPRECATED + ); + + return $this->slackRecord->stringify($fields); + } + + public function setFormatter(FormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter() + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php new file mode 100644 index 0000000..a2df0f1 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack Webhooks + * + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + */ +class SlackWebhookHandler extends AbstractProcessingHandler +{ + /** + * Slack Webhook token + * @var string + */ + private $webhookUrl; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $webhookUrl Slack Webhook URL + * @param string|null $channel Slack channel (encoded ID or name) + * @param string $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($webhookUrl, $channel = null, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeContextAndExtra = false, $level = Logger::CRITICAL, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->webhookUrl = $webhookUrl; + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $this->formatter + ); + } + + public function getSlackRecord() + { + return $this->slackRecord; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + $postData = $this->slackRecord->getSlackData($record); + $postString = json_encode($postData); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $this->webhookUrl); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + if (defined('CURLOPT_SAFE_UPLOAD')) { + curl_setopt($ch, CURLOPT_SAFE_UPLOAD, true); + } + curl_setopt($ch, CURLOPT_POSTFIELDS, array('payload' => $postString)); + + Curl\Util::execute($ch); + } + + public function setFormatter(FormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter() + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php new file mode 100644 index 0000000..baead52 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through Slack's Slackbot + * + * @author Haralan Dobrev + * @see https://slack.com/apps/A0F81R8ET-slackbot + */ +class SlackbotHandler extends AbstractProcessingHandler +{ + /** + * The slug of the Slack team + * @var string + */ + private $slackTeam; + + /** + * Slackbot token + * @var string + */ + private $token; + + /** + * Slack channel name + * @var string + */ + private $channel; + + /** + * @param string $slackTeam Slack team slug + * @param string $token Slackbot token + * @param string $channel Slack channel (encoded ID or name) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($slackTeam, $token, $channel, $level = Logger::CRITICAL, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->slackTeam = $slackTeam; + $this->token = $token; + $this->channel = $channel; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + $slackbotUrl = sprintf( + 'https://%s.slack.com/services/hooks/slackbot?token=%s&channel=%s', + $this->slackTeam, + $this->token, + $this->channel + ); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $slackbotUrl); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $record['message']); + + Curl\Util::execute($ch); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php new file mode 100644 index 0000000..7a61bf4 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -0,0 +1,346 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any socket - uses fsockopen() or pfsockopen(). + * + * @author Pablo de Leon Belloc + * @see http://php.net/manual/en/function.fsockopen.php + */ +class SocketHandler extends AbstractProcessingHandler +{ + private $connectionString; + private $connectionTimeout; + private $resource; + private $timeout = 0; + private $writingTimeout = 10; + private $lastSentBytes = null; + private $persistent = false; + private $errno; + private $errstr; + private $lastWritingAt; + + /** + * @param string $connectionString Socket connection string + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->connectionString = $connectionString; + $this->connectionTimeout = (float) ini_get('default_socket_timeout'); + } + + /** + * Connect (if necessary) and write to the socket + * + * @param array $record + * + * @throws \UnexpectedValueException + * @throws \RuntimeException + */ + protected function write(array $record) + { + $this->connectIfNotConnected(); + $data = $this->generateDataStream($record); + $this->writeToSocket($data); + } + + /** + * We will not close a PersistentSocket instance so it can be reused in other requests. + */ + public function close() + { + if (!$this->isPersistent()) { + $this->closeSocket(); + } + } + + /** + * Close socket, if open + */ + public function closeSocket() + { + if (is_resource($this->resource)) { + fclose($this->resource); + $this->resource = null; + } + } + + /** + * Set socket connection to nbe persistent. It only has effect before the connection is initiated. + * + * @param bool $persistent + */ + public function setPersistent($persistent) + { + $this->persistent = (boolean) $persistent; + } + + /** + * Set connection timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.fsockopen.php + */ + public function setConnectionTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->connectionTimeout = (float) $seconds; + } + + /** + * Set write timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + public function setTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->timeout = (float) $seconds; + } + + /** + * Set writing timeout. Only has effect during connection in the writing cycle. + * + * @param float $seconds 0 for no timeout + */ + public function setWritingTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->writingTimeout = (float) $seconds; + } + + /** + * Get current connection string + * + * @return string + */ + public function getConnectionString() + { + return $this->connectionString; + } + + /** + * Get persistent setting + * + * @return bool + */ + public function isPersistent() + { + return $this->persistent; + } + + /** + * Get current connection timeout setting + * + * @return float + */ + public function getConnectionTimeout() + { + return $this->connectionTimeout; + } + + /** + * Get current in-transfer timeout + * + * @return float + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Get current local writing timeout + * + * @return float + */ + public function getWritingTimeout() + { + return $this->writingTimeout; + } + + /** + * Check to see if the socket is currently available. + * + * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. + * + * @return bool + */ + public function isConnected() + { + return is_resource($this->resource) + && !feof($this->resource); // on TCP - other party can close connection. + } + + /** + * Wrapper to allow mocking + */ + protected function pfsockopen() + { + return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + */ + protected function fsockopen() + { + return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + protected function streamSetTimeout() + { + $seconds = floor($this->timeout); + $microseconds = round(($this->timeout - $seconds) * 1e6); + + return stream_set_timeout($this->resource, $seconds, $microseconds); + } + + /** + * Wrapper to allow mocking + */ + protected function fwrite($data) + { + return @fwrite($this->resource, $data); + } + + /** + * Wrapper to allow mocking + */ + protected function streamGetMetadata() + { + return stream_get_meta_data($this->resource); + } + + private function validateTimeout($value) + { + $ok = filter_var($value, FILTER_VALIDATE_FLOAT); + if ($ok === false || $value < 0) { + throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); + } + } + + private function connectIfNotConnected() + { + if ($this->isConnected()) { + return; + } + $this->connect(); + } + + protected function generateDataStream($record) + { + return (string) $record['formatted']; + } + + /** + * @return resource|null + */ + protected function getResource() + { + return $this->resource; + } + + private function connect() + { + $this->createSocketResource(); + $this->setSocketTimeout(); + } + + private function createSocketResource() + { + if ($this->isPersistent()) { + $resource = $this->pfsockopen(); + } else { + $resource = $this->fsockopen(); + } + if (!$resource) { + throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); + } + $this->resource = $resource; + } + + private function setSocketTimeout() + { + if (!$this->streamSetTimeout()) { + throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); + } + } + + private function writeToSocket($data) + { + $length = strlen($data); + $sent = 0; + $this->lastSentBytes = $sent; + while ($this->isConnected() && $sent < $length) { + if (0 == $sent) { + $chunk = $this->fwrite($data); + } else { + $chunk = $this->fwrite(substr($data, $sent)); + } + if ($chunk === false) { + throw new \RuntimeException("Could not write to socket"); + } + $sent += $chunk; + $socketInfo = $this->streamGetMetadata(); + if ($socketInfo['timed_out']) { + throw new \RuntimeException("Write timed-out"); + } + + if ($this->writingIsTimedOut($sent)) { + throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)"); + } + } + if (!$this->isConnected() && $sent < $length) { + throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); + } + } + + private function writingIsTimedOut($sent) + { + $writingTimeout = (int) floor($this->writingTimeout); + if (0 === $writingTimeout) { + return false; + } + + if ($sent !== $this->lastSentBytes) { + $this->lastWritingAt = time(); + $this->lastSentBytes = $sent; + + return false; + } else { + usleep(100); + } + + if ((time() - $this->lastWritingAt) >= $writingTimeout) { + $this->closeSocket(); + + return true; + } + + return false; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php new file mode 100644 index 0000000..09a1573 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + */ +class StreamHandler extends AbstractProcessingHandler +{ + protected $stream; + protected $url; + private $errorMessage; + protected $filePermission; + protected $useLocking; + private $dirCreated; + + /** + * @param resource|string $stream + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param Boolean $useLocking Try to lock log file before doing any writes + * + * @throws \Exception If a missing directory is not buildable + * @throws \InvalidArgumentException If stream is not a resource or string + */ + public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + parent::__construct($level, $bubble); + if (is_resource($stream)) { + $this->stream = $stream; + } elseif (is_string($stream)) { + $this->url = $stream; + } else { + throw new \InvalidArgumentException('A stream must either be a resource or a string.'); + } + + $this->filePermission = $filePermission; + $this->useLocking = $useLocking; + } + + /** + * {@inheritdoc} + */ + public function close() + { + if ($this->url && is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + } + + /** + * Return the currently active stream if it is open + * + * @return resource|null + */ + public function getStream() + { + return $this->stream; + } + + /** + * Return the stream URL if it was configured with a URL and not an active resource + * + * @return string|null + */ + public function getUrl() + { + return $this->url; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!is_resource($this->stream)) { + if (null === $this->url || '' === $this->url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + } + $this->createDir(); + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $this->stream = fopen($this->url, 'a'); + if ($this->filePermission !== null) { + @chmod($this->url, $this->filePermission); + } + restore_error_handler(); + if (!is_resource($this->stream)) { + $this->stream = null; + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$this->errorMessage, $this->url)); + } + } + + if ($this->useLocking) { + // ignoring errors here, there's not much we can do about them + flock($this->stream, LOCK_EX); + } + + $this->streamWrite($this->stream, $record); + + if ($this->useLocking) { + flock($this->stream, LOCK_UN); + } + } + + /** + * Write to stream + * @param resource $stream + * @param array $record + */ + protected function streamWrite($stream, array $record) + { + fwrite($stream, (string) $record['formatted']); + } + + private function customErrorHandler($code, $msg) + { + $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + } + + /** + * @param string $stream + * + * @return null|string + */ + private function getDirFromStream($stream) + { + $pos = strpos($stream, '://'); + if ($pos === false) { + return dirname($stream); + } + + if ('file://' === substr($stream, 0, 7)) { + return dirname(substr($stream, 7)); + } + + return; + } + + private function createDir() + { + // Do not try to create dir if it has already been tried. + if ($this->dirCreated) { + return; + } + + $dir = $this->getDirFromStream($this->url); + if (null !== $dir && !is_dir($dir)) { + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $status = mkdir($dir, 0777, true); + restore_error_handler(); + if (false === $status) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir)); + } + } + $this->dirCreated = true; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php new file mode 100644 index 0000000..aba1396 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * SwiftMailerHandler uses Swift_Mailer to send the emails + * + * @author Gyula Sallai + */ +class SwiftMailerHandler extends MailHandler +{ + protected $mailer; + private $messageTemplate; + + /** + * @param \Swift_Mailer $mailer The mailer to use + * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->messageTemplate = $message; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Creates instance of Swift_Message to be sent + * + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * @return \Swift_Message + */ + protected function buildMessage($content, array $records) + { + $message = null; + if ($this->messageTemplate instanceof \Swift_Message) { + $message = clone $this->messageTemplate; + $message->generateId(); + } elseif (is_callable($this->messageTemplate)) { + $message = call_user_func($this->messageTemplate, $content, $records); + } + + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it'); + } + + if ($records) { + $subjectFormatter = new LineFormatter($message->getSubject()); + $message->setSubject($subjectFormatter->format($this->getHighestRecord($records))); + } + + $message->setBody($content); + $message->setDate(time()); + + return $message; + } + + /** + * BC getter, to be removed in 2.0 + */ + public function __get($name) + { + if ($name === 'message') { + trigger_error('SwiftMailerHandler->message is deprecated, use ->buildMessage() instead to retrieve the message', E_USER_DEPRECATED); + + return $this->buildMessage(null, array()); + } + + throw new \InvalidArgumentException('Invalid property '.$name); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php new file mode 100644 index 0000000..376bc3b --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Logs to syslog service. + * + * usage example: + * + * $log = new Logger('application'); + * $syslog = new SyslogHandler('myfacility', 'local6'); + * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + * $syslog->setFormatter($formatter); + * $log->pushHandler($syslog); + * + * @author Sven Paulus + */ +class SyslogHandler extends AbstractSyslogHandler +{ + protected $ident; + protected $logopts; + + /** + * @param string $ident + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID + */ + public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID) + { + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->logopts = $logopts; + } + + /** + * {@inheritdoc} + */ + public function close() + { + closelog(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!openlog($this->ident, $this->logopts, $this->facility)) { + throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"'); + } + syslog($this->logLevels[$record['level']], (string) $record['formatted']); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/src/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php new file mode 100644 index 0000000..3bff085 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\SyslogUdp; + +class UdpSocket +{ + const DATAGRAM_MAX_LENGTH = 65023; + + protected $ip; + protected $port; + protected $socket; + + public function __construct($ip, $port = 514) + { + $this->ip = $ip; + $this->port = $port; + $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + } + + public function write($line, $header = "") + { + $this->send($this->assembleMessage($line, $header)); + } + + public function close() + { + if (is_resource($this->socket)) { + socket_close($this->socket); + $this->socket = null; + } + } + + protected function send($chunk) + { + if (!is_resource($this->socket)) { + throw new \LogicException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore'); + } + socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); + } + + protected function assembleMessage($line, $header) + { + $chunkSize = self::DATAGRAM_MAX_LENGTH - strlen($header); + + return $header . substr($line, 0, $chunkSize); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php new file mode 100644 index 0000000..74d946a --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Handler\SyslogUdp\UdpSocket; + +/** + * A Handler for logging to a remote syslogd server. + * + * @author Jesper Skovgaard Nielsen + */ +class SyslogUdpHandler extends AbstractSyslogHandler +{ + protected $socket; + + /** + * @param string $host + * @param int $port + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($facility, $level, $bubble); + + $this->socket = new UdpSocket($host, $port ?: 514); + } + + protected function write(array $record) + { + $lines = $this->splitMessageIntoLines($record['formatted']); + + $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']]); + + foreach ($lines as $line) { + $this->socket->write($line, $header); + } + } + + public function close() + { + $this->socket->close(); + } + + private function splitMessageIntoLines($message) + { + if (is_array($message)) { + $message = implode("\n", $message); + } + + return preg_split('/$\R?^/m', $message, -1, PREG_SPLIT_NO_EMPTY); + } + + /** + * Make common syslog header (see rfc5424) + */ + protected function makeCommonSyslogHeader($severity) + { + $priority = $severity + $this->facility; + + return "<$priority>1 "; + } + + /** + * Inject your own socket, mainly used for testing + */ + public function setSocket($socket) + { + $this->socket = $socket; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php new file mode 100644 index 0000000..e39cfc6 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.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 Monolog\Handler; + +/** + * Used for testing purposes. + * + * It records all records and gives you access to them for verification. + * + * @author Jordi Boggiano + * + * @method bool hasEmergency($record) + * @method bool hasAlert($record) + * @method bool hasCritical($record) + * @method bool hasError($record) + * @method bool hasWarning($record) + * @method bool hasNotice($record) + * @method bool hasInfo($record) + * @method bool hasDebug($record) + * + * @method bool hasEmergencyRecords() + * @method bool hasAlertRecords() + * @method bool hasCriticalRecords() + * @method bool hasErrorRecords() + * @method bool hasWarningRecords() + * @method bool hasNoticeRecords() + * @method bool hasInfoRecords() + * @method bool hasDebugRecords() + * + * @method bool hasEmergencyThatContains($message) + * @method bool hasAlertThatContains($message) + * @method bool hasCriticalThatContains($message) + * @method bool hasErrorThatContains($message) + * @method bool hasWarningThatContains($message) + * @method bool hasNoticeThatContains($message) + * @method bool hasInfoThatContains($message) + * @method bool hasDebugThatContains($message) + * + * @method bool hasEmergencyThatMatches($message) + * @method bool hasAlertThatMatches($message) + * @method bool hasCriticalThatMatches($message) + * @method bool hasErrorThatMatches($message) + * @method bool hasWarningThatMatches($message) + * @method bool hasNoticeThatMatches($message) + * @method bool hasInfoThatMatches($message) + * @method bool hasDebugThatMatches($message) + * + * @method bool hasEmergencyThatPasses($message) + * @method bool hasAlertThatPasses($message) + * @method bool hasCriticalThatPasses($message) + * @method bool hasErrorThatPasses($message) + * @method bool hasWarningThatPasses($message) + * @method bool hasNoticeThatPasses($message) + * @method bool hasInfoThatPasses($message) + * @method bool hasDebugThatPasses($message) + */ +class TestHandler extends AbstractProcessingHandler +{ + protected $records = array(); + protected $recordsByLevel = array(); + + public function getRecords() + { + return $this->records; + } + + public function clear() + { + $this->records = array(); + $this->recordsByLevel = array(); + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_array($record)) { + $record = $record['message']; + } + + return $this->hasRecordThatPasses(function ($rec) use ($record) { + return $rec['message'] === $record; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses($predicate, $level) + { + if (!is_callable($predicate)) { + throw new \InvalidArgumentException("Expected a callable for hasRecordThatSucceeds"); + } + + if (!isset($this->recordsByLevel[$level])) { + return false; + } + + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = constant('Monolog\Logger::' . strtoupper($matches[2])); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + + return call_user_func_array(array($this, $genericMethod), $args); + } + } + + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php new file mode 100644 index 0000000..2732ba3 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.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 Monolog\Handler; + +/** + * Forwards records to multiple handlers suppressing failures of each handler + * and continuing through to give every handler a chance to succeed. + * + * @author Craig D'Amelio + */ +class WhatFailureGroupHandler extends GroupHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + } catch (\Exception $e) { + // What failure? + } catch (\Throwable $e) { + // What failure? + } + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + } catch (\Exception $e) { + // What failure? + } catch (\Throwable $e) { + // What failure? + } + } + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/src/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php new file mode 100644 index 0000000..f22cf21 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.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 Monolog\Handler; + +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to Zend Monitor + * + * @author Christian Bergau + */ +class ZendMonitorHandler extends AbstractProcessingHandler +{ + /** + * Monolog level / ZendMonitor Custom Event priority map + * + * @var array + */ + protected $levelMap = array( + Logger::DEBUG => 1, + Logger::INFO => 2, + Logger::NOTICE => 3, + Logger::WARNING => 4, + Logger::ERROR => 5, + Logger::CRITICAL => 6, + Logger::ALERT => 7, + Logger::EMERGENCY => 0, + ); + + /** + * Construct + * + * @param int $level + * @param bool $bubble + * @throws MissingExtensionException + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + if (!function_exists('zend_monitor_custom_event')) { + throw new MissingExtensionException('You must have Zend Server installed in order to use this handler'); + } + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->writeZendMonitorCustomEvent( + $this->levelMap[$record['level']], + $record['message'], + $record['formatted'] + ); + } + + /** + * Write a record to Zend Monitor + * + * @param int $level + * @param string $message + * @param array $formatted + */ + protected function writeZendMonitorCustomEvent($level, $message, $formatted) + { + zend_monitor_custom_event($level, $message, $formatted); + } + + /** + * {@inheritdoc} + */ + public function getDefaultFormatter() + { + return new NormalizerFormatter(); + } + + /** + * Get the level map + * + * @return array + */ + public function getLevelMap() + { + return $this->levelMap; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Logger.php b/src/vendor/monolog/monolog/src/Monolog/Logger.php new file mode 100644 index 0000000..49d00af --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Logger.php @@ -0,0 +1,700 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\HandlerInterface; +use Monolog\Handler\StreamHandler; +use Psr\Log\LoggerInterface; +use Psr\Log\InvalidArgumentException; + +/** + * Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + */ +class Logger implements LoggerInterface +{ + /** + * Detailed debug information + */ + const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + const INFO = 200; + + /** + * Uncommon events + */ + const NOTICE = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + const WARNING = 300; + + /** + * Runtime errors + */ + const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + const ALERT = 550; + + /** + * Urgent alert. + */ + const EMERGENCY = 600; + + /** + * Monolog API version + * + * This is only bumped when API breaks are done and should + * follow the major version of the library + * + * @var int + */ + const API = 1; + + /** + * Logging levels from syslog protocol defined in RFC 5424 + * + * @var array $levels Logging levels + */ + protected static $levels = array( + self::DEBUG => 'DEBUG', + self::INFO => 'INFO', + self::NOTICE => 'NOTICE', + self::WARNING => 'WARNING', + self::ERROR => 'ERROR', + self::CRITICAL => 'CRITICAL', + self::ALERT => 'ALERT', + self::EMERGENCY => 'EMERGENCY', + ); + + /** + * @var \DateTimeZone + */ + protected static $timezone; + + /** + * @var string + */ + protected $name; + + /** + * The handler stack + * + * @var HandlerInterface[] + */ + protected $handlers; + + /** + * Processors that will process all log records + * + * To process records of a single handler instead, add the processor on that specific handler + * + * @var callable[] + */ + protected $processors; + + /** + * @var bool + */ + protected $microsecondTimestamps = true; + + /** + * @param string $name The logging channel + * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param callable[] $processors Optional array of processors + */ + public function __construct($name, array $handlers = array(), array $processors = array()) + { + $this->name = $name; + $this->handlers = $handlers; + $this->processors = $processors; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Return a new cloned instance with the name changed + * + * @return static + */ + public function withName($name) + { + $new = clone $this; + $new->name = $name; + + return $new; + } + + /** + * Pushes a handler on to the stack. + * + * @param HandlerInterface $handler + * @return $this + */ + public function pushHandler(HandlerInterface $handler) + { + array_unshift($this->handlers, $handler); + + return $this; + } + + /** + * Pops a handler from the stack + * + * @return HandlerInterface + */ + public function popHandler() + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + + return array_shift($this->handlers); + } + + /** + * Set handlers, replacing all existing ones. + * + * If a map is passed, keys will be ignored. + * + * @param HandlerInterface[] $handlers + * @return $this + */ + public function setHandlers(array $handlers) + { + $this->handlers = array(); + foreach (array_reverse($handlers) as $handler) { + $this->pushHandler($handler); + } + + return $this; + } + + /** + * @return HandlerInterface[] + */ + public function getHandlers() + { + return $this->handlers; + } + + /** + * Adds a processor on to the stack. + * + * @param callable $callback + * @return $this + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * @return callable[] + */ + public function getProcessors() + { + return $this->processors; + } + + /** + * Control the use of microsecond resolution timestamps in the 'datetime' + * member of new records. + * + * Generating microsecond resolution timestamps by calling + * microtime(true), formatting the result via sprintf() and then parsing + * the resulting string via \DateTime::createFromFormat() can incur + * a measurable runtime overhead vs simple usage of DateTime to capture + * a second resolution timestamp in systems which generate a large number + * of log events. + * + * @param bool $micro True to use microtime() to create timestamps + */ + public function useMicrosecondTimestamps($micro) + { + $this->microsecondTimestamps = (bool) $micro; + } + + /** + * Adds a log record. + * + * @param int $level The logging level + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addRecord($level, $message, array $context = array()) + { + if (!$this->handlers) { + $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + } + + $levelName = static::getLevelName($level); + + // check if any handler will handle this message so we can return early and save cycles + $handlerKey = null; + reset($this->handlers); + while ($handler = current($this->handlers)) { + if ($handler->isHandling(array('level' => $level))) { + $handlerKey = key($this->handlers); + break; + } + + next($this->handlers); + } + + if (null === $handlerKey) { + return false; + } + + if (!static::$timezone) { + static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); + } + + // php7.1+ always has microseconds enabled, so we do not need this hack + if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) { + $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone); + } else { + $ts = new \DateTime(null, static::$timezone); + } + $ts->setTimezone(static::$timezone); + + $record = array( + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => $levelName, + 'channel' => $this->name, + 'datetime' => $ts, + 'extra' => array(), + ); + + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + + while ($handler = current($this->handlers)) { + if (true === $handler->handle($record)) { + break; + } + + next($this->handlers); + } + + return true; + } + + /** + * Adds a log record at the DEBUG level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addDebug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addInfo($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addNotice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addWarning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addError($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addCritical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addAlert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addEmergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Gets all supported logging levels. + * + * @return array Assoc array with human-readable level names => level codes. + */ + public static function getLevels() + { + return array_flip(static::$levels); + } + + /** + * Gets the name of the logging level. + * + * @param int $level + * @return string + */ + public static function getLevelName($level) + { + if (!isset(static::$levels[$level])) { + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); + } + + return static::$levels[$level]; + } + + /** + * Converts PSR-3 levels to Monolog ones if necessary + * + * @param string|int Level number (monolog) or name (PSR-3) + * @return int + */ + public static function toMonologLevel($level) + { + if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) { + return constant(__CLASS__.'::'.strtoupper($level)); + } + + return $level; + } + + /** + * Checks whether the Logger has a handler that listens on the given level + * + * @param int $level + * @return Boolean + */ + public function isHandling($level) + { + $record = array( + 'level' => $level, + ); + + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * Adds a log record at an arbitrary level. + * + * This method allows for compatibility with common interfaces. + * + * @param mixed $level The log level + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function log($level, $message, array $context = array()) + { + $level = static::toMonologLevel($level); + + return $this->addRecord($level, $message, $context); + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function debug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function info($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function notice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function warn($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function warning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function err($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function error($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function crit($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function critical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function alert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function emerg($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function emergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Set the timezone to be used for the timestamp of log records. + * + * This is stored globally for all Logger instances + * + * @param \DateTimeZone $tz Timezone object + */ + public static function setTimezone(\DateTimeZone $tz) + { + self::$timezone = $tz; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/src/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php new file mode 100644 index 0000000..1899400 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects Git branch and Git commit SHA in all records + * + * @author Nick Otter + * @author Jordi Boggiano + */ +class GitProcessor +{ + private $level; + private static $cache; + + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['git'] = self::getGitInfo(); + + return $record; + } + + private static function getGitInfo() + { + if (self::$cache) { + return self::$cache; + } + + $branches = `git branch -v --no-abbrev`; + if (preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { + return self::$cache = array( + 'branch' => $matches[1], + 'commit' => $matches[2], + ); + } + + return self::$cache = array(); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/src/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 0000000..2691630 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + */ +class IntrospectionProcessor +{ + private $level; + + private $skipClassesPartials; + + private $skipStackFramesCount; + + private $skipFunctions = array( + 'call_user_func', + 'call_user_func_array', + ); + + public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0) + { + $this->level = Logger::toMonologLevel($level); + $this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials); + $this->skipStackFramesCount = $skipStackFramesCount; + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + + while ($this->isTraceClassOrSkippedFunction($trace, $i)) { + if (isset($trace[$i]['class'])) { + foreach ($this->skipClassesPartials as $part) { + if (strpos($trace[$i]['class'], $part) !== false) { + $i++; + continue 2; + } + } + } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) { + $i++; + continue; + } + + break; + } + + $i += $this->skipStackFramesCount; + + // we should have the call source now + $record['extra'] = array_merge( + $record['extra'], + array( + 'file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, + 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, + 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, + ) + ); + + return $record; + } + + private function isTraceClassOrSkippedFunction(array $trace, $index) + { + if (!isset($trace[$index])) { + return false; + } + + return isset($trace[$index]['class']) || in_array($trace[$index]['function'], $this->skipFunctions); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/src/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php new file mode 100644 index 0000000..0543e92 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_peak_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryPeakUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_peak_usage($this->realUsage); + $formatted = $this->formatBytes($bytes); + + $record['extra']['memory_peak_usage'] = $formatted; + + return $record; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/src/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php new file mode 100644 index 0000000..85f9dc5 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Some methods that are common for all memory processors + * + * @author Rob Jensen + */ +abstract class MemoryProcessor +{ + /** + * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported. + */ + protected $realUsage; + + /** + * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + protected $useFormatting; + + /** + * @param bool $realUsage Set this to true to get the real size of memory allocated from system. + * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + public function __construct($realUsage = true, $useFormatting = true) + { + $this->realUsage = (boolean) $realUsage; + $this->useFormatting = (boolean) $useFormatting; + } + + /** + * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is + * + * @param int $bytes + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as is + */ + protected function formatBytes($bytes) + { + $bytes = (int) $bytes; + + if (!$this->useFormatting) { + return $bytes; + } + + if ($bytes > 1024 * 1024) { + return round($bytes / 1024 / 1024, 2).' MB'; + } elseif ($bytes > 1024) { + return round($bytes / 1024, 2).' KB'; + } + + return $bytes . ' B'; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/src/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php new file mode 100644 index 0000000..2783d65 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_usage($this->realUsage); + $formatted = $this->formatBytes($bytes); + + $record['extra']['memory_usage'] = $formatted; + + return $record; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/src/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php new file mode 100644 index 0000000..7c07a7e --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects Hg branch and Hg revision number in all records + * + * @author Jonathan A. Schweder + */ +class MercurialProcessor +{ + private $level; + private static $cache; + + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['hg'] = self::getMercurialInfo(); + + return $record; + } + + private static function getMercurialInfo() + { + if (self::$cache) { + return self::$cache; + } + + $result = explode(' ', trim(`hg id -nb`)); + if (count($result) >= 3) { + return self::$cache = array( + 'branch' => $result[1], + 'revision' => $result[2], + ); + } + + return self::$cache = array(); + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/src/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php new file mode 100644 index 0000000..9d3f559 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds value of getmypid into records + * + * @author Andreas Hörnicke + */ +class ProcessIdProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $record['extra']['process_id'] = getmypid(); + + return $record; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/src/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php new file mode 100644 index 0000000..c2686ce --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Processes a record's message according to PSR-3 rules + * + * It replaces {foo} with the value from $context['foo'] + * + * @author Jordi Boggiano + */ +class PsrLogMessageProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + if (false === strpos($record['message'], '{')) { + return $record; + } + + $replacements = array(); + foreach ($record['context'] as $key => $val) { + if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { + $replacements['{'.$key.'}'] = $val; + } elseif (is_object($val)) { + $replacements['{'.$key.'}'] = '[object '.get_class($val).']'; + } else { + $replacements['{'.$key.'}'] = '['.gettype($val).']'; + } + } + + $record['message'] = strtr($record['message'], $replacements); + + return $record; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/src/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php new file mode 100644 index 0000000..7e2df2a --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds a tags array into record + * + * @author Martijn Riemers + */ +class TagProcessor +{ + private $tags; + + public function __construct(array $tags = array()) + { + $this->setTags($tags); + } + + public function addTags(array $tags = array()) + { + $this->tags = array_merge($this->tags, $tags); + } + + public function setTags(array $tags = array()) + { + $this->tags = $tags; + } + + public function __invoke(array $record) + { + $record['extra']['tags'] = $this->tags; + + return $record; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/src/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php new file mode 100644 index 0000000..812707c --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.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 Monolog\Processor; + +/** + * Adds a unique identifier into records + * + * @author Simon Mönch + */ +class UidProcessor +{ + private $uid; + + public function __construct($length = 7) + { + if (!is_int($length) || $length > 32 || $length < 1) { + throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); + } + + $this->uid = substr(hash('md5', uniqid('', true)), 0, $length); + } + + public function __invoke(array $record) + { + $record['extra']['uid'] = $this->uid; + + return $record; + } + + /** + * @return string + */ + public function getUid() + { + return $this->uid; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/src/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php new file mode 100644 index 0000000..ea1d897 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects url/method and remote IP of the current web request in all records + * + * @author Jordi Boggiano + */ +class WebProcessor +{ + /** + * @var array|\ArrayAccess + */ + protected $serverData; + + /** + * Default fields + * + * Array is structured as [key in record.extra => key in $serverData] + * + * @var array + */ + protected $extraFields = array( + 'url' => 'REQUEST_URI', + 'ip' => 'REMOTE_ADDR', + 'http_method' => 'REQUEST_METHOD', + 'server' => 'SERVER_NAME', + 'referrer' => 'HTTP_REFERER', + ); + + /** + * @param array|\ArrayAccess $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data + * @param array|null $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer + */ + public function __construct($serverData = null, array $extraFields = null) + { + if (null === $serverData) { + $this->serverData = &$_SERVER; + } elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) { + $this->serverData = $serverData; + } else { + throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); + } + + if (null !== $extraFields) { + if (isset($extraFields[0])) { + foreach (array_keys($this->extraFields) as $fieldName) { + if (!in_array($fieldName, $extraFields)) { + unset($this->extraFields[$fieldName]); + } + } + } else { + $this->extraFields = $extraFields; + } + } + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // skip processing if for some reason request data + // is not present (CLI or wonky SAPIs) + if (!isset($this->serverData['REQUEST_URI'])) { + return $record; + } + + $record['extra'] = $this->appendExtraFields($record['extra']); + + return $record; + } + + /** + * @param string $extraName + * @param string $serverName + * @return $this + */ + public function addExtraField($extraName, $serverName) + { + $this->extraFields[$extraName] = $serverName; + + return $this; + } + + /** + * @param array $extra + * @return array + */ + private function appendExtraFields(array $extra) + { + foreach ($this->extraFields as $extraName => $serverName) { + $extra[$extraName] = isset($this->serverData[$serverName]) ? $this->serverData[$serverName] : null; + } + + if (isset($this->serverData['UNIQUE_ID'])) { + $extra['unique_id'] = $this->serverData['UNIQUE_ID']; + } + + return $extra; + } +} diff --git a/src/vendor/monolog/monolog/src/Monolog/Registry.php b/src/vendor/monolog/monolog/src/Monolog/Registry.php new file mode 100644 index 0000000..159b751 --- /dev/null +++ b/src/vendor/monolog/monolog/src/Monolog/Registry.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use InvalidArgumentException; + +/** + * Monolog log registry + * + * Allows to get `Logger` instances in the global scope + * via static method calls on this class. + * + * + * $application = new Monolog\Logger('application'); + * $api = new Monolog\Logger('api'); + * + * Monolog\Registry::addLogger($application); + * Monolog\Registry::addLogger($api); + * + * function testLogger() + * { + * Monolog\Registry::api()->addError('Sent to $api Logger instance'); + * Monolog\Registry::application()->addError('Sent to $application Logger instance'); + * } + * + * + * @author Tomas Tatarko + */ +class Registry +{ + /** + * List of all loggers in the registry (by named indexes) + * + * @var Logger[] + */ + private static $loggers = array(); + + /** + * Adds new logging channel to the registry + * + * @param Logger $logger Instance of the logging channel + * @param string|null $name Name of the logging channel ($logger->getName() by default) + * @param bool $overwrite Overwrite instance in the registry if the given name already exists? + * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists + */ + public static function addLogger(Logger $logger, $name = null, $overwrite = false) + { + $name = $name ?: $logger->getName(); + + if (isset(self::$loggers[$name]) && !$overwrite) { + throw new InvalidArgumentException('Logger with the given name already exists'); + } + + self::$loggers[$name] = $logger; + } + + /** + * Checks if such logging channel exists by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function hasLogger($logger) + { + if ($logger instanceof Logger) { + $index = array_search($logger, self::$loggers, true); + + return false !== $index; + } else { + return isset(self::$loggers[$logger]); + } + } + + /** + * Removes instance from registry by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function removeLogger($logger) + { + if ($logger instanceof Logger) { + if (false !== ($idx = array_search($logger, self::$loggers, true))) { + unset(self::$loggers[$idx]); + } + } else { + unset(self::$loggers[$logger]); + } + } + + /** + * Clears the registry + */ + public static function clear() + { + self::$loggers = array(); + } + + /** + * Gets Logger instance from the registry + * + * @param string $name Name of the requested Logger instance + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function getInstance($name) + { + if (!isset(self::$loggers[$name])) { + throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); + } + + return self::$loggers[$name]; + } + + /** + * Gets Logger instance from the registry via static method call + * + * @param string $name Name of the requested Logger instance + * @param array $arguments Arguments passed to static method call + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function __callStatic($name, $arguments) + { + return self::getInstance($name); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php new file mode 100644 index 0000000..a9a3f30 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\TestHandler; + +class ErrorHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testHandleError() + { + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new ErrorHandler($logger); + + $errHandler->registerErrorHandler(array(E_USER_NOTICE => Logger::EMERGENCY), false); + trigger_error('Foo', E_USER_ERROR); + $this->assertCount(1, $handler->getRecords()); + $this->assertTrue($handler->hasErrorRecords()); + trigger_error('Foo', E_USER_NOTICE); + $this->assertCount(2, $handler->getRecords()); + $this->assertTrue($handler->hasEmergencyRecords()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php new file mode 100644 index 0000000..71c4204 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class ChromePHPFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testDefaultFormat() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + array( + 'message' => 'log', + 'context' => array('from' => 'logger'), + 'extra' => array('ip' => '127.0.0.1'), + ), + 'unknown', + 'error', + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::CRITICAL, + 'level_name' => 'CRITICAL', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + array( + 'message' => 'log', + 'context' => array('from' => 'logger'), + 'extra' => array('ip' => '127.0.0.1'), + ), + 'test : 14', + 'error', + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testFormatWithoutContext() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::DEBUG, + 'level_name' => 'DEBUG', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + 'log', + 'unknown', + 'log', + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::formatBatch + */ + public function testBatchFormatThrowException() + { + $formatter = new ChromePHPFormatter(); + $records = array( + array( + 'level' => Logger::INFO, + 'level_name' => 'INFO', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ), + array( + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log2', + ), + ); + + $this->assertEquals( + array( + array( + 'meh', + 'log', + 'unknown', + 'info', + ), + array( + 'foo', + 'log2', + 'unknown', + 'warn', + ), + ), + $formatter->formatBatch($records) + ); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php new file mode 100644 index 0000000..90cc48d --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class ElasticaFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists("Elastica\Document")) { + $this->markTestSkipped("ruflin/elastica not installed"); + } + } + + /** + * @covers Monolog\Formatter\ElasticaFormatter::__construct + * @covers Monolog\Formatter\ElasticaFormatter::format + * @covers Monolog\Formatter\ElasticaFormatter::getDocument + */ + public function testFormat() + { + // test log message + $msg = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + // expected values + $expected = $msg; + $expected['datetime'] = '1970-01-01T00:00:00.000000+00:00'; + $expected['context'] = array( + 'class' => '[object] (stdClass: {})', + 'foo' => 7, + 0 => 'bar', + ); + + // format log message + $formatter = new ElasticaFormatter('my_index', 'doc_type'); + $doc = $formatter->format($msg); + $this->assertInstanceOf('Elastica\Document', $doc); + + // Document parameters + $params = $doc->getParams(); + $this->assertEquals('my_index', $params['_index']); + $this->assertEquals('doc_type', $params['_type']); + + // Document data values + $data = $doc->getData(); + foreach (array_keys($expected) as $key) { + $this->assertEquals($expected[$key], $data[$key]); + } + } + + /** + * @covers Monolog\Formatter\ElasticaFormatter::getIndex + * @covers Monolog\Formatter\ElasticaFormatter::getType + */ + public function testGetters() + { + $formatter = new ElasticaFormatter('my_index', 'doc_type'); + $this->assertEquals('my_index', $formatter->getIndex()); + $this->assertEquals('doc_type', $formatter->getType()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php new file mode 100644 index 0000000..1b2fd97 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class FlowdockFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\FlowdockFormatter::format + */ + public function testFormat() + { + $formatter = new FlowdockFormatter('test_source', 'source@test.com'); + $record = $this->getRecord(); + + $expected = array( + 'source' => 'test_source', + 'from_address' => 'source@test.com', + 'subject' => 'in test_source: WARNING - test', + 'content' => 'test', + 'tags' => array('#logs', '#warning', '#test'), + 'project' => 'test_source', + ); + $formatted = $formatter->format($record); + + $this->assertEquals($expected, $formatted['flowdock']); + } + + /** + * @ covers Monolog\Formatter\FlowdockFormatter::formatBatch + */ + public function testFormatBatch() + { + $formatter = new FlowdockFormatter('test_source', 'source@test.com'); + $records = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + $formatted = $formatter->formatBatch($records); + + $this->assertArrayHasKey('flowdock', $formatted[0]); + $this->assertArrayHasKey('flowdock', $formatted[1]); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php new file mode 100644 index 0000000..622b2ba --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class FluentdFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\FluentdFormatter::__construct + * @covers Monolog\Formatter\FluentdFormatter::isUsingLevelsInTag + */ + public function testConstruct() + { + $formatter = new FluentdFormatter(); + $this->assertEquals(false, $formatter->isUsingLevelsInTag()); + $formatter = new FluentdFormatter(false); + $this->assertEquals(false, $formatter->isUsingLevelsInTag()); + $formatter = new FluentdFormatter(true); + $this->assertEquals(true, $formatter->isUsingLevelsInTag()); + } + + /** + * @covers Monolog\Formatter\FluentdFormatter::format + */ + public function testFormat() + { + $record = $this->getRecord(Logger::WARNING); + $record['datetime'] = new \DateTime("@0"); + + $formatter = new FluentdFormatter(); + $this->assertEquals( + '["test",0,{"message":"test","extra":[],"level":300,"level_name":"WARNING"}]', + $formatter->format($record) + ); + } + + /** + * @covers Monolog\Formatter\FluentdFormatter::format + */ + public function testFormatWithTag() + { + $record = $this->getRecord(Logger::ERROR); + $record['datetime'] = new \DateTime("@0"); + + $formatter = new FluentdFormatter(true); + $this->assertEquals( + '["test.error",0,{"message":"test","extra":[]}]', + $formatter->format($record) + ); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php new file mode 100644 index 0000000..e6bd309 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists('\Gelf\Message')) { + $this->markTestSkipped("graylog2/gelf-php or mlehner/gelf-php is not installed"); + } + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testDefaultFormatter() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals(0, $message->getTimestamp()); + $this->assertEquals('log', $message->getShortMessage()); + $this->assertEquals('meh', $message->getFacility()); + $this->assertEquals(null, $message->getLine()); + $this->assertEquals(null, $message->getFile()); + $this->assertEquals($this->isLegacy() ? 3 : 'error', $message->getLevel()); + $this->assertNotEmpty($message->getHost()); + + $formatter = new GelfMessageFormatter('mysystem'); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('mysystem', $message->getHost()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('test', $message->getFile()); + $this->assertEquals(14, $message->getLine()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + * @expectedException InvalidArgumentException + */ + public function testFormatInvalidFails() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + ); + + $formatter->format($record); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithContext() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['_ctxt_from']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, null, 'CTX'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['_CTXfrom']); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithContextContainingException() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger', 'exception' => array( + 'class' => '\Exception', + 'file' => '/some/file/in/dir.php:56', + 'trace' => array('/some/file/1.php:23', '/some/file/2.php:3'), + )), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $this->assertEquals("/some/file/in/dir.php", $message->getFile()); + $this->assertEquals("56", $message->getLine()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithExtra() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_key', $message_array); + $this->assertEquals('pair', $message_array['_key']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, 'EXT'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_EXTkey', $message_array); + $this->assertEquals('pair', $message_array['_EXTkey']); + } + + public function testFormatWithLargeData() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('exception' => str_repeat(' ', 32767)), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => str_repeat(' ', 32767)), + 'message' => 'log' + ); + $message = $formatter->format($record); + $messageArray = $message->toArray(); + + // 200 for padding + metadata + $length = 200; + + foreach ($messageArray as $key => $value) { + if (!in_array($key, array('level', 'timestamp'))) { + $length += strlen($value); + } + } + + // in graylog2/gelf-php before 1.4.1 empty strings are filtered and won't be included in the message + // though it should be sufficient to ensure that the entire message length does not exceed the maximum + // length being allowed + $this->assertLessThanOrEqual(32766, $length, 'The message length is no longer than the maximum allowed length'); + } + + private function isLegacy() + { + return interface_exists('\Gelf\IMessagePublisher'); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php new file mode 100644 index 0000000..c9445f3 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class JsonFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\JsonFormatter::__construct + * @covers Monolog\Formatter\JsonFormatter::getBatchMode + * @covers Monolog\Formatter\JsonFormatter::isAppendingNewlines + */ + public function testConstruct() + { + $formatter = new JsonFormatter(); + $this->assertEquals(JsonFormatter::BATCH_MODE_JSON, $formatter->getBatchMode()); + $this->assertEquals(true, $formatter->isAppendingNewlines()); + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES, false); + $this->assertEquals(JsonFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode()); + $this->assertEquals(false, $formatter->isAppendingNewlines()); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::format + */ + public function testFormat() + { + $formatter = new JsonFormatter(); + $record = $this->getRecord(); + $this->assertEquals(json_encode($record)."\n", $formatter->format($record)); + + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + $record = $this->getRecord(); + $this->assertEquals(json_encode($record), $formatter->format($record)); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::formatBatch + * @covers Monolog\Formatter\JsonFormatter::formatBatchJson + */ + public function testFormatBatch() + { + $formatter = new JsonFormatter(); + $records = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + $this->assertEquals(json_encode($records), $formatter->formatBatch($records)); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::formatBatch + * @covers Monolog\Formatter\JsonFormatter::formatBatchNewlines + */ + public function testFormatBatchNewlines() + { + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES); + $records = $expected = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + array_walk($expected, function (&$value, $key) { + $value = json_encode($value); + }); + $this->assertEquals(implode("\n", $expected), $formatter->formatBatch($records)); + } + + public function testDefFormatWithException() + { + $formatter = new JsonFormatter(); + $exception = new \RuntimeException('Foo'); + $formattedException = $this->formatException($exception); + + $message = $this->formatRecordWithExceptionInContext($formatter, $exception); + + $this->assertContextContainsFormattedException($formattedException, $message); + } + + public function testDefFormatWithPreviousException() + { + $formatter = new JsonFormatter(); + $exception = new \RuntimeException('Foo', 0, new \LogicException('Wut?')); + $formattedPrevException = $this->formatException($exception->getPrevious()); + $formattedException = $this->formatException($exception, $formattedPrevException); + + $message = $this->formatRecordWithExceptionInContext($formatter, $exception); + + $this->assertContextContainsFormattedException($formattedException, $message); + } + + public function testDefFormatWithThrowable() + { + if (!class_exists('Error') || !is_subclass_of('Error', 'Throwable')) { + $this->markTestSkipped('Requires PHP >=7'); + } + + $formatter = new JsonFormatter(); + $throwable = new \Error('Foo'); + $formattedThrowable = $this->formatException($throwable); + + $message = $this->formatRecordWithExceptionInContext($formatter, $throwable); + + $this->assertContextContainsFormattedException($formattedThrowable, $message); + } + + /** + * @param string $expected + * @param string $actual + * + * @internal param string $exception + */ + private function assertContextContainsFormattedException($expected, $actual) + { + $this->assertEquals( + '{"level_name":"CRITICAL","channel":"core","context":{"exception":'.$expected.'},"datetime":null,"extra":[],"message":"foobar"}'."\n", + $actual + ); + } + + /** + * @param JsonFormatter $formatter + * @param \Exception|\Throwable $exception + * + * @return string + */ + private function formatRecordWithExceptionInContext(JsonFormatter $formatter, $exception) + { + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => $exception), + 'datetime' => null, + 'extra' => array(), + 'message' => 'foobar', + )); + return $message; + } + + /** + * @param \Exception|\Throwable $exception + * + * @return string + */ + private function formatExceptionFilePathWithLine($exception) + { + $options = 0; + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } + $path = substr(json_encode($exception->getFile(), $options), 1, -1); + return $path . ':' . $exception->getLine(); + } + + /** + * @param \Exception|\Throwable $exception + * + * @param null|string $previous + * + * @return string + */ + private function formatException($exception, $previous = null) + { + $formattedException = + '{"class":"' . get_class($exception) . + '","message":"' . $exception->getMessage() . + '","code":' . $exception->getCode() . + ',"file":"' . $this->formatExceptionFilePathWithLine($exception) . + ($previous ? '","previous":' . $previous : '"') . + '}'; + return $formattedException; + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php new file mode 100644 index 0000000..310d93c --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.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 Monolog\Formatter; + +/** + * @covers Monolog\Formatter\LineFormatter + */ +class LineFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function testDefFormatWithString() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'context' => array(), + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array(), + )); + $this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message); + } + + public function testDefFormatWithArrayContext() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array(), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + 'bool' => false, + 'null' => null, + ), + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foo {"foo":"bar","baz":"qux","bool":false,"null":null} []'."\n", $message); + } + + public function testDefFormatExtras() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] {"ip":"127.0.0.1"}'."\n", $message); + } + + public function testFormatExtras() + { + $formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra.file% %extra%\n", 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test'), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] test {"ip":"127.0.0.1"}'."\n", $message); + } + + public function testContextAndExtraOptionallyNotShownIfEmpty() + { + $formatter = new LineFormatter(null, 'Y-m-d', false, true); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log '."\n", $message); + } + + public function testContextAndExtraReplacement() + { + $formatter = new LineFormatter('%context.foo% => %extra.foo%'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 'bar'), + 'datetime' => new \DateTime, + 'extra' => array('foo' => 'xbar'), + 'message' => 'log', + )); + $this->assertEquals('bar => xbar', $message); + } + + public function testDefFormatWithObject() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFoo, 'bar' => new TestBar, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'message' => 'foobar', + )); + + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foobar [] {"foo":"[object] (Monolog\\\\Formatter\\\\TestFoo: {\\"foo\\":\\"foo\\"})","bar":"[object] (Monolog\\\\Formatter\\\\TestBar: bar)","baz":[],"res":"[resource] (stream)"}'."\n", $message); + } + + public function testDefFormatWithException() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => new \RuntimeException('Foo')), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'foobar', + )); + + $path = str_replace('\\/', '/', json_encode(__FILE__)); + + $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 8).')"} []'."\n", $message); + } + + public function testDefFormatWithPreviousException() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $previous = new \LogicException('Wut?'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => new \RuntimeException('Foo', 0, $previous)), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'foobar', + )); + + $path = str_replace('\\/', '/', json_encode(__FILE__)); + + $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 8).', LogicException(code: 0): Wut? at '.substr($path, 1, -1).':'.(__LINE__ - 12).')"} []'."\n", $message); + } + + public function testBatchFormat() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals('['.date('Y-m-d').'] test.CRITICAL: bar [] []'."\n".'['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message); + } + + public function testFormatShouldStripInlineLineBreaks() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format( + array( + 'message' => "foo\nbar", + 'context' => array(), + 'extra' => array(), + ) + ); + + $this->assertRegExp('/foo bar/', $message); + } + + public function testFormatShouldNotStripInlineLineBreaksWhenFlagIsSet() + { + $formatter = new LineFormatter(null, 'Y-m-d', true); + $message = $formatter->format( + array( + 'message' => "foo\nbar", + 'context' => array(), + 'extra' => array(), + ) + ); + + $this->assertRegExp('/foo\nbar/', $message); + } +} + +class TestFoo +{ + public $foo = 'foo'; +} + +class TestBar +{ + public function __toString() + { + return 'bar'; + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php new file mode 100644 index 0000000..6d59b3f --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\TestCase; + +class LogglyFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\LogglyFormatter::__construct + */ + public function testConstruct() + { + $formatter = new LogglyFormatter(); + $this->assertEquals(LogglyFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode()); + $formatter = new LogglyFormatter(LogglyFormatter::BATCH_MODE_JSON); + $this->assertEquals(LogglyFormatter::BATCH_MODE_JSON, $formatter->getBatchMode()); + } + + /** + * @covers Monolog\Formatter\LogglyFormatter::format + */ + public function testFormat() + { + $formatter = new LogglyFormatter(); + $record = $this->getRecord(); + $formatted_decoded = json_decode($formatter->format($record), true); + $this->assertArrayHasKey("timestamp", $formatted_decoded); + $this->assertEquals(new \DateTime($formatted_decoded["timestamp"]), $record["datetime"]); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php new file mode 100644 index 0000000..9f6b1cc --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php @@ -0,0 +1,333 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class LogstashFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + \PHPUnit_Framework_Error_Warning::$enabled = true; + + return parent::tearDown(); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testDefaultFormatter() + { + $formatter = new LogstashFormatter('test', 'hostname'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']); + $this->assertEquals('log', $message['@message']); + $this->assertEquals('meh', $message['@fields']['channel']); + $this->assertContains('meh', $message['@tags']); + $this->assertEquals(Logger::ERROR, $message['@fields']['level']); + $this->assertEquals('test', $message['@type']); + $this->assertEquals('hostname', $message['@source']); + + $formatter = new LogstashFormatter('mysystem'); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('mysystem', $message['@type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('test', $message['@fields']['file']); + $this->assertEquals(14, $message['@fields']['line']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithContext() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['ctxt_from']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, null, 'CTX'); + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['CTXfrom']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithExtra() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('key', $message_array); + $this->assertEquals('pair', $message_array['key']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, 'EXT'); + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('EXTkey', $message_array); + $this->assertEquals('pair', $message_array['EXTkey']); + } + + public function testFormatWithApplicationName() + { + $formatter = new LogstashFormatter('app', 'test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('@type', $message); + $this->assertEquals('app', $message['@type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testDefaultFormatterV1() + { + $formatter = new LogstashFormatter('test', 'hostname', null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']); + $this->assertEquals("1", $message['@version']); + $this->assertEquals('log', $message['message']); + $this->assertEquals('meh', $message['channel']); + $this->assertEquals('ERROR', $message['level']); + $this->assertEquals('test', $message['type']); + $this->assertEquals('hostname', $message['host']); + + $formatter = new LogstashFormatter('mysystem', null, null, 'ctxt_', LogstashFormatter::V1); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('mysystem', $message['type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithFileAndLineV1() + { + $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('test', $message['file']); + $this->assertEquals(14, $message['line']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithContextV1() + { + $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('ctxt_from', $message); + $this->assertEquals('logger', $message['ctxt_from']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, null, 'CTX', LogstashFormatter::V1); + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('CTXfrom', $message); + $this->assertEquals('logger', $message['CTXfrom']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithExtraV1() + { + $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('key', $message); + $this->assertEquals('pair', $message['key']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, 'EXT', 'ctxt_', LogstashFormatter::V1); + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('EXTkey', $message); + $this->assertEquals('pair', $message['EXTkey']); + } + + public function testFormatWithApplicationNameV1() + { + $formatter = new LogstashFormatter('app', 'test', null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('type', $message); + $this->assertEquals('app', $message['type']); + } + + public function testFormatWithLatin9Data() + { + if (version_compare(PHP_VERSION, '5.5.0', '<')) { + // Ignore the warning that will be emitted by PHP <5.5.0 + \PHPUnit_Framework_Error_Warning::$enabled = false; + } + $formatter = new LogstashFormatter('test', 'hostname'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => '¯\_(ツ)_/¯', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array( + 'user_agent' => "\xD6WN; FBCR/OrangeEspa\xF1a; Vers\xE3o/4.0; F\xE4rist", + ), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']); + $this->assertEquals('log', $message['@message']); + $this->assertEquals('¯\_(ツ)_/¯', $message['@fields']['channel']); + $this->assertContains('¯\_(ツ)_/¯', $message['@tags']); + $this->assertEquals(Logger::ERROR, $message['@fields']['level']); + $this->assertEquals('test', $message['@type']); + $this->assertEquals('hostname', $message['@source']); + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $this->assertEquals('ÖWN; FBCR/OrangeEspaña; Versão/4.0; Färist', $message['@fields']['user_agent']); + } else { + // PHP <5.5 does not return false for an element encoding failure, + // instead it emits a warning (possibly) and nulls the value. + $this->assertEquals(null, $message['@fields']['user_agent']); + } + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php new file mode 100644 index 0000000..52e699e --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php @@ -0,0 +1,262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * @author Florian Plattner + */ +class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists('MongoDate')) { + $this->markTestSkipped('mongo extension not installed'); + } + } + + public function constructArgumentProvider() + { + return array( + array(1, true, 1, true), + array(0, false, 0, false), + ); + } + + /** + * @param $traceDepth + * @param $traceAsString + * @param $expectedTraceDepth + * @param $expectedTraceAsString + * + * @dataProvider constructArgumentProvider + */ + public function testConstruct($traceDepth, $traceAsString, $expectedTraceDepth, $expectedTraceAsString) + { + $formatter = new MongoDBFormatter($traceDepth, $traceAsString); + + $reflTrace = new \ReflectionProperty($formatter, 'exceptionTraceAsString'); + $reflTrace->setAccessible(true); + $this->assertEquals($expectedTraceAsString, $reflTrace->getValue($formatter)); + + $reflDepth = new\ReflectionProperty($formatter, 'maxNestingLevel'); + $reflDepth->setAccessible(true); + $this->assertEquals($expectedTraceDepth, $reflDepth->getValue($formatter)); + } + + public function testSimpleFormat() + { + $record = array( + 'message' => 'some log message', + 'context' => array(), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(); + $formattedRecord = $formatter->format($record); + + $this->assertCount(7, $formattedRecord); + $this->assertEquals('some log message', $formattedRecord['message']); + $this->assertEquals(array(), $formattedRecord['context']); + $this->assertEquals(Logger::WARNING, $formattedRecord['level']); + $this->assertEquals(Logger::getLevelName(Logger::WARNING), $formattedRecord['level_name']); + $this->assertEquals('test', $formattedRecord['channel']); + $this->assertInstanceOf('\MongoDate', $formattedRecord['datetime']); + $this->assertEquals('0.00000000 1391212800', $formattedRecord['datetime']->__toString()); + $this->assertEquals(array(), $formattedRecord['extra']); + } + + public function testRecursiveFormat() + { + $someObject = new \stdClass(); + $someObject->foo = 'something'; + $someObject->bar = 'stuff'; + + $record = array( + 'message' => 'some log message', + 'context' => array( + 'stuff' => new \DateTime('2014-02-01 02:31:33'), + 'some_object' => $someObject, + 'context_string' => 'some string', + 'context_int' => 123456, + 'except' => new \Exception('exception message', 987), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(); + $formattedRecord = $formatter->format($record); + + $this->assertCount(5, $formattedRecord['context']); + $this->assertInstanceOf('\MongoDate', $formattedRecord['context']['stuff']); + $this->assertEquals('0.00000000 1391221893', $formattedRecord['context']['stuff']->__toString()); + $this->assertEquals( + array( + 'foo' => 'something', + 'bar' => 'stuff', + 'class' => 'stdClass', + ), + $formattedRecord['context']['some_object'] + ); + $this->assertEquals('some string', $formattedRecord['context']['context_string']); + $this->assertEquals(123456, $formattedRecord['context']['context_int']); + + $this->assertCount(5, $formattedRecord['context']['except']); + $this->assertEquals('exception message', $formattedRecord['context']['except']['message']); + $this->assertEquals(987, $formattedRecord['context']['except']['code']); + $this->assertInternalType('string', $formattedRecord['context']['except']['file']); + $this->assertInternalType('integer', $formattedRecord['context']['except']['code']); + $this->assertInternalType('string', $formattedRecord['context']['except']['trace']); + $this->assertEquals('Exception', $formattedRecord['context']['except']['class']); + } + + public function testFormatDepthArray() + { + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => array( + 'property' => 'anything', + 'nest3' => array( + 'nest4' => 'value', + 'property' => 'nothing', + ), + ), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(2); + $formattedResult = $formatter->format($record); + + $this->assertEquals( + array( + 'nest2' => array( + 'property' => 'anything', + 'nest3' => '[...]', + ), + ), + $formattedResult['context'] + ); + } + + public function testFormatDepthArrayInfiniteNesting() + { + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => array( + 'property' => 'something', + 'nest3' => array( + 'property' => 'anything', + 'nest4' => array( + 'property' => 'nothing', + ), + ), + ), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(0); + $formattedResult = $formatter->format($record); + + $this->assertEquals( + array( + 'nest2' => array( + 'property' => 'something', + 'nest3' => array( + 'property' => 'anything', + 'nest4' => array( + 'property' => 'nothing', + ), + ), + ), + ), + $formattedResult['context'] + ); + } + + public function testFormatDepthObjects() + { + $someObject = new \stdClass(); + $someObject->property = 'anything'; + $someObject->nest3 = new \stdClass(); + $someObject->nest3->property = 'nothing'; + $someObject->nest3->nest4 = 'invisible'; + + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => $someObject, + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(2, true); + $formattedResult = $formatter->format($record); + + $this->assertEquals( + array( + 'nest2' => array( + 'property' => 'anything', + 'nest3' => '[...]', + 'class' => 'stdClass', + ), + ), + $formattedResult['context'] + ); + } + + public function testFormatDepthException() + { + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => new \Exception('exception message', 987), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(2, false); + $formattedRecord = $formatter->format($record); + + $this->assertEquals('exception message', $formattedRecord['context']['nest2']['message']); + $this->assertEquals(987, $formattedRecord['context']['nest2']['code']); + $this->assertEquals('[...]', $formattedRecord['context']['nest2']['trace']); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php new file mode 100644 index 0000000..57bcdf9 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php @@ -0,0 +1,423 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * @covers Monolog\Formatter\NormalizerFormatter + */ +class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + \PHPUnit_Framework_Error_Warning::$enabled = true; + + return parent::tearDown(); + } + + public function testFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFooNorm, 'bar' => new TestBarNorm, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + 'inf' => INF, + '-inf' => -INF, + 'nan' => acos(4), + ), + )); + + $this->assertEquals(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => date('Y-m-d'), + 'extra' => array( + 'foo' => '[object] (Monolog\\Formatter\\TestFooNorm: {"foo":"foo"})', + 'bar' => '[object] (Monolog\\Formatter\\TestBarNorm: bar)', + 'baz' => array(), + 'res' => '[resource] (stream)', + ), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + 'inf' => 'INF', + '-inf' => '-INF', + 'nan' => 'NaN', + ), + ), $formatted); + } + + public function testFormatExceptions() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $e = new \LogicException('bar'); + $e2 = new \RuntimeException('foo', 0, $e); + $formatted = $formatter->format(array( + 'exception' => $e2, + )); + + $this->assertGreaterThan(5, count($formatted['exception']['trace'])); + $this->assertTrue(isset($formatted['exception']['previous'])); + unset($formatted['exception']['trace'], $formatted['exception']['previous']); + + $this->assertEquals(array( + 'exception' => array( + 'class' => get_class($e2), + 'message' => $e2->getMessage(), + 'code' => $e2->getCode(), + 'file' => $e2->getFile().':'.$e2->getLine(), + ), + ), $formatted); + } + + public function testFormatSoapFaultException() + { + if (!class_exists('SoapFault')) { + $this->markTestSkipped('Requires the soap extension'); + } + + $formatter = new NormalizerFormatter('Y-m-d'); + $e = new \SoapFault('foo', 'bar', 'hello', 'world'); + $formatted = $formatter->format(array( + 'exception' => $e, + )); + + unset($formatted['exception']['trace']); + + $this->assertEquals(array( + 'exception' => array( + 'class' => 'SoapFault', + 'message' => 'bar', + 'code' => 0, + 'file' => $e->getFile().':'.$e->getLine(), + 'faultcode' => 'foo', + 'faultactor' => 'hello', + 'detail' => 'world', + ), + ), $formatted); + } + + public function testFormatToStringExceptionHandle() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $this->setExpectedException('RuntimeException', 'Could not convert to string'); + $formatter->format(array( + 'myObject' => new TestToStringError(), + )); + } + + public function testBatchFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + ), $formatted); + } + + /** + * Test issue #137 + */ + public function testIgnoresRecursiveObjectReferences() + { + // set up the recursion + $foo = new \stdClass(); + $bar = new \stdClass(); + + $foo->bar = $bar; + $bar->foo = $foo; + + // set an error handler to assert that the error is not raised anymore + $that = $this; + set_error_handler(function ($level, $message, $file, $line, $context) use ($that) { + if (error_reporting() & $level) { + restore_error_handler(); + $that->fail("$message should not be raised"); + } + }); + + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + $res = $reflMethod->invoke($formatter, array($foo, $bar), true); + + restore_error_handler(); + + $this->assertEquals(@json_encode(array($foo, $bar)), $res); + } + + public function testIgnoresInvalidTypes() + { + // set up the recursion + $resource = fopen(__FILE__, 'r'); + + // set an error handler to assert that the error is not raised anymore + $that = $this; + set_error_handler(function ($level, $message, $file, $line, $context) use ($that) { + if (error_reporting() & $level) { + restore_error_handler(); + $that->fail("$message should not be raised"); + } + }); + + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + $res = $reflMethod->invoke($formatter, array($resource), true); + + restore_error_handler(); + + $this->assertEquals(@json_encode(array($resource)), $res); + } + + public function testNormalizeHandleLargeArrays() + { + $formatter = new NormalizerFormatter(); + $largeArray = range(1, 2000); + + $res = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array($largeArray), + 'datetime' => new \DateTime, + 'extra' => array(), + )); + + $this->assertCount(1000, $res['context'][0]); + $this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']); + } + + /** + * @expectedException RuntimeException + */ + public function testThrowsOnInvalidEncoding() + { + if (version_compare(PHP_VERSION, '5.5.0', '<')) { + // Ignore the warning that will be emitted by PHP <5.5.0 + \PHPUnit_Framework_Error_Warning::$enabled = false; + } + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + + // send an invalid unicode sequence as a object that can't be cleaned + $record = new \stdClass; + $record->message = "\xB1\x31"; + $res = $reflMethod->invoke($formatter, $record); + if (PHP_VERSION_ID < 50500 && $res === '{"message":null}') { + throw new \RuntimeException('PHP 5.3/5.4 throw a warning and null the value instead of returning false entirely'); + } + } + + public function testConvertsInvalidEncodingAsLatin9() + { + if (version_compare(PHP_VERSION, '5.5.0', '<')) { + // Ignore the warning that will be emitted by PHP <5.5.0 + \PHPUnit_Framework_Error_Warning::$enabled = false; + } + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + + $res = $reflMethod->invoke($formatter, array('message' => "\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE")); + + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $this->assertSame('{"message":"€ŠšŽžŒœŸ"}', $res); + } else { + // PHP <5.5 does not return false for an element encoding failure, + // instead it emits a warning (possibly) and nulls the value. + $this->assertSame('{"message":null}', $res); + } + } + + /** + * @param mixed $in Input + * @param mixed $expect Expected output + * @covers Monolog\Formatter\NormalizerFormatter::detectAndCleanUtf8 + * @dataProvider providesDetectAndCleanUtf8 + */ + public function testDetectAndCleanUtf8($in, $expect) + { + $formatter = new NormalizerFormatter(); + $formatter->detectAndCleanUtf8($in); + $this->assertSame($expect, $in); + } + + public function providesDetectAndCleanUtf8() + { + $obj = new \stdClass; + + return array( + 'null' => array(null, null), + 'int' => array(123, 123), + 'float' => array(123.45, 123.45), + 'bool false' => array(false, false), + 'bool true' => array(true, true), + 'ascii string' => array('abcdef', 'abcdef'), + 'latin9 string' => array("\xB1\x31\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE\xFF", '±1€ŠšŽžŒœŸÿ'), + 'unicode string' => array('¤¦¨´¸¼½¾€ŠšŽžŒœŸ', '¤¦¨´¸¼½¾€ŠšŽžŒœŸ'), + 'empty array' => array(array(), array()), + 'array' => array(array('abcdef'), array('abcdef')), + 'object' => array($obj, $obj), + ); + } + + /** + * @param int $code + * @param string $msg + * @dataProvider providesHandleJsonErrorFailure + */ + public function testHandleJsonErrorFailure($code, $msg) + { + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'handleJsonError'); + $reflMethod->setAccessible(true); + + $this->setExpectedException('RuntimeException', $msg); + $reflMethod->invoke($formatter, $code, 'faked'); + } + + public function providesHandleJsonErrorFailure() + { + return array( + 'depth' => array(JSON_ERROR_DEPTH, 'Maximum stack depth exceeded'), + 'state' => array(JSON_ERROR_STATE_MISMATCH, 'Underflow or the modes mismatch'), + 'ctrl' => array(JSON_ERROR_CTRL_CHAR, 'Unexpected control character found'), + 'default' => array(-1, 'Unknown error'), + ); + } + + public function testExceptionTraceWithArgs() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Not supported in HHVM since it detects errors differently'); + } + + // This happens i.e. in React promises or Guzzle streams where stream wrappers are registered + // and no file or line are included in the trace because it's treated as internal function + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); + }); + + try { + // This will contain $resource and $wrappedResource as arguments in the trace item + $resource = fopen('php://memory', 'rw+'); + fwrite($resource, 'test_resource'); + $wrappedResource = new TestFooNorm; + $wrappedResource->foo = $resource; + // Just do something stupid with a resource/wrapped resource as argument + array_keys($wrappedResource); + } catch (\Exception $e) { + restore_error_handler(); + } + + $formatter = new NormalizerFormatter(); + $record = array('context' => array('exception' => $e)); + $result = $formatter->format($record); + + $this->assertRegExp( + '%"resource":"\[resource\] \(stream\)"%', + $result['context']['exception']['trace'][0] + ); + + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $pattern = '%"wrappedResource":"\[object\] \(Monolog\\\\\\\\Formatter\\\\\\\\TestFooNorm: \)"%'; + } else { + $pattern = '%\\\\"foo\\\\":null%'; + } + + // Tests that the wrapped resource is ignored while encoding, only works for PHP <= 5.4 + $this->assertRegExp( + $pattern, + $result['context']['exception']['trace'][0] + ); + } +} + +class TestFooNorm +{ + public $foo = 'foo'; +} + +class TestBarNorm +{ + public function __toString() + { + return 'bar'; + } +} + +class TestStreamFoo +{ + public $foo; + public $resource; + + public function __construct($resource) + { + $this->resource = $resource; + $this->foo = 'BAR'; + } + + public function __toString() + { + fseek($this->resource, 0); + + return $this->foo . ' - ' . (string) stream_get_contents($this->resource); + } +} + +class TestToStringError +{ + public function __toString() + { + throw new \RuntimeException('Could not convert to string'); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php new file mode 100644 index 0000000..b1c8fd4 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.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 Monolog\Formatter; + +class ScalarFormatterTest extends \PHPUnit_Framework_TestCase +{ + private $formatter; + + public function setUp() + { + $this->formatter = new ScalarFormatter(); + } + + public function buildTrace(\Exception $e) + { + $data = array(); + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data[] = $frame['file'].':'.$frame['line']; + } else { + $data[] = json_encode($frame); + } + } + + return $data; + } + + public function encodeJson($data) + { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return json_encode($data); + } + + public function testFormat() + { + $exception = new \Exception('foo'); + $formatted = $this->formatter->format(array( + 'foo' => 'string', + 'bar' => 1, + 'baz' => false, + 'bam' => array(1, 2, 3), + 'bat' => array('foo' => 'bar'), + 'bap' => \DateTime::createFromFormat(\DateTime::ISO8601, '1970-01-01T00:00:00+0000'), + 'ban' => $exception, + )); + + $this->assertSame(array( + 'foo' => 'string', + 'bar' => 1, + 'baz' => false, + 'bam' => $this->encodeJson(array(1, 2, 3)), + 'bat' => $this->encodeJson(array('foo' => 'bar')), + 'bap' => '1970-01-01 00:00:00', + 'ban' => $this->encodeJson(array( + 'class' => get_class($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + 'trace' => $this->buildTrace($exception), + )), + ), $formatted); + } + + public function testFormatWithErrorContext() + { + $context = array('file' => 'foo', 'line' => 1); + $formatted = $this->formatter->format(array( + 'context' => $context, + )); + + $this->assertSame(array( + 'context' => $this->encodeJson($context), + ), $formatted); + } + + public function testFormatWithExceptionContext() + { + $exception = new \Exception('foo'); + $formatted = $this->formatter->format(array( + 'context' => array( + 'exception' => $exception, + ), + )); + + $this->assertSame(array( + 'context' => $this->encodeJson(array( + 'exception' => array( + 'class' => get_class($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + 'trace' => $this->buildTrace($exception), + ), + )), + ), $formatted); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php b/src/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php new file mode 100644 index 0000000..52f15a3 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class WildfireFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testDefaultFormat() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '125|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},' + .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testFormatWithFileAndLine() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '129|[{"Type":"ERROR","File":"test","Line":14,"Label":"meh"},' + .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testFormatWithoutContext() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '58|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},"log"]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::formatBatch + * @expectedException BadMethodCallException + */ + public function testBatchFormatThrowException() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $wildfire->formatBatch(array($record)); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testTableFormat() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'table-channel', + 'context' => array( + WildfireFormatter::TABLE => array( + array('col1', 'col2', 'col3'), + array('val1', 'val2', 'val3'), + array('foo1', 'foo2', 'foo3'), + array('bar1', 'bar2', 'bar3'), + ), + ), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'table-message', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '171|[{"Type":"TABLE","File":"","Line":"","Label":"table-channel: table-message"},[["col1","col2","col3"],["val1","val2","val3"],["foo1","foo2","foo3"],["bar1","bar2","bar3"]]]|', + $message + ); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php new file mode 100644 index 0000000..568eb9d --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Processor\WebProcessor; + +class AbstractHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\AbstractHandler::__construct + * @covers Monolog\Handler\AbstractHandler::getLevel + * @covers Monolog\Handler\AbstractHandler::setLevel + * @covers Monolog\Handler\AbstractHandler::getBubble + * @covers Monolog\Handler\AbstractHandler::setBubble + * @covers Monolog\Handler\AbstractHandler::getFormatter + * @covers Monolog\Handler\AbstractHandler::setFormatter + */ + public function testConstructAndGetSet() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false)); + $this->assertEquals(Logger::WARNING, $handler->getLevel()); + $this->assertEquals(false, $handler->getBubble()); + + $handler->setLevel(Logger::ERROR); + $handler->setBubble(true); + $handler->setFormatter($formatter = new LineFormatter); + $this->assertEquals(Logger::ERROR, $handler->getLevel()); + $this->assertEquals(true, $handler->getBubble()); + $this->assertSame($formatter, $handler->getFormatter()); + } + + /** + * @covers Monolog\Handler\AbstractHandler::handleBatch + */ + public function testHandleBatch() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $handler->expects($this->exactly(2)) + ->method('handle'); + $handler->handleBatch(array($this->getRecord(), $this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractHandler::isHandling + */ + public function testIsHandling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false)); + $this->assertTrue($handler->isHandling($this->getRecord())); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractHandler::__construct + */ + public function testHandlesPsrStyleLevels() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array('warning', false)); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + $handler->setLevel('debug'); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractHandler::getFormatter + * @covers Monolog\Handler\AbstractHandler::getDefaultFormatter + */ + public function testGetFormatterInitializesDefault() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $this->assertInstanceOf('Monolog\Formatter\LineFormatter', $handler->getFormatter()); + } + + /** + * @covers Monolog\Handler\AbstractHandler::pushProcessor + * @covers Monolog\Handler\AbstractHandler::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Handler\AbstractHandler::pushProcessor + * @expectedException InvalidArgumentException + */ + public function testPushProcessorWithNonCallable() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + + $handler->pushProcessor(new \stdClass()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php new file mode 100644 index 0000000..24d4f63 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Processor\WebProcessor; + +class AbstractProcessingHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleLowerLevelMessage() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, true)); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleBubbling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, true)); + $this->assertFalse($handler->handle($this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleNotBubbling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, false)); + $this->assertTrue($handler->handle($this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleIsFalseWhenNotHandled() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, false)); + $this->assertTrue($handler->handle($this->getRecord())); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::processRecord + */ + public function testProcessRecord() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler'); + $handler->pushProcessor(new WebProcessor(array( + 'REQUEST_URI' => '', + 'REQUEST_METHOD' => '', + 'REMOTE_ADDR' => '', + 'SERVER_NAME' => '', + 'UNIQUE_ID' => '', + ))); + $handledRecord = null; + $handler->expects($this->once()) + ->method('write') + ->will($this->returnCallback(function ($record) use (&$handledRecord) { + $handledRecord = $record; + })) + ; + $handler->handle($this->getRecord()); + $this->assertEquals(6, count($handledRecord['extra'])); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php new file mode 100644 index 0000000..8e0e723 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Connection\AMQPConnection; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class AmqpHandlerTest extends TestCase +{ + public function testHandleAmqpExt() + { + if (!class_exists('AMQPConnection') || !class_exists('AMQPExchange')) { + $this->markTestSkipped("amqp-php not installed"); + } + + if (!class_exists('AMQPChannel')) { + $this->markTestSkipped("Please update AMQP to version >= 1.0"); + } + + $messages = array(); + + $exchange = $this->getMock('AMQPExchange', array('publish', 'setName'), array(), '', false); + $exchange->expects($this->once()) + ->method('setName') + ->with('log') + ; + $exchange->expects($this->any()) + ->method('publish') + ->will($this->returnCallback(function ($message, $routing_key, $flags = 0, $attributes = array()) use (&$messages) { + $messages[] = array($message, $routing_key, $flags, $attributes); + })) + ; + + $handler = new AmqpHandler($exchange, 'log'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + array( + 'message' => 'test', + 'context' => array( + 'data' => array(), + 'foo' => 34, + ), + 'level' => 300, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'extra' => array(), + ), + 'warn.test', + 0, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ), + ); + + $handler->handle($record); + + $this->assertCount(1, $messages); + $messages[0][0] = json_decode($messages[0][0], true); + unset($messages[0][0]['datetime']); + $this->assertEquals($expected, $messages[0]); + } + + public function testHandlePhpAmqpLib() + { + if (!class_exists('PhpAmqpLib\Connection\AMQPConnection')) { + $this->markTestSkipped("php-amqplib not installed"); + } + + $messages = array(); + + $exchange = $this->getMock('PhpAmqpLib\Channel\AMQPChannel', array('basic_publish', '__destruct'), array(), '', false); + + $exchange->expects($this->any()) + ->method('basic_publish') + ->will($this->returnCallback(function (AMQPMessage $msg, $exchange = "", $routing_key = "", $mandatory = false, $immediate = false, $ticket = null) use (&$messages) { + $messages[] = array($msg, $exchange, $routing_key, $mandatory, $immediate, $ticket); + })) + ; + + $handler = new AmqpHandler($exchange, 'log'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + array( + 'message' => 'test', + 'context' => array( + 'data' => array(), + 'foo' => 34, + ), + 'level' => 300, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'extra' => array(), + ), + 'log', + 'warn.test', + false, + false, + null, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ), + ); + + $handler->handle($record); + + $this->assertCount(1, $messages); + + /* @var $msg AMQPMessage */ + $msg = $messages[0][0]; + $messages[0][0] = json_decode($msg->body, true); + $messages[0][] = $msg->get_properties(); + unset($messages[0][0]['datetime']); + + $this->assertEquals($expected, $messages[0]); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php new file mode 100644 index 0000000..ffb1d74 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\BrowserConsoleHandlerTest + */ +class BrowserConsoleHandlerTest extends TestCase +{ + protected function setUp() + { + BrowserConsoleHandler::reset(); + } + + protected function generateScript() + { + $reflMethod = new \ReflectionMethod('Monolog\Handler\BrowserConsoleHandler', 'generateScript'); + $reflMethod->setAccessible(true); + + return $reflMethod->invoke(null); + } + + public function testStyling() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, 'foo[[bar]]{color: red}')); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testEscaping() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, "[foo] [[\"bar\n[baz]\"]]{color: red}")); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testAutolabel() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, '[[foo]]{macro: autolabel}')); + $handler->handle($this->getRecord(Logger::DEBUG, '[[bar]]{macro: autolabel}')); + $handler->handle($this->getRecord(Logger::DEBUG, '[[foo]]{macro: autolabel}')); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testContext() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, 'test', array('foo' => 'bar'))); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testConcurrentHandlers() + { + $handler1 = new BrowserConsoleHandler(); + $handler1->setFormatter($this->getIdentityFormatter()); + + $handler2 = new BrowserConsoleHandler(); + $handler2->setFormatter($this->getIdentityFormatter()); + + $handler1->handle($this->getRecord(Logger::DEBUG, 'test1')); + $handler2->handle($this->getRecord(Logger::DEBUG, 'test2')); + $handler1->handle($this->getRecord(Logger::DEBUG, 'test3')); + $handler2->handle($this->getRecord(Logger::DEBUG, 'test4')); + + $expected = <<assertEquals($expected, $this->generateScript()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php new file mode 100644 index 0000000..da8b3c3 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class BufferHandlerTest extends TestCase +{ + private $shutdownCheckHandler; + + /** + * @covers Monolog\Handler\BufferHandler::__construct + * @covers Monolog\Handler\BufferHandler::handle + * @covers Monolog\Handler\BufferHandler::close + */ + public function testHandleBuffers() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->close(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + + /** + * @covers Monolog\Handler\BufferHandler::close + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testPropagatesRecordsAtEndOfRequest() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->shutdownCheckHandler = $test; + register_shutdown_function(array($this, 'checkPropagation')); + } + + public function checkPropagation() + { + if (!$this->shutdownCheckHandler->hasWarningRecords() || !$this->shutdownCheckHandler->hasDebugRecords()) { + echo '!!! BufferHandlerTest::testPropagatesRecordsAtEndOfRequest failed to verify that the messages have been propagated' . PHP_EOL; + exit(1); + } + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimit() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 2); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimitWithFlushOnOverflow() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 3, Logger::DEBUG, true, true); + + // send two records + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertCount(0, $test->getRecords()); + + // overflow + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertCount(3, $test->getRecords()); + + // should buffer again + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertCount(3, $test->getRecords()); + + $handler->close(); + $this->assertCount(5, $test->getRecords()); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleLevel() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0, Logger::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testFlush() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->flush(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->flush(); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php new file mode 100644 index 0000000..ef3cd1c --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\ChromePHPHandler + */ +class ChromePHPHandlerTest extends TestCase +{ + protected function setUp() + { + TestChromePHPHandler::reset(); + $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; Chrome/1.0'; + } + + public function testHeaders() + { + $handler = new TestChromePHPHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + 'test', + 'test', + ), + 'request_uri' => '', + )))), + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testHeadersOverflow() + { + $handler = new TestChromePHPHandler(); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 150 * 1024))); + + // overflow chrome headers limit + $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 100 * 1024))); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + array( + 'test', + 'test', + 'unknown', + 'log', + ), + array( + 'test', + str_repeat('a', 150 * 1024), + 'unknown', + 'warn', + ), + array( + 'monolog', + 'Incomplete logs, chrome header size limit reached', + 'unknown', + 'warn', + ), + ), + 'request_uri' => '', + )))), + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testConcurrentHandlers() + { + $handler = new TestChromePHPHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $handler2 = new TestChromePHPHandler(); + $handler2->setFormatter($this->getIdentityFormatter()); + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + 'test', + 'test', + 'test', + 'test', + ), + 'request_uri' => '', + )))), + ); + + $this->assertEquals($expected, $handler2->getHeaders()); + } +} + +class TestChromePHPHandler extends ChromePHPHandler +{ + protected $headers = array(); + + public static function reset() + { + self::$initialized = false; + self::$overflowed = false; + self::$sendHeaders = true; + self::$json['rows'] = array(); + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php new file mode 100644 index 0000000..9fc4b38 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class CouchDBHandlerTest extends TestCase +{ + public function testHandle() + { + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new CouchDBHandler(); + + try { + $handler->handle($record); + } catch (\RuntimeException $e) { + $this->markTestSkipped('Could not connect to couchdb server on http://localhost:5984'); + } + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php new file mode 100644 index 0000000..e2aff86 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class DeduplicationHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + */ + public function testFlushPassthruIfAllRecordsUnderTrigger() + { + $test = new TestHandler(); + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + + $handler->flush(); + + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + */ + public function testFlushPassthruIfEmptyLog() + { + $test = new TestHandler(); + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $handler->handle($this->getRecord(Logger::ERROR, 'Foo:bar')); + $handler->handle($this->getRecord(Logger::CRITICAL, "Foo\nbar")); + + $handler->flush(); + + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + * @covers Monolog\Handler\DeduplicationHandler::isDuplicate + * @depends testFlushPassthruIfEmptyLog + */ + public function testFlushSkipsIfLogExists() + { + $test = new TestHandler(); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $handler->handle($this->getRecord(Logger::ERROR, 'Foo:bar')); + $handler->handle($this->getRecord(Logger::CRITICAL, "Foo\nbar")); + + $handler->flush(); + + $this->assertFalse($test->hasErrorRecords()); + $this->assertFalse($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + * @covers Monolog\Handler\DeduplicationHandler::isDuplicate + * @depends testFlushPassthruIfEmptyLog + */ + public function testFlushPassthruIfLogTooOld() + { + $test = new TestHandler(); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $record = $this->getRecord(Logger::ERROR); + $record['datetime']->modify('+62seconds'); + $handler->handle($record); + $record = $this->getRecord(Logger::CRITICAL); + $record['datetime']->modify('+62seconds'); + $handler->handle($record); + + $handler->flush(); + + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + * @covers Monolog\Handler\DeduplicationHandler::isDuplicate + * @covers Monolog\Handler\DeduplicationHandler::collectLogs + */ + public function testGcOldLogs() + { + $test = new TestHandler(); + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + // handle two records from yesterday, and one recent + $record = $this->getRecord(Logger::ERROR); + $record['datetime']->modify('-1day -10seconds'); + $handler->handle($record); + $record2 = $this->getRecord(Logger::CRITICAL); + $record2['datetime']->modify('-1day -10seconds'); + $handler->handle($record2); + $record3 = $this->getRecord(Logger::CRITICAL); + $record3['datetime']->modify('-30seconds'); + $handler->handle($record3); + + // log is written as none of them are duplicate + $handler->flush(); + $this->assertSame( + $record['datetime']->getTimestamp() . ":ERROR:test\n" . + $record2['datetime']->getTimestamp() . ":CRITICAL:test\n" . + $record3['datetime']->getTimestamp() . ":CRITICAL:test\n", + file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log') + ); + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + + // clear test handler + $test->clear(); + $this->assertFalse($test->hasErrorRecords()); + $this->assertFalse($test->hasCriticalRecords()); + + // log new records, duplicate log gets GC'd at the end of this flush call + $handler->handle($record = $this->getRecord(Logger::ERROR)); + $handler->handle($record2 = $this->getRecord(Logger::CRITICAL)); + $handler->flush(); + + // log should now contain the new errors and the previous one that was recent enough + $this->assertSame( + $record3['datetime']->getTimestamp() . ":CRITICAL:test\n" . + $record['datetime']->getTimestamp() . ":ERROR:test\n" . + $record2['datetime']->getTimestamp() . ":CRITICAL:test\n", + file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log') + ); + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + public static function tearDownAfterClass() + { + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php new file mode 100644 index 0000000..d67da90 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class DoctrineCouchDBHandlerTest extends TestCase +{ + protected function setup() + { + if (!class_exists('Doctrine\CouchDB\CouchDBClient')) { + $this->markTestSkipped('The "doctrine/couchdb" package is not installed'); + } + } + + public function testHandle() + { + $client = $this->getMockBuilder('Doctrine\\CouchDB\\CouchDBClient') + ->setMethods(array('postDocument')) + ->disableOriginalConstructor() + ->getMock(); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $client->expects($this->once()) + ->method('postDocument') + ->with($expected); + + $handler = new DoctrineCouchDBHandler($client); + $handler->handle($record); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php new file mode 100644 index 0000000..f3a6968 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; + +class DynamoDbHandlerTest extends TestCase +{ + private $client; + + public function setUp() + { + if (!class_exists('Aws\DynamoDb\DynamoDbClient')) { + $this->markTestSkipped('aws/aws-sdk-php not installed'); + } + + $this->client = $this->getMockBuilder('Aws\DynamoDb\DynamoDbClient') + ->setMethods(array('formatAttributes', '__call')) + ->disableOriginalConstructor()->getMock(); + } + + public function testConstruct() + { + $this->assertInstanceOf('Monolog\Handler\DynamoDbHandler', new DynamoDbHandler($this->client, 'foo')); + } + + public function testInterface() + { + $this->assertInstanceOf('Monolog\Handler\HandlerInterface', new DynamoDbHandler($this->client, 'foo')); + } + + public function testGetFormatter() + { + $handler = new DynamoDbHandler($this->client, 'foo'); + $this->assertInstanceOf('Monolog\Formatter\ScalarFormatter', $handler->getFormatter()); + } + + public function testHandle() + { + $record = $this->getRecord(); + $formatter = $this->getMock('Monolog\Formatter\FormatterInterface'); + $formatted = array('foo' => 1, 'bar' => 2); + $handler = new DynamoDbHandler($this->client, 'foo'); + $handler->setFormatter($formatter); + + $formatter + ->expects($this->once()) + ->method('format') + ->with($record) + ->will($this->returnValue($formatted)); + $this->client + ->expects($this->once()) + ->method('formatAttributes') + ->with($this->isType('array')) + ->will($this->returnValue($formatted)); + $this->client + ->expects($this->once()) + ->method('__call') + ->with('putItem', array(array( + 'TableName' => 'foo', + 'Item' => $formatted, + ))); + + $handler->handle($record); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php new file mode 100644 index 0000000..1687074 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\TestCase; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Request; +use Elastica\Response; + +class ElasticSearchHandlerTest extends TestCase +{ + /** + * @var Client mock + */ + protected $client; + + /** + * @var array Default handler options + */ + protected $options = array( + 'index' => 'my_index', + 'type' => 'doc_type', + ); + + public function setUp() + { + // Elastica lib required + if (!class_exists("Elastica\Client")) { + $this->markTestSkipped("ruflin/elastica not installed"); + } + + // base mock Elastica Client object + $this->client = $this->getMockBuilder('Elastica\Client') + ->setMethods(array('addDocuments')) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::write + * @covers Monolog\Handler\ElasticSearchHandler::handleBatch + * @covers Monolog\Handler\ElasticSearchHandler::bulkSend + * @covers Monolog\Handler\ElasticSearchHandler::getDefaultFormatter + */ + public function testHandle() + { + // log message + $msg = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + // format expected result + $formatter = new ElasticaFormatter($this->options['index'], $this->options['type']); + $expected = array($formatter->format($msg)); + + // setup ES client mock + $this->client->expects($this->any()) + ->method('addDocuments') + ->with($expected); + + // perform tests + $handler = new ElasticSearchHandler($this->client, $this->options); + $handler->handle($msg); + $handler->handleBatch(array($msg)); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::setFormatter + */ + public function testSetFormatter() + { + $handler = new ElasticSearchHandler($this->client); + $formatter = new ElasticaFormatter('index_new', 'type_new'); + $handler->setFormatter($formatter); + $this->assertInstanceOf('Monolog\Formatter\ElasticaFormatter', $handler->getFormatter()); + $this->assertEquals('index_new', $handler->getFormatter()->getIndex()); + $this->assertEquals('type_new', $handler->getFormatter()->getType()); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::setFormatter + * @expectedException InvalidArgumentException + * @expectedExceptionMessage ElasticSearchHandler is only compatible with ElasticaFormatter + */ + public function testSetFormatterInvalid() + { + $handler = new ElasticSearchHandler($this->client); + $formatter = new NormalizerFormatter(); + $handler->setFormatter($formatter); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::__construct + * @covers Monolog\Handler\ElasticSearchHandler::getOptions + */ + public function testOptions() + { + $expected = array( + 'index' => $this->options['index'], + 'type' => $this->options['type'], + 'ignore_error' => false, + ); + $handler = new ElasticSearchHandler($this->client, $this->options); + $this->assertEquals($expected, $handler->getOptions()); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::bulkSend + * @dataProvider providerTestConnectionErrors + */ + public function testConnectionErrors($ignore, $expectedError) + { + $clientOpts = array('host' => '127.0.0.1', 'port' => 1); + $client = new Client($clientOpts); + $handlerOpts = array('ignore_error' => $ignore); + $handler = new ElasticSearchHandler($client, $handlerOpts); + + if ($expectedError) { + $this->setExpectedException($expectedError[0], $expectedError[1]); + $handler->handle($this->getRecord()); + } else { + $this->assertFalse($handler->handle($this->getRecord())); + } + } + + /** + * @return array + */ + public function providerTestConnectionErrors() + { + return array( + array(false, array('RuntimeException', 'Error sending messages to Elasticsearch')), + array(true, false), + ); + } + + /** + * Integration test using localhost Elastic Search server + * + * @covers Monolog\Handler\ElasticSearchHandler::__construct + * @covers Monolog\Handler\ElasticSearchHandler::handleBatch + * @covers Monolog\Handler\ElasticSearchHandler::bulkSend + * @covers Monolog\Handler\ElasticSearchHandler::getDefaultFormatter + */ + public function testHandleIntegration() + { + $msg = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $expected = $msg; + $expected['datetime'] = $msg['datetime']->format(\DateTime::ISO8601); + $expected['context'] = array( + 'class' => '[object] (stdClass: {})', + 'foo' => 7, + 0 => 'bar', + ); + + $client = new Client(); + $handler = new ElasticSearchHandler($client, $this->options); + try { + $handler->handleBatch(array($msg)); + } catch (\RuntimeException $e) { + $this->markTestSkipped("Cannot connect to Elastic Search server on localhost"); + } + + // check document id from ES server response + $documentId = $this->getCreatedDocId($client->getLastResponse()); + $this->assertNotEmpty($documentId, 'No elastic document id received'); + + // retrieve document source from ES and validate + $document = $this->getDocSourceFromElastic( + $client, + $this->options['index'], + $this->options['type'], + $documentId + ); + $this->assertEquals($expected, $document); + + // remove test index from ES + $client->request("/{$this->options['index']}", Request::DELETE); + } + + /** + * Return last created document id from ES response + * @param Response $response Elastica Response object + * @return string|null + */ + protected function getCreatedDocId(Response $response) + { + $data = $response->getData(); + if (!empty($data['items'][0]['create']['_id'])) { + return $data['items'][0]['create']['_id']; + } + } + + /** + * Retrieve document by id from Elasticsearch + * @param Client $client Elastica client + * @param string $index + * @param string $type + * @param string $documentId + * @return array + */ + protected function getDocSourceFromElastic(Client $client, $index, $type, $documentId) + { + $resp = $client->request("/{$index}/{$type}/{$documentId}", Request::GET); + $data = $resp->getData(); + if (!empty($data['_source'])) { + return $data['_source']; + } + + return array(); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php new file mode 100644 index 0000000..99785cb --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +function error_log() +{ + $GLOBALS['error_log'][] = func_get_args(); +} + +class ErrorLogHandlerTest extends TestCase +{ + protected function setUp() + { + $GLOBALS['error_log'] = array(); + } + + /** + * @covers Monolog\Handler\ErrorLogHandler::__construct + * @expectedException InvalidArgumentException + * @expectedExceptionMessage The given message type "42" is not supported + */ + public function testShouldNotAcceptAnInvalidTypeOnContructor() + { + new ErrorLogHandler(42); + } + + /** + * @covers Monolog\Handler\ErrorLogHandler::write + */ + public function testShouldLogMessagesUsingErrorLogFuncion() + { + $type = ErrorLogHandler::OPERATING_SYSTEM; + $handler = new ErrorLogHandler($type); + $handler->setFormatter(new LineFormatter('%channel%.%level_name%: %message% %context% %extra%', null, true)); + $handler->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + + $this->assertSame("test.ERROR: Foo\nBar\r\n\r\nBaz [] []", $GLOBALS['error_log'][0][0]); + $this->assertSame($GLOBALS['error_log'][0][1], $type); + + $handler = new ErrorLogHandler($type, Logger::DEBUG, true, true); + $handler->setFormatter(new LineFormatter(null, null, true)); + $handler->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + + $this->assertStringMatchesFormat('[%s] test.ERROR: Foo', $GLOBALS['error_log'][1][0]); + $this->assertSame($GLOBALS['error_log'][1][1], $type); + + $this->assertStringMatchesFormat('Bar', $GLOBALS['error_log'][2][0]); + $this->assertSame($GLOBALS['error_log'][2][1], $type); + + $this->assertStringMatchesFormat('Baz [] []', $GLOBALS['error_log'][3][0]); + $this->assertSame($GLOBALS['error_log'][3][1], $type); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php new file mode 100644 index 0000000..31b7686 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class FilterHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\FilterHandler::isHandling + */ + public function testIsHandling() + { + $test = new TestHandler(); + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::INFO))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::NOTICE))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::CRITICAL))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::ALERT))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::EMERGENCY))); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + * @covers Monolog\Handler\FilterHandler::setAcceptedLevels + * @covers Monolog\Handler\FilterHandler::isHandling + */ + public function testHandleProcessOnlyNeededLevels() + { + $test = new TestHandler(); + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::NOTICE)); + $this->assertTrue($test->hasNoticeRecords()); + + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $handler->handle($this->getRecord(Logger::ERROR)); + $this->assertFalse($test->hasErrorRecords()); + $handler->handle($this->getRecord(Logger::CRITICAL)); + $this->assertFalse($test->hasCriticalRecords()); + $handler->handle($this->getRecord(Logger::ALERT)); + $this->assertFalse($test->hasAlertRecords()); + $handler->handle($this->getRecord(Logger::EMERGENCY)); + $this->assertFalse($test->hasEmergencyRecords()); + + $test = new TestHandler(); + $handler = new FilterHandler($test, array(Logger::INFO, Logger::ERROR)); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::NOTICE)); + $this->assertFalse($test->hasNoticeRecords()); + $handler->handle($this->getRecord(Logger::ERROR)); + $this->assertTrue($test->hasErrorRecords()); + $handler->handle($this->getRecord(Logger::CRITICAL)); + $this->assertFalse($test->hasCriticalRecords()); + } + + /** + * @covers Monolog\Handler\FilterHandler::setAcceptedLevels + * @covers Monolog\Handler\FilterHandler::getAcceptedLevels + */ + public function testAcceptedLevelApi() + { + $test = new TestHandler(); + $handler = new FilterHandler($test); + + $levels = array(Logger::INFO, Logger::ERROR); + $handler->setAcceptedLevels($levels); + $this->assertSame($levels, $handler->getAcceptedLevels()); + + $handler->setAcceptedLevels(array('info', 'error')); + $this->assertSame($levels, $handler->getAcceptedLevels()); + + $levels = array(Logger::CRITICAL, Logger::ALERT, Logger::EMERGENCY); + $handler->setAcceptedLevels(Logger::CRITICAL, Logger::EMERGENCY); + $this->assertSame($levels, $handler->getAcceptedLevels()); + + $handler->setAcceptedLevels('critical', 'emergency'); + $this->assertSame($levels, $handler->getAcceptedLevels()); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new FilterHandler($test, Logger::DEBUG, Logger::EMERGENCY); + $handler->pushProcessor( + function ($record) { + $record['extra']['foo'] = true; + + return $record; + } + ); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + */ + public function testHandleRespectsBubble() + { + $test = new TestHandler(); + + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE, false); + $this->assertTrue($handler->handle($this->getRecord(Logger::INFO))); + $this->assertFalse($handler->handle($this->getRecord(Logger::WARNING))); + + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE, true); + $this->assertFalse($handler->handle($this->getRecord(Logger::INFO))); + $this->assertFalse($handler->handle($this->getRecord(Logger::WARNING))); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + */ + public function testHandleWithCallback() + { + $test = new TestHandler(); + $handler = new FilterHandler( + function ($record, $handler) use ($test) { + return $test; + }, Logger::INFO, Logger::NOTICE, false + ); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + * @expectedException \RuntimeException + */ + public function testHandleWithBadCallbackThrowsException() + { + $handler = new FilterHandler( + function ($record, $handler) { + return 'foo'; + } + ); + $handler->handle($this->getRecord(Logger::WARNING)); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php new file mode 100644 index 0000000..b92bf43 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy; +use Psr\Log\LogLevel; + +class FingersCrossedHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleBuffers() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->close(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 3); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleStopsBufferingAfterTrigger() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + * @covers Monolog\Handler\FingersCrossedHandler::reset + */ + public function testHandleRestartBufferingAfterReset() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->reset(); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleRestartBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::WARNING, 0, false, false); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleBufferLimit() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::WARNING, 2); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleWithCallback() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler(function ($record, $handler) use ($test) { + return $test; + }); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 3); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + * @expectedException RuntimeException + */ + public function testHandleWithBadCallbackThrowsException() + { + $handler = new FingersCrossedHandler(function ($record, $handler) { + return 'foo'; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::isHandling + */ + public function testIsHandlingAlways() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::ERROR); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated + */ + public function testErrorLevelActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated + */ + public function testErrorLevelActivationStrategyWithPsrLevel() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy('warning')); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testOverrideActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy('warning')); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->activate(); + $this->assertTrue($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated + */ + public function testChannelLevelActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy(Logger::ERROR, array('othertest' => Logger::DEBUG))); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $record = $this->getRecord(Logger::DEBUG); + $record['channel'] = 'othertest'; + $handler->handle($record); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated + */ + public function testChannelLevelActivationStrategyWithPsrLevels() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy('error', array('othertest' => 'debug'))); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $record = $this->getRecord(Logger::DEBUG); + $record['channel'] = 'othertest'; + $handler->handle($record); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::INFO); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::close + */ + public function testPassthruOnClose() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING), 0, true, true, Logger::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertFalse($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::close + */ + public function testPsrLevelPassthruOnClose() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING), 0, true, true, LogLevel::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertFalse($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php new file mode 100644 index 0000000..0eb10a6 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\FirePHPHandler + */ +class FirePHPHandlerTest extends TestCase +{ + public function setUp() + { + TestFirePHPHandler::reset(); + $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; FirePHP/1.0'; + } + + public function testHeaders() + { + $handler = new TestFirePHPHandler; + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3', + 'X-Wf-1-1-1-1' => 'test', + 'X-Wf-1-1-1-2' => 'test', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testConcurrentHandlers() + { + $handler = new TestFirePHPHandler; + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $handler2 = new TestFirePHPHandler; + $handler2->setFormatter($this->getIdentityFormatter()); + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3', + 'X-Wf-1-1-1-1' => 'test', + 'X-Wf-1-1-1-2' => 'test', + ); + + $expected2 = array( + 'X-Wf-1-1-1-3' => 'test', + 'X-Wf-1-1-1-4' => 'test', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + $this->assertEquals($expected2, $handler2->getHeaders()); + } +} + +class TestFirePHPHandler extends FirePHPHandler +{ + protected $headers = array(); + + public static function reset() + { + self::$initialized = false; + self::$sendHeaders = true; + self::$messageIndex = 1; + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep b/src/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php new file mode 100644 index 0000000..91cdd31 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.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 Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use Monolog\TestCase; + +/** + * @coversDefaultClass \Monolog\Handler\FleepHookHandler + */ +class FleepHookHandlerTest extends TestCase +{ + /** + * Default token to use in tests + */ + const TOKEN = '123abc'; + + /** + * @var FleepHookHandler + */ + private $handler; + + public function setUp() + { + parent::setUp(); + + if (!extension_loaded('openssl')) { + $this->markTestSkipped('This test requires openssl extension to run'); + } + + // Create instances of the handler and logger for convenience + $this->handler = new FleepHookHandler(self::TOKEN); + } + + /** + * @covers ::__construct + */ + public function testConstructorSetsExpectedDefaults() + { + $this->assertEquals(Logger::DEBUG, $this->handler->getLevel()); + $this->assertEquals(true, $this->handler->getBubble()); + } + + /** + * @covers ::getDefaultFormatter + */ + public function testHandlerUsesLineFormatterWhichIgnoresEmptyArrays() + { + $record = array( + 'message' => 'msg', + 'context' => array(), + 'level' => Logger::DEBUG, + 'level_name' => Logger::getLevelName(Logger::DEBUG), + 'channel' => 'channel', + 'datetime' => new \DateTime(), + 'extra' => array(), + ); + + $expectedFormatter = new LineFormatter(null, null, true, true); + $expected = $expectedFormatter->format($record); + + $handlerFormatter = $this->handler->getFormatter(); + $actual = $handlerFormatter->format($record); + + $this->assertEquals($expected, $actual, 'Empty context and extra arrays should not be rendered'); + } + + /** + * @covers ::__construct + */ + public function testConnectionStringisConstructedCorrectly() + { + $this->assertEquals('ssl://' . FleepHookHandler::FLEEP_HOST . ':443', $this->handler->getConnectionString()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php new file mode 100644 index 0000000..4b120d5 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FlowdockFormatter; +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Dominik Liebler + * @see https://www.hipchat.com/docs/api + */ +class FlowdockHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $res; + + /** + * @var FlowdockHandler + */ + private $handler; + + public function setUp() + { + if (!extension_loaded('openssl')) { + $this->markTestSkipped('This test requires openssl to run'); + } + } + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/messages\/team_inbox\/.* HTTP\/1.1\\r\\nHost: api.flowdock.com\\r\\nContent-Type: application\/json\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/"source":"test_source"/', $content); + $this->assertRegexp('/"from_address":"source@test\.com"/', $content); + } + + private function createHandler($token = 'myToken') + { + $constructorArgs = array($token, Logger::DEBUG); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\FlowdockHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter(new FlowdockFormatter('test_source', 'source@test.com')); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php new file mode 100644 index 0000000..9d007b1 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.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 Monolog\Handler; + +use Gelf\Message; +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +class GelfHandlerLegacyTest extends TestCase +{ + public function setUp() + { + if (!class_exists('Gelf\MessagePublisher') || !class_exists('Gelf\Message')) { + $this->markTestSkipped("mlehner/gelf-php not installed"); + } + + require_once __DIR__ . '/GelfMockMessagePublisher.php'; + } + + /** + * @covers Monolog\Handler\GelfHandler::__construct + */ + public function testConstruct() + { + $handler = new GelfHandler($this->getMessagePublisher()); + $this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler); + } + + protected function getHandler($messagePublisher) + { + $handler = new GelfHandler($messagePublisher); + + return $handler; + } + + protected function getMessagePublisher() + { + return new GelfMockMessagePublisher('localhost'); + } + + public function testDebug() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $record = $this->getRecord(Logger::DEBUG, "A test debug message"); + $handler->handle($record); + + $this->assertEquals(7, $messagePublisher->lastMessage->getLevel()); + $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); + $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); + } + + public function testWarning() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $handler->handle($record); + + $this->assertEquals(4, $messagePublisher->lastMessage->getLevel()); + $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); + $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); + } + + public function testInjectedGelfMessageFormatter() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX')); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $record['extra']['blarg'] = 'yep'; + $record['context']['from'] = 'logger'; + $handler->handle($record); + + $this->assertEquals('mysystem', $messagePublisher->lastMessage->getHost()); + $this->assertArrayHasKey('_EXTblarg', $messagePublisher->lastMessage->toArray()); + $this->assertArrayHasKey('_CTXfrom', $messagePublisher->lastMessage->toArray()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php new file mode 100644 index 0000000..8cdd64f --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\Message; +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +class GelfHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists('Gelf\Publisher') || !class_exists('Gelf\Message')) { + $this->markTestSkipped("graylog2/gelf-php not installed"); + } + } + + /** + * @covers Monolog\Handler\GelfHandler::__construct + */ + public function testConstruct() + { + $handler = new GelfHandler($this->getMessagePublisher()); + $this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler); + } + + protected function getHandler($messagePublisher) + { + $handler = new GelfHandler($messagePublisher); + + return $handler; + } + + protected function getMessagePublisher() + { + return $this->getMock('Gelf\Publisher', array('publish'), array(), '', false); + } + + public function testDebug() + { + $record = $this->getRecord(Logger::DEBUG, "A test debug message"); + $expectedMessage = new Message(); + $expectedMessage + ->setLevel(7) + ->setFacility("test") + ->setShortMessage($record['message']) + ->setTimestamp($record['datetime']) + ; + + $messagePublisher = $this->getMessagePublisher(); + $messagePublisher->expects($this->once()) + ->method('publish') + ->with($expectedMessage); + + $handler = $this->getHandler($messagePublisher); + + $handler->handle($record); + } + + public function testWarning() + { + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $expectedMessage = new Message(); + $expectedMessage + ->setLevel(4) + ->setFacility("test") + ->setShortMessage($record['message']) + ->setTimestamp($record['datetime']) + ; + + $messagePublisher = $this->getMessagePublisher(); + $messagePublisher->expects($this->once()) + ->method('publish') + ->with($expectedMessage); + + $handler = $this->getHandler($messagePublisher); + + $handler->handle($record); + } + + public function testInjectedGelfMessageFormatter() + { + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $record['extra']['blarg'] = 'yep'; + $record['context']['from'] = 'logger'; + + $expectedMessage = new Message(); + $expectedMessage + ->setLevel(4) + ->setFacility("test") + ->setHost("mysystem") + ->setShortMessage($record['message']) + ->setTimestamp($record['datetime']) + ->setAdditional("EXTblarg", 'yep') + ->setAdditional("CTXfrom", 'logger') + ; + + $messagePublisher = $this->getMessagePublisher(); + $messagePublisher->expects($this->once()) + ->method('publish') + ->with($expectedMessage); + + $handler = $this->getHandler($messagePublisher); + $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX')); + $handler->handle($record); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php new file mode 100644 index 0000000..873d92f --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\MessagePublisher; +use Gelf\Message; + +class GelfMockMessagePublisher extends MessagePublisher +{ + public function publish(Message $message) + { + $this->lastMessage = $message; + } + + public $lastMessage = null; +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php new file mode 100644 index 0000000..a1b8617 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class GroupHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\GroupHandler::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorOnlyTakesHandler() + { + new GroupHandler(array(new TestHandler(), "foo")); + } + + /** + * @covers Monolog\Handler\GroupHandler::__construct + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandle() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\GroupHandler::handleBatch + */ + public function testHandleBatch() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\GroupHandler::isHandling + */ + public function testIsHandling() + { + $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING)); + $handler = new GroupHandler($testHandlers); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new GroupHandler(array($test)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandleBatchUsesProcessors() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + $this->assertTrue($records[1]['extra']['foo']); + } + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php new file mode 100644 index 0000000..d8d0452 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +/** + * @author Alexey Karapetov + */ +class HandlerWrapperTest extends TestCase +{ + /** + * @var HandlerWrapper + */ + private $wrapper; + + private $handler; + + public function setUp() + { + parent::setUp(); + $this->handler = $this->getMock('Monolog\\Handler\\HandlerInterface'); + $this->wrapper = new HandlerWrapper($this->handler); + } + + /** + * @return array + */ + public function trueFalseDataProvider() + { + return array( + array(true), + array(false), + ); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testIsHandling($result) + { + $record = $this->getRecord(); + $this->handler->expects($this->once()) + ->method('isHandling') + ->with($record) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->isHandling($record)); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testHandle($result) + { + $record = $this->getRecord(); + $this->handler->expects($this->once()) + ->method('handle') + ->with($record) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->handle($record)); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testHandleBatch($result) + { + $records = $this->getMultipleRecords(); + $this->handler->expects($this->once()) + ->method('handleBatch') + ->with($records) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->handleBatch($records)); + } + + public function testPushProcessor() + { + $processor = function () {}; + $this->handler->expects($this->once()) + ->method('pushProcessor') + ->with($processor); + + $this->assertEquals($this->wrapper, $this->wrapper->pushProcessor($processor)); + } + + public function testPopProcessor() + { + $processor = function () {}; + $this->handler->expects($this->once()) + ->method('popProcessor') + ->willReturn($processor); + + $this->assertEquals($processor, $this->wrapper->popProcessor()); + } + + public function testSetFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $this->handler->expects($this->once()) + ->method('setFormatter') + ->with($formatter); + + $this->assertEquals($this->wrapper, $this->wrapper->setFormatter($formatter)); + } + + public function testGetFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $this->handler->expects($this->once()) + ->method('getFormatter') + ->willReturn($formatter); + + $this->assertEquals($formatter, $this->wrapper->getFormatter()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php new file mode 100644 index 0000000..52dc9da --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandlerTest extends TestCase +{ + private $res; + /** @var HipChatHandler */ + private $handler; + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: api.hipchat.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testWriteCustomHostHeader() + { + $this->createHandler('myToken', 'room1', 'Monolog', true, 'hipchat.foo.bar'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testWriteV2() + { + $this->createHandler('myToken', 'room1', 'Monolog', false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room1\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testWriteV2Notify() + { + $this->createHandler('myToken', 'room1', 'Monolog', true, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room1\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testRoomSpaces() + { + $this->createHandler('myToken', 'room name', 'Monolog', false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room%20name\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=Monolog$/', $content); + } + + public function testWriteContentV1WithoutName() + { + $this->createHandler('myToken', 'room1', null, false, 'hipchat.foo.bar', 'v1'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=$/', $content); + + return $content; + } + + /** + * @depends testWriteCustomHostHeader + */ + public function testWriteContentNotify($content) + { + $this->assertRegexp('/notify=1&message=test1&message_format=text&color=red&room_id=room1&from=Monolog$/', $content); + } + + /** + * @depends testWriteV2 + */ + public function testWriteContentV2($content) + { + $this->assertRegexp('/notify=false&message=test1&message_format=text&color=red&from=Monolog$/', $content); + } + + /** + * @depends testWriteV2Notify + */ + public function testWriteContentV2Notify($content) + { + $this->assertRegexp('/notify=true&message=test1&message_format=text&color=red&from=Monolog$/', $content); + } + + public function testWriteContentV2WithoutName() + { + $this->createHandler('myToken', 'room1', null, false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/notify=false&message=test1&message_format=text&color=red$/', $content); + + return $content; + } + + public function testWriteWithComplexMessage() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content); + } + + public function testWriteTruncatesLongMessage() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, str_repeat('abcde', 2000))); + fseek($this->res, 0); + $content = fread($this->res, 12000); + + $this->assertRegexp('/message='.str_repeat('abcde', 1900).'\+%5Btruncated%5D/', $content); + } + + /** + * @dataProvider provideLevelColors + */ + public function testWriteWithErrorLevelsAndColors($level, $expectedColor) + { + $this->createHandler(); + $this->handler->handle($this->getRecord($level, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/color='.$expectedColor.'/', $content); + } + + public function provideLevelColors() + { + return array( + array(Logger::DEBUG, 'gray'), + array(Logger::INFO, 'green'), + array(Logger::WARNING, 'yellow'), + array(Logger::ERROR, 'red'), + array(Logger::CRITICAL, 'red'), + array(Logger::ALERT, 'red'), + array(Logger::EMERGENCY,'red'), + array(Logger::NOTICE, 'green'), + ); + } + + /** + * @dataProvider provideBatchRecords + */ + public function testHandleBatch($records, $expectedColor) + { + $this->createHandler(); + + $this->handler->handleBatch($records); + + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/color='.$expectedColor.'/', $content); + } + + public function provideBatchRecords() + { + return array( + array( + array( + array('level' => Logger::WARNING, 'message' => 'Oh bugger!', 'level_name' => 'warning', 'datetime' => new \DateTime()), + array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()), + array('level' => Logger::CRITICAL, 'message' => 'Everything is broken!', 'level_name' => 'critical', 'datetime' => new \DateTime()), + ), + 'red', + ), + array( + array( + array('level' => Logger::WARNING, 'message' => 'Oh bugger!', 'level_name' => 'warning', 'datetime' => new \DateTime()), + array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()), + ), + 'yellow', + ), + array( + array( + array('level' => Logger::DEBUG, 'message' => 'Just debugging.', 'level_name' => 'debug', 'datetime' => new \DateTime()), + array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()), + ), + 'green', + ), + array( + array( + array('level' => Logger::DEBUG, 'message' => 'Just debugging.', 'level_name' => 'debug', 'datetime' => new \DateTime()), + ), + 'gray', + ), + ); + } + + private function createHandler($token = 'myToken', $room = 'room1', $name = 'Monolog', $notify = false, $host = 'api.hipchat.com', $version = 'v1') + { + $constructorArgs = array($token, $room, $name, $notify, Logger::DEBUG, true, true, 'text', $host, $version); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\HipChatHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCreateWithTooLongName() + { + $hipChatHandler = new HipChatHandler('token', 'room', 'SixteenCharsHere'); + } + + public function testCreateWithTooLongNameV2() + { + // creating a handler with too long of a name but using the v2 api doesn't matter. + $hipChatHandler = new HipChatHandler('token', 'room', 'SixteenCharsHere', false, Logger::CRITICAL, true, true, 'test', 'api.hipchat.com', 'v2'); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php new file mode 100644 index 0000000..b2deb40 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $res; + + /** + * @var LogEntriesHandler + */ + private $handler; + + public function testWriteContent() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Critical write test')); + + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] test.CRITICAL: Critical write test/', $content); + } + + public function testWriteBatchContent() + { + $records = array( + $this->getRecord(), + $this->getRecord(), + $this->getRecord(), + ); + $this->createHandler(); + $this->handler->handleBatch($records); + + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/(testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] .* \[\] \[\]\n){3}/', $content); + } + + private function createHandler() + { + $useSSL = extension_loaded('openssl'); + $args = array('testToken', $useSSL, Logger::DEBUG, true); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\LogEntriesHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $args + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php new file mode 100644 index 0000000..6754f3d --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class MailHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\MailHandler::handleBatch + */ + public function testHandleBatch() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once()) + ->method('formatBatch'); // Each record is formatted + + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + $handler->expects($this->once()) + ->method('send'); + $handler->expects($this->never()) + ->method('write'); // write is for individual records + + $handler->setFormatter($formatter); + + $handler->handleBatch($this->getMultipleRecords()); + } + + /** + * @covers Monolog\Handler\MailHandler::handleBatch + */ + public function testHandleBatchNotSendsMailIfMessagesAreBelowLevel() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + ); + + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + $handler->expects($this->never()) + ->method('send'); + $handler->setLevel(Logger::ERROR); + + $handler->handleBatch($records); + } + + /** + * @covers Monolog\Handler\MailHandler::write + */ + public function testHandle() + { + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + + $record = $this->getRecord(); + $records = array($record); + $records[0]['formatted'] = '['.$record['datetime']->format('Y-m-d H:i:s').'] test.WARNING: test [] []'."\n"; + + $handler->expects($this->once()) + ->method('send') + ->with($records[0]['formatted'], $records); + + $handler->handle($record); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php new file mode 100644 index 0000000..a083322 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.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 Monolog\Handler; + +use Raven_Client; + +class MockRavenClient extends Raven_Client +{ + public function capture($data, $stack, $vars = null) + { + $data = array_merge($this->get_user_data(), $data); + $this->lastData = $data; + $this->lastStack = $stack; + } + + public $lastData; + public $lastStack; +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php new file mode 100644 index 0000000..0fdef63 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class MongoDBHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidMongo() + { + new MongoDBHandler(new \stdClass(), 'DB', 'Collection'); + } + + public function testHandle() + { + $mongo = $this->getMock('Mongo', array('selectCollection'), array(), '', false); + $collection = $this->getMock('stdClass', array('save')); + + $mongo->expects($this->once()) + ->method('selectCollection') + ->with('DB', 'Collection') + ->will($this->returnValue($collection)); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $collection->expects($this->once()) + ->method('save') + ->with($expected); + + $handler = new MongoDBHandler($mongo, 'DB', 'Collection'); + $handler->handle($record); + } +} + +if (!class_exists('Mongo')) { + class Mongo + { + public function selectCollection() + { + } + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php new file mode 100644 index 0000000..ddf545d --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use InvalidArgumentException; + +function mail($to, $subject, $message, $additional_headers = null, $additional_parameters = null) +{ + $GLOBALS['mail'][] = func_get_args(); +} + +class NativeMailerHandlerTest extends TestCase +{ + protected function setUp() + { + $GLOBALS['mail'] = array(); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', "receiver@example.org\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->addHeader("Content-Type: text/html\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterArrayHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->addHeader(array("Content-Type: text/html\r\nFrom: faked@attacker.org")); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterContentTypeInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->setContentType("text/html\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterEncodingInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->setEncoding("utf-8\r\nFrom: faked@attacker.org"); + } + + public function testSend() + { + $to = 'spammer@example.org'; + $subject = 'dear victim'; + $from = 'receiver@example.org'; + + $mailer = new NativeMailerHandler($to, $subject, $from); + $mailer->handleBatch(array()); + + // batch is empty, nothing sent + $this->assertEmpty($GLOBALS['mail']); + + // non-empty batch + $mailer->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + $this->assertNotEmpty($GLOBALS['mail']); + $this->assertInternalType('array', $GLOBALS['mail']); + $this->assertArrayHasKey('0', $GLOBALS['mail']); + $params = $GLOBALS['mail'][0]; + $this->assertCount(5, $params); + $this->assertSame($to, $params[0]); + $this->assertSame($subject, $params[1]); + $this->assertStringEndsWith(" test.ERROR: Foo Bar Baz [] []\n", $params[2]); + $this->assertSame("From: $from\r\nContent-type: text/plain; charset=utf-8\r\n", $params[3]); + $this->assertSame('', $params[4]); + } + + public function testMessageSubjectFormatting() + { + $mailer = new NativeMailerHandler('to@example.org', 'Alert: %level_name% %message%', 'from@example.org'); + $mailer->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + $this->assertNotEmpty($GLOBALS['mail']); + $this->assertInternalType('array', $GLOBALS['mail']); + $this->assertArrayHasKey('0', $GLOBALS['mail']); + $params = $GLOBALS['mail'][0]; + $this->assertCount(5, $params); + $this->assertSame('Alert: ERROR Foo Bar Baz', $params[1]); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php new file mode 100644 index 0000000..4d3a615 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.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 Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\TestCase; +use Monolog\Logger; + +class NewRelicHandlerTest extends TestCase +{ + public static $appname; + public static $customParameters; + public static $transactionName; + + public function setUp() + { + self::$appname = null; + self::$customParameters = array(); + self::$transactionName = null; + } + + /** + * @expectedException Monolog\Handler\MissingExtensionException + */ + public function testThehandlerThrowsAnExceptionIfTheNRExtensionIsNotLoaded() + { + $handler = new StubNewRelicHandlerWithoutExtension(); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testThehandlerCanHandleTheRecord() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testThehandlerCanAddContextParamsToTheNewRelicTrace() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('a' => 'b'))); + $this->assertEquals(array('context_a' => 'b'), self::$customParameters); + } + + public function testThehandlerCanAddExplodedContextParamsToTheNewRelicTrace() + { + $handler = new StubNewRelicHandler(Logger::ERROR, true, self::$appname, true); + $handler->handle($this->getRecord( + Logger::ERROR, + 'log message', + array('a' => array('key1' => 'value1', 'key2' => 'value2')) + )); + $this->assertEquals( + array('context_a_key1' => 'value1', 'context_a_key2' => 'value2'), + self::$customParameters + ); + } + + public function testThehandlerCanAddExtraParamsToTheNewRelicTrace() + { + $record = $this->getRecord(Logger::ERROR, 'log message'); + $record['extra'] = array('c' => 'd'); + + $handler = new StubNewRelicHandler(); + $handler->handle($record); + + $this->assertEquals(array('extra_c' => 'd'), self::$customParameters); + } + + public function testThehandlerCanAddExplodedExtraParamsToTheNewRelicTrace() + { + $record = $this->getRecord(Logger::ERROR, 'log message'); + $record['extra'] = array('c' => array('key1' => 'value1', 'key2' => 'value2')); + + $handler = new StubNewRelicHandler(Logger::ERROR, true, self::$appname, true); + $handler->handle($record); + + $this->assertEquals( + array('extra_c_key1' => 'value1', 'extra_c_key2' => 'value2'), + self::$customParameters + ); + } + + public function testThehandlerCanAddExtraContextAndParamsToTheNewRelicTrace() + { + $record = $this->getRecord(Logger::ERROR, 'log message', array('a' => 'b')); + $record['extra'] = array('c' => 'd'); + + $handler = new StubNewRelicHandler(); + $handler->handle($record); + + $expected = array( + 'context_a' => 'b', + 'extra_c' => 'd', + ); + + $this->assertEquals($expected, self::$customParameters); + } + + public function testThehandlerCanHandleTheRecordsFormattedUsingTheLineFormatter() + { + $handler = new StubNewRelicHandler(); + $handler->setFormatter(new LineFormatter()); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testTheAppNameIsNullByDefault() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals(null, self::$appname); + } + + public function testTheAppNameCanBeInjectedFromtheConstructor() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, 'myAppName'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals('myAppName', self::$appname); + } + + public function testTheAppNameCanBeOverriddenFromEachLog() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, 'myAppName'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('appname' => 'logAppName'))); + + $this->assertEquals('logAppName', self::$appname); + } + + public function testTheTransactionNameIsNullByDefault() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals(null, self::$transactionName); + } + + public function testTheTransactionNameCanBeInjectedFromTheConstructor() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, null, false, 'myTransaction'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals('myTransaction', self::$transactionName); + } + + public function testTheTransactionNameCanBeOverriddenFromEachLog() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, null, false, 'myTransaction'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('transaction_name' => 'logTransactName'))); + + $this->assertEquals('logTransactName', self::$transactionName); + } +} + +class StubNewRelicHandlerWithoutExtension extends NewRelicHandler +{ + protected function isNewRelicEnabled() + { + return false; + } +} + +class StubNewRelicHandler extends NewRelicHandler +{ + protected function isNewRelicEnabled() + { + return true; + } +} + +function newrelic_notice_error() +{ + return true; +} + +function newrelic_set_appname($appname) +{ + return NewRelicHandlerTest::$appname = $appname; +} + +function newrelic_name_transaction($transactionName) +{ + return NewRelicHandlerTest::$transactionName = $transactionName; +} + +function newrelic_add_custom_parameter($key, $value) +{ + NewRelicHandlerTest::$customParameters[$key] = $value; + + return true; +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php new file mode 100644 index 0000000..292df78 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\NullHandler::handle + */ +class NullHandlerTest extends TestCase +{ + public function testHandle() + { + $handler = new NullHandler(); + $this->assertTrue($handler->handle($this->getRecord())); + } + + public function testHandleLowerLevelRecord() + { + $handler = new NullHandler(Logger::WARNING); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php new file mode 100644 index 0000000..152573e --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.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 Monolog\Handler; + +use Exception; +use Monolog\ErrorHandler; +use Monolog\Logger; +use Monolog\TestCase; +use PhpConsole\Connector; +use PhpConsole\Dispatcher\Debug as DebugDispatcher; +use PhpConsole\Dispatcher\Errors as ErrorDispatcher; +use PhpConsole\Handler; +use PHPUnit_Framework_MockObject_MockObject; + +/** + * @covers Monolog\Handler\PHPConsoleHandler + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + */ +class PHPConsoleHandlerTest extends TestCase +{ + /** @var Connector|PHPUnit_Framework_MockObject_MockObject */ + protected $connector; + /** @var DebugDispatcher|PHPUnit_Framework_MockObject_MockObject */ + protected $debugDispatcher; + /** @var ErrorDispatcher|PHPUnit_Framework_MockObject_MockObject */ + protected $errorDispatcher; + + protected function setUp() + { + if (!class_exists('PhpConsole\Connector')) { + $this->markTestSkipped('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + $this->connector = $this->initConnectorMock(); + + $this->debugDispatcher = $this->initDebugDispatcherMock($this->connector); + $this->connector->setDebugDispatcher($this->debugDispatcher); + + $this->errorDispatcher = $this->initErrorDispatcherMock($this->connector); + $this->connector->setErrorsDispatcher($this->errorDispatcher); + } + + protected function initDebugDispatcherMock(Connector $connector) + { + return $this->getMockBuilder('PhpConsole\Dispatcher\Debug') + ->disableOriginalConstructor() + ->setMethods(array('dispatchDebug')) + ->setConstructorArgs(array($connector, $connector->getDumper())) + ->getMock(); + } + + protected function initErrorDispatcherMock(Connector $connector) + { + return $this->getMockBuilder('PhpConsole\Dispatcher\Errors') + ->disableOriginalConstructor() + ->setMethods(array('dispatchError', 'dispatchException')) + ->setConstructorArgs(array($connector, $connector->getDumper())) + ->getMock(); + } + + protected function initConnectorMock() + { + $connector = $this->getMockBuilder('PhpConsole\Connector') + ->disableOriginalConstructor() + ->setMethods(array( + 'sendMessage', + 'onShutDown', + 'isActiveClient', + 'setSourcesBasePath', + 'setServerEncoding', + 'setPassword', + 'enableSslOnlyMode', + 'setAllowedIpMasks', + 'setHeadersLimit', + 'startEvalRequestsListener', + )) + ->getMock(); + + $connector->expects($this->any()) + ->method('isActiveClient') + ->will($this->returnValue(true)); + + return $connector; + } + + protected function getHandlerDefaultOption($name) + { + $handler = new PHPConsoleHandler(array(), $this->connector); + $options = $handler->getOptions(); + + return $options[$name]; + } + + protected function initLogger($handlerOptions = array(), $level = Logger::DEBUG) + { + return new Logger('test', array( + new PHPConsoleHandler($handlerOptions, $this->connector, $level), + )); + } + + public function testInitWithDefaultConnector() + { + $handler = new PHPConsoleHandler(); + $this->assertEquals(spl_object_hash(Connector::getInstance()), spl_object_hash($handler->getConnector())); + } + + public function testInitWithCustomConnector() + { + $handler = new PHPConsoleHandler(array(), $this->connector); + $this->assertEquals(spl_object_hash($this->connector), spl_object_hash($handler->getConnector())); + } + + public function testDebug() + { + $this->debugDispatcher->expects($this->once())->method('dispatchDebug')->with($this->equalTo('test')); + $this->initLogger()->addDebug('test'); + } + + public function testDebugContextInMessage() + { + $message = 'test'; + $tag = 'tag'; + $context = array($tag, 'custom' => mt_rand()); + $expectedMessage = $message . ' ' . json_encode(array_slice($context, 1)); + $this->debugDispatcher->expects($this->once())->method('dispatchDebug')->with( + $this->equalTo($expectedMessage), + $this->equalTo($tag) + ); + $this->initLogger()->addDebug($message, $context); + } + + public function testDebugTags($tagsContextKeys = null) + { + $expectedTags = mt_rand(); + $logger = $this->initLogger($tagsContextKeys ? array('debugTagsKeysInContext' => $tagsContextKeys) : array()); + if (!$tagsContextKeys) { + $tagsContextKeys = $this->getHandlerDefaultOption('debugTagsKeysInContext'); + } + foreach ($tagsContextKeys as $key) { + $debugDispatcher = $this->initDebugDispatcherMock($this->connector); + $debugDispatcher->expects($this->once())->method('dispatchDebug')->with( + $this->anything(), + $this->equalTo($expectedTags) + ); + $this->connector->setDebugDispatcher($debugDispatcher); + $logger->addDebug('test', array($key => $expectedTags)); + } + } + + public function testError($classesPartialsTraceIgnore = null) + { + $code = E_USER_NOTICE; + $message = 'message'; + $file = __FILE__; + $line = __LINE__; + $this->errorDispatcher->expects($this->once())->method('dispatchError')->with( + $this->equalTo($code), + $this->equalTo($message), + $this->equalTo($file), + $this->equalTo($line), + $classesPartialsTraceIgnore ?: $this->equalTo($this->getHandlerDefaultOption('classesPartialsTraceIgnore')) + ); + $errorHandler = ErrorHandler::register($this->initLogger($classesPartialsTraceIgnore ? array('classesPartialsTraceIgnore' => $classesPartialsTraceIgnore) : array()), false); + $errorHandler->registerErrorHandler(array(), false, E_USER_WARNING); + $errorHandler->handleError($code, $message, $file, $line); + } + + public function testException() + { + $e = new Exception(); + $this->errorDispatcher->expects($this->once())->method('dispatchException')->with( + $this->equalTo($e) + ); + $handler = $this->initLogger(); + $handler->log( + \Psr\Log\LogLevel::ERROR, + sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), + array('exception' => $e) + ); + } + + /** + * @expectedException Exception + */ + public function testWrongOptionsThrowsException() + { + new PHPConsoleHandler(array('xxx' => 1)); + } + + public function testOptionEnabled() + { + $this->debugDispatcher->expects($this->never())->method('dispatchDebug'); + $this->initLogger(array('enabled' => false))->addDebug('test'); + } + + public function testOptionClassesPartialsTraceIgnore() + { + $this->testError(array('Class', 'Namespace\\')); + } + + public function testOptionDebugTagsKeysInContext() + { + $this->testDebugTags(array('key1', 'key2')); + } + + public function testOptionUseOwnErrorsAndExceptionsHandler() + { + $this->initLogger(array('useOwnErrorsHandler' => true, 'useOwnExceptionsHandler' => true)); + $this->assertEquals(array(Handler::getInstance(), 'handleError'), set_error_handler(function () { + })); + $this->assertEquals(array(Handler::getInstance(), 'handleException'), set_exception_handler(function () { + })); + } + + public static function provideConnectorMethodsOptionsSets() + { + return array( + array('sourcesBasePath', 'setSourcesBasePath', __DIR__), + array('serverEncoding', 'setServerEncoding', 'cp1251'), + array('password', 'setPassword', '******'), + array('enableSslOnlyMode', 'enableSslOnlyMode', true, false), + array('ipMasks', 'setAllowedIpMasks', array('127.0.0.*')), + array('headersLimit', 'setHeadersLimit', 2500), + array('enableEvalListener', 'startEvalRequestsListener', true, false), + ); + } + + /** + * @dataProvider provideConnectorMethodsOptionsSets + */ + public function testOptionCallsConnectorMethod($option, $method, $value, $isArgument = true) + { + $expectCall = $this->connector->expects($this->once())->method($method); + if ($isArgument) { + $expectCall->with($value); + } + new PHPConsoleHandler(array($option => $value), $this->connector); + } + + public function testOptionDetectDumpTraceAndSource() + { + new PHPConsoleHandler(array('detectDumpTraceAndSource' => true), $this->connector); + $this->assertTrue($this->connector->getDebugDispatcher()->detectTraceAndSource); + } + + public static function provideDumperOptionsValues() + { + return array( + array('dumperLevelLimit', 'levelLimit', 1001), + array('dumperItemsCountLimit', 'itemsCountLimit', 1002), + array('dumperItemSizeLimit', 'itemSizeLimit', 1003), + array('dumperDumpSizeLimit', 'dumpSizeLimit', 1004), + array('dumperDetectCallbacks', 'detectCallbacks', true), + ); + } + + /** + * @dataProvider provideDumperOptionsValues + */ + public function testDumperOptions($option, $dumperProperty, $value) + { + new PHPConsoleHandler(array($option => $value), $this->connector); + $this->assertEquals($value, $this->connector->getDumper()->$dumperProperty); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php new file mode 100644 index 0000000..64eaab1 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\PsrHandler::handle + */ +class PsrHandlerTest extends TestCase +{ + public function logLevelProvider() + { + $levels = array(); + $monologLogger = new Logger(''); + + foreach ($monologLogger->getLevels() as $levelName => $level) { + $levels[] = array($levelName, $level); + } + + return $levels; + } + + /** + * @dataProvider logLevelProvider + */ + public function testHandlesAllLevels($levelName, $level) + { + $message = 'Hello, world! ' . $level; + $context = array('foo' => 'bar', 'level' => $level); + + $psrLogger = $this->getMock('Psr\Log\NullLogger'); + $psrLogger->expects($this->once()) + ->method('log') + ->with(strtolower($levelName), $message, $context); + + $handler = new PsrHandler($psrLogger); + $handler->handle(array('level' => $level, 'level_name' => $levelName, 'message' => $message, 'context' => $context)); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php new file mode 100644 index 0000000..56df474 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * Almost all examples (expected header, titles, messages) taken from + * https://www.pushover.net/api + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandlerTest extends TestCase +{ + private $res; + private $handler; + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/1\/messages.json HTTP\/1.1\\r\\nHost: api.pushover.net\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}$/', $content); + } + + public function testWriteWithComplexTitle() + { + $this->createHandler('myToken', 'myUser', 'Backup finished - SQL1'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/title=Backup\+finished\+-\+SQL1/', $content); + } + + public function testWriteWithComplexMessage() + { + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content); + } + + public function testWriteWithTooLongMessage() + { + $message = str_pad('test', 520, 'a'); + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, $message)); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $expectedMessage = substr($message, 0, 505); + + $this->assertRegexp('/message=' . $expectedMessage . '&title/', $content); + } + + public function testWriteWithHighPriority() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=1$/', $content); + } + + public function testWriteWithEmergencyPriority() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200$/', $content); + } + + public function testWriteToMultipleUsers() + { + $this->createHandler('myToken', array('userA', 'userB')); + $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=userA&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200POST/', $content); + $this->assertRegexp('/token=myToken&user=userB&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200$/', $content); + } + + private function createHandler($token = 'myToken', $user = 'myUser', $title = 'Monolog') + { + $constructorArgs = array($token, $user, $title); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\PushoverHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php new file mode 100644 index 0000000..574077d --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php @@ -0,0 +1,253 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +class RavenHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists('Raven_Client')) { + $this->markTestSkipped('raven/raven not installed'); + } + + require_once __DIR__ . '/MockRavenClient.php'; + } + + /** + * @covers Monolog\Handler\RavenHandler::__construct + */ + public function testConstruct() + { + $handler = new RavenHandler($this->getRavenClient()); + $this->assertInstanceOf('Monolog\Handler\RavenHandler', $handler); + } + + protected function getHandler($ravenClient) + { + $handler = new RavenHandler($ravenClient); + + return $handler; + } + + protected function getRavenClient() + { + $dsn = 'http://43f6017361224d098402974103bfc53d:a6a0538fc2934ba2bed32e08741b2cd3@marca.python.live.cheggnet.com:9000/1'; + + return new MockRavenClient($dsn); + } + + public function testDebug() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $record = $this->getRecord(Logger::DEBUG, 'A test debug message'); + $handler->handle($record); + + $this->assertEquals($ravenClient::DEBUG, $ravenClient->lastData['level']); + $this->assertContains($record['message'], $ravenClient->lastData['message']); + } + + public function testWarning() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $record = $this->getRecord(Logger::WARNING, 'A test warning message'); + $handler->handle($record); + + $this->assertEquals($ravenClient::WARNING, $ravenClient->lastData['level']); + $this->assertContains($record['message'], $ravenClient->lastData['message']); + } + + public function testTag() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $tags = array(1, 2, 'foo'); + $record = $this->getRecord(Logger::INFO, 'test', array('tags' => $tags)); + $handler->handle($record); + + $this->assertEquals($tags, $ravenClient->lastData['tags']); + } + + public function testExtraParameters() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $checksum = '098f6bcd4621d373cade4e832627b4f6'; + $release = '05a671c66aefea124cc08b76ea6d30bb'; + $record = $this->getRecord(Logger::INFO, 'test', array('checksum' => $checksum, 'release' => $release)); + $handler->handle($record); + + $this->assertEquals($checksum, $ravenClient->lastData['checksum']); + $this->assertEquals($release, $ravenClient->lastData['release']); + } + + public function testFingerprint() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $fingerprint = array('{{ default }}', 'other value'); + $record = $this->getRecord(Logger::INFO, 'test', array('fingerprint' => $fingerprint)); + $handler->handle($record); + + $this->assertEquals($fingerprint, $ravenClient->lastData['fingerprint']); + } + + public function testUserContext() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $recordWithNoContext = $this->getRecord(Logger::INFO, 'test with default user context'); + // set user context 'externally' + + $user = array( + 'id' => '123', + 'email' => 'test@test.com', + ); + + $recordWithContext = $this->getRecord(Logger::INFO, 'test', array('user' => $user)); + + $ravenClient->user_context(array('id' => 'test_user_id')); + // handle context + $handler->handle($recordWithContext); + $this->assertEquals($user, $ravenClient->lastData['user']); + + // check to see if its reset + $handler->handle($recordWithNoContext); + $this->assertInternalType('array', $ravenClient->context->user); + $this->assertSame('test_user_id', $ravenClient->context->user['id']); + + // handle with null context + $ravenClient->user_context(null); + $handler->handle($recordWithContext); + $this->assertEquals($user, $ravenClient->lastData['user']); + + // check to see if its reset + $handler->handle($recordWithNoContext); + $this->assertNull($ravenClient->context->user); + } + + public function testException() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + try { + $this->methodThatThrowsAnException(); + } catch (\Exception $e) { + $record = $this->getRecord(Logger::ERROR, $e->getMessage(), array('exception' => $e)); + $handler->handle($record); + } + + $this->assertEquals($record['message'], $ravenClient->lastData['message']); + } + + public function testHandleBatch() + { + $records = $this->getMultipleRecords(); + $records[] = $this->getRecord(Logger::WARNING, 'warning'); + $records[] = $this->getRecord(Logger::WARNING, 'warning'); + + $logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $logFormatter->expects($this->once())->method('formatBatch'); + + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once())->method('format')->with($this->callback(function ($record) { + return $record['level'] == 400; + })); + + $handler = $this->getHandler($this->getRavenClient()); + $handler->setBatchFormatter($logFormatter); + $handler->setFormatter($formatter); + $handler->handleBatch($records); + } + + public function testHandleBatchDoNothingIfRecordsAreBelowLevel() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + ); + + $handler = $this->getMock('Monolog\Handler\RavenHandler', null, array($this->getRavenClient())); + $handler->expects($this->never())->method('handle'); + $handler->setLevel(Logger::ERROR); + $handler->handleBatch($records); + } + + public function testHandleBatchPicksProperMessage() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information 1'), + $this->getRecord(Logger::ERROR, 'error 1'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error 2'), + $this->getRecord(Logger::INFO, 'information 2'), + ); + + $logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $logFormatter->expects($this->once())->method('formatBatch'); + + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once())->method('format')->with($this->callback(function ($record) use ($records) { + return $record['message'] == 'error 1'; + })); + + $handler = $this->getHandler($this->getRavenClient()); + $handler->setBatchFormatter($logFormatter); + $handler->setFormatter($formatter); + $handler->handleBatch($records); + } + + public function testGetSetBatchFormatter() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $handler->setBatchFormatter($formatter = new LineFormatter()); + $this->assertSame($formatter, $handler->getBatchFormatter()); + } + + public function testRelease() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + $release = 'v42.42.42'; + $handler->setRelease($release); + $record = $this->getRecord(Logger::INFO, 'test'); + $handler->handle($record); + $this->assertEquals($release, $ravenClient->lastData['release']); + + $localRelease = 'v41.41.41'; + $record = $this->getRecord(Logger::INFO, 'test', array('release' => $localRelease)); + $handler->handle($record); + $this->assertEquals($localRelease, $ravenClient->lastData['release']); + } + + private function methodThatThrowsAnException() + { + throw new \Exception('This is an exception'); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php new file mode 100644 index 0000000..689d527 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +class RedisHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidRedis() + { + new RedisHandler(new \stdClass(), 'key'); + } + + public function testConstructorShouldWorkWithPredis() + { + $redis = $this->getMock('Predis\Client'); + $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key')); + } + + public function testConstructorShouldWorkWithRedis() + { + $redis = $this->getMock('Redis'); + $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key')); + } + + public function testPredisHandle() + { + $redis = $this->getMock('Predis\Client', array('rpush')); + + // Predis\Client uses rpush + $redis->expects($this->once()) + ->method('rpush') + ->with('key', 'test'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key'); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testRedisHandle() + { + $redis = $this->getMock('Redis', array('rpush')); + + // Redis uses rPush + $redis->expects($this->once()) + ->method('rPush') + ->with('key', 'test'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key'); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testRedisHandleCapped() + { + $redis = $this->getMock('Redis', array('multi', 'rpush', 'ltrim', 'exec')); + + // Redis uses multi + $redis->expects($this->once()) + ->method('multi') + ->will($this->returnSelf()); + + $redis->expects($this->once()) + ->method('rpush') + ->will($this->returnSelf()); + + $redis->expects($this->once()) + ->method('ltrim') + ->will($this->returnSelf()); + + $redis->expects($this->once()) + ->method('exec') + ->will($this->returnSelf()); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key', Logger::DEBUG, true, 10); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testPredisHandleCapped() + { + $redis = $this->getMock('Predis\Client', array('transaction')); + + $redisTransaction = $this->getMock('Predis\Client', array('rpush', 'ltrim')); + + $redisTransaction->expects($this->once()) + ->method('rpush') + ->will($this->returnSelf()); + + $redisTransaction->expects($this->once()) + ->method('ltrim') + ->will($this->returnSelf()); + + // Redis uses multi + $redis->expects($this->once()) + ->method('transaction') + ->will($this->returnCallback(function ($cb) use ($redisTransaction) { + $cb($redisTransaction); + })); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key', Logger::DEBUG, true, 10); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php new file mode 100644 index 0000000..c510617 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use PHPUnit_Framework_Error_Deprecated; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class RotatingFileHandlerTest extends TestCase +{ + /** + * This var should be private but then the anonymous function + * in the `setUp` method won't be able to set it. `$this` cant't + * be used in the anonymous function in `setUp` because PHP 5.3 + * does not support it. + */ + public $lastError; + + public function setUp() + { + $dir = __DIR__.'/Fixtures'; + chmod($dir, 0777); + if (!is_writable($dir)) { + $this->markTestSkipped($dir.' must be writable to test the RotatingFileHandler.'); + } + $this->lastError = null; + $self = $this; + // workaround with &$self used for PHP 5.3 + set_error_handler(function($code, $message) use (&$self) { + $self->lastError = array( + 'code' => $code, + 'message' => $message, + ); + }); + } + + private function assertErrorWasTriggered($code, $message) + { + if (empty($this->lastError)) { + $this->fail( + sprintf( + 'Failed asserting that error with code `%d` and message `%s` was triggered', + $code, + $message + ) + ); + } + $this->assertEquals($code, $this->lastError['code'], sprintf('Expected an error with code %d to be triggered, got `%s` instead', $code, $this->lastError['code'])); + $this->assertEquals($message, $this->lastError['message'], sprintf('Expected an error with message `%d` to be triggered, got `%s` instead', $message, $this->lastError['message'])); + } + + public function testRotationCreatesNewFile() + { + touch(__DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400).'.rot'); + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot'); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + $this->assertTrue(file_exists($log)); + $this->assertEquals('test', file_get_contents($log)); + } + + /** + * @dataProvider rotationTests + */ + public function testRotation($createFile, $dateFormat, $timeCallback) + { + touch($old1 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-1)).'.rot'); + touch($old2 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-2)).'.rot'); + touch($old3 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-3)).'.rot'); + touch($old4 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-4)).'.rot'); + + $log = __DIR__.'/Fixtures/foo-'.date($dateFormat).'.rot'; + + if ($createFile) { + touch($log); + } + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->setFilenameFormat('{filename}-{date}', $dateFormat); + $handler->handle($this->getRecord()); + + $handler->close(); + + $this->assertTrue(file_exists($log)); + $this->assertTrue(file_exists($old1)); + $this->assertEquals($createFile, file_exists($old2)); + $this->assertEquals($createFile, file_exists($old3)); + $this->assertEquals($createFile, file_exists($old4)); + $this->assertEquals('test', file_get_contents($log)); + } + + public function rotationTests() + { + $now = time(); + $dayCallback = function($ago) use ($now) { + return $now + 86400 * $ago; + }; + $monthCallback = function($ago) { + return gmmktime(0, 0, 0, date('n') + $ago, date('d'), date('Y')); + }; + $yearCallback = function($ago) { + return gmmktime(0, 0, 0, date('n'), date('d'), date('Y') + $ago); + }; + + return array( + 'Rotation is triggered when the file of the current day is not present' + => array(true, RotatingFileHandler::FILE_PER_DAY, $dayCallback), + 'Rotation is not triggered when the file of the current day is already present' + => array(false, RotatingFileHandler::FILE_PER_DAY, $dayCallback), + + 'Rotation is triggered when the file of the current month is not present' + => array(true, RotatingFileHandler::FILE_PER_MONTH, $monthCallback), + 'Rotation is not triggered when the file of the current month is already present' + => array(false, RotatingFileHandler::FILE_PER_MONTH, $monthCallback), + + 'Rotation is triggered when the file of the current year is not present' + => array(true, RotatingFileHandler::FILE_PER_YEAR, $yearCallback), + 'Rotation is not triggered when the file of the current year is already present' + => array(false, RotatingFileHandler::FILE_PER_YEAR, $yearCallback), + ); + } + + /** + * @dataProvider dateFormatProvider + */ + public function testAllowOnlyFixedDefinedDateFormats($dateFormat, $valid) + { + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFilenameFormat('{filename}-{date}', $dateFormat); + if (!$valid) { + $this->assertErrorWasTriggered( + E_USER_DEPRECATED, + 'Invalid date format - format must be one of RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), '. + 'RotatingFileHandler::FILE_PER_MONTH ("Y-m") or RotatingFileHandler::FILE_PER_YEAR ("Y"), '. + 'or you can set one of the date formats using slashes, underscores and/or dots instead of dashes.' + ); + } + } + + public function dateFormatProvider() + { + return array( + array(RotatingFileHandler::FILE_PER_DAY, true), + array(RotatingFileHandler::FILE_PER_MONTH, true), + array(RotatingFileHandler::FILE_PER_YEAR, true), + array('m-d-Y', false), + array('Y-m-d-h-i', false) + ); + } + + /** + * @dataProvider filenameFormatProvider + */ + public function testDisallowFilenameFormatsWithoutDate($filenameFormat, $valid) + { + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFilenameFormat($filenameFormat, RotatingFileHandler::FILE_PER_DAY); + if (!$valid) { + $this->assertErrorWasTriggered( + E_USER_DEPRECATED, + 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.' + ); + } + } + + public function filenameFormatProvider() + { + return array( + array('{filename}', false), + array('{filename}-{date}', true), + array('{date}', true), + array('foobar-{date}', true), + array('foo-{date}-bar', true), + array('{date}-foobar', true), + array('foobar', false), + ); + } + + public function testReuseCurrentFile() + { + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + file_put_contents($log, "foo"); + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot'); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + $this->assertEquals('footest', file_get_contents($log)); + } + + public function tearDown() + { + foreach (glob(__DIR__.'/Fixtures/*.rot') as $file) { + unlink($file); + } + restore_error_handler(); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php new file mode 100644 index 0000000..b354cee --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; + +/** + * @covers Monolog\Handler\SamplingHandler::handle + */ +class SamplingHandlerTest extends TestCase +{ + public function testHandle() + { + $testHandler = new TestHandler(); + $handler = new SamplingHandler($testHandler, 2); + for ($i = 0; $i < 10000; $i++) { + $handler->handle($this->getRecord()); + } + $count = count($testHandler->getRecords()); + // $count should be half of 10k, so between 4k and 6k + $this->assertLessThan(6000, $count); + $this->assertGreaterThan(4000, $count); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php new file mode 100644 index 0000000..d6c6c22 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php @@ -0,0 +1,341 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Logger; +use Monolog\TestCase; + +/** + * @coversDefaultClass Monolog\Handler\Slack\SlackRecord + */ +class SlackRecordTest extends TestCase +{ + private $channel; + + protected function setUp() + { + $this->channel = 'monolog_alerts'; + } + + public function dataGetAttachmentColor() + { + return array( + array(Logger::DEBUG, SlackRecord::COLOR_DEFAULT), + array(Logger::INFO, SlackRecord::COLOR_GOOD), + array(Logger::NOTICE, SlackRecord::COLOR_GOOD), + array(Logger::WARNING, SlackRecord::COLOR_WARNING), + array(Logger::ERROR, SlackRecord::COLOR_DANGER), + array(Logger::CRITICAL, SlackRecord::COLOR_DANGER), + array(Logger::ALERT, SlackRecord::COLOR_DANGER), + array(Logger::EMERGENCY, SlackRecord::COLOR_DANGER), + ); + } + /** + * @dataProvider dataGetAttachmentColor + * @param int $logLevel + * @param string $expectedColour RGB hex color or name of Slack color + * @covers ::getAttachmentColor + */ + public function testGetAttachmentColor($logLevel, $expectedColour) + { + $slackRecord = new SlackRecord('#test'); + $this->assertSame( + $expectedColour, + $slackRecord->getAttachmentColor($logLevel) + ); + } + + public function testAddsChannel() + { + $record = new SlackRecord($this->channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('channel', $data); + $this->assertSame($this->channel, $data['channel']); + } + + public function testStringifyReturnsNullWithNoLineFormatter() + { + $slackRecord = new SlackRecord('#test'); + $this->assertNull($slackRecord->stringify(array('foo' => 'bar'))); + } + + public function testAddsDefaultUsername() + { + $record = new SlackRecord($this->channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('username', $data); + $this->assertSame('Monolog', $data['username']); + } + + /** + * @return array + */ + public function dataStringify() + { + return array( + array(array(), ''), + array(array('foo' => 'bar'), 'foo: bar'), + array(array('Foo' => 'bAr'), 'Foo: bAr'), + ); + } + + /** + * @dataProvider dataStringify + */ + public function testStringifyWithLineFormatter($fields, $expectedResult) + { + $slackRecord = new SlackRecord( + '#test', + 'test', + true, + null, + true, + true + ); + + $this->assertSame($expectedResult, $slackRecord->stringify($fields)); + } + + public function testAddsCustomUsername() + { + $username = 'Monolog bot'; + $record = new SlackRecord($this->channel, $username); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('username', $data); + $this->assertSame($username, $data['username']); + } + + public function testNoIcon() + { + $record = new SlackRecord($this->channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayNotHasKey('icon_emoji', $data); + } + + public function testAddsIcon() + { + $record = new SlackRecord($this->channel, 'Monolog', true, 'ghost'); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('icon_emoji', $data); + $this->assertSame(':ghost:', $data['icon_emoji']); + } + + public function testAddsEmptyTextIfUseAttachment() + { + $record = new SlackRecord($this->channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('text', $data); + $this->assertSame('', $data['text']); + } + + public function testAttachmentsNotPresentIfNoAttachment() + { + $record = new SlackRecord($this->channel, 'Monolog', false); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayNotHasKey('attachments', $data); + } + + public function testAddsOneAttachment() + { + $record = new SlackRecord($this->channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('attachments', $data); + $this->assertArrayHasKey(0, $data['attachments']); + $this->assertInternalType('array', $data['attachments'][0]); + } + + public function testTextEqualsMessageIfNoFormatter() + { + $message = 'Test message'; + $record = new SlackRecord($this->channel, 'Monolog', false); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message, $data['text']); + } + + public function testTextEqualsFormatterOutput() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter + ->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message'] . 'test'; })); + + $formatter2 = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter2 + ->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message'] . 'test1'; })); + + $message = 'Test message'; + $record = new SlackRecord($this->channel, 'Monolog', false, null, false, false, $formatter); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message . 'test', $data['text']); + + $record->setFormatter($formatter2); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message . 'test1', $data['text']); + } + + public function testAddsFallbackAndTextToAttachment() + { + $message = 'Test message'; + $record = new SlackRecord($this->channel); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertSame($message, $data['attachments'][0]['text']); + $this->assertSame($message, $data['attachments'][0]['fallback']); + } + + public function testMapsLevelToColorAttachmentColor() + { + $record = new SlackRecord($this->channel); + $errorLoggerRecord = $this->getRecord(Logger::ERROR); + $emergencyLoggerRecord = $this->getRecord(Logger::EMERGENCY); + $warningLoggerRecord = $this->getRecord(Logger::WARNING); + $infoLoggerRecord = $this->getRecord(Logger::INFO); + $debugLoggerRecord = $this->getRecord(Logger::DEBUG); + + $data = $record->getSlackData($errorLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']); + + $data = $record->getSlackData($emergencyLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']); + + $data = $record->getSlackData($warningLoggerRecord); + $this->assertSame(SlackRecord::COLOR_WARNING, $data['attachments'][0]['color']); + + $data = $record->getSlackData($infoLoggerRecord); + $this->assertSame(SlackRecord::COLOR_GOOD, $data['attachments'][0]['color']); + + $data = $record->getSlackData($debugLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DEFAULT, $data['attachments'][0]['color']); + } + + public function testAddsShortAttachmentWithoutContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord($this->channel, 'Monolog', true, null, true); + $data = $record->getSlackData($this->getRecord($level, 'test', array('test' => 1))); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertSame($levelName, $attachment['title']); + $this->assertSame(array(), $attachment['fields']); + } + + public function testAddsShortAttachmentWithContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord($this->channel, 'Monolog', true, null, true, true); + $loggerRecord = $this->getRecord($level, 'test', array('test' => 1)); + $loggerRecord['extra'] = array('tags' => array('web')); + $data = $record->getSlackData($loggerRecord); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(2, $attachment['fields']); + $this->assertSame($levelName, $attachment['title']); + $this->assertSame( + array( + array( + 'title' => 'Extra', + 'value' => 'tags: ["web"]', + 'short' => true + ), + array( + 'title' => 'Context', + 'value' => 'test: 1', + 'short' => true + ) + ), + $attachment['fields'] + ); + } + + public function testAddsLongAttachmentWithoutContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord($this->channel, 'Monolog', true, null); + $data = $record->getSlackData($this->getRecord($level, 'test', array('test' => 1))); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(1, $attachment['fields']); + $this->assertSame('Message', $attachment['title']); + $this->assertSame( + array(array( + 'title' => 'Level', + 'value' => $levelName, + 'short' => true + )), + $attachment['fields'] + ); + } + + public function testAddsLongAttachmentWithContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord($this->channel, 'Monolog', true, null, false, true); + $loggerRecord = $this->getRecord($level, 'test', array('test' => 1)); + $loggerRecord['extra'] = array('tags' => array('web')); + $data = $record->getSlackData($loggerRecord); + + $expectedFields = array( + array( + 'title' => 'Level', + 'value' => $levelName, + 'short' => true, + ), + array( + 'title' => 'tags', + 'value' => '["web"]', + 'short' => false + ), + array( + 'title' => 'test', + 'value' => 1, + 'short' => false + ) + ); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(3, $attachment['fields']); + $this->assertSame('Message', $attachment['title']); + $this->assertSame( + $expectedFields, + $attachment['fields'] + ); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php new file mode 100644 index 0000000..b12b01f --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\Slack\SlackRecord; + +/** + * @author Greg Kedzierski + * @see https://api.slack.com/ + */ +class SlackHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $res; + + /** + * @var SlackHandler + */ + private $handler; + + public function setUp() + { + if (!extension_loaded('openssl')) { + $this->markTestSkipped('This test requires openssl to run'); + } + } + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/api\/chat.postMessage HTTP\/1.1\\r\\nHost: slack.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + } + + public function testWriteContent() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegExp('/username=Monolog/', $content); + $this->assertRegExp('/channel=channel1/', $content); + $this->assertRegExp('/token=myToken/', $content); + $this->assertRegExp('/attachments/', $content); + } + + public function testWriteContentUsesFormatterIfProvided() + { + $this->createHandler('myToken', 'channel1', 'Monolog', false); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->createHandler('myToken', 'channel1', 'Monolog', false); + $this->handler->setFormatter(new LineFormatter('foo--%message%')); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test2')); + fseek($this->res, 0); + $content2 = fread($this->res, 1024); + + $this->assertRegexp('/text=test1/', $content); + $this->assertRegexp('/text=foo--test2/', $content2); + } + + public function testWriteContentWithEmoji() + { + $this->createHandler('myToken', 'channel1', 'Monolog', true, 'alien'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/icon_emoji=%3Aalien%3A/', $content); + } + + /** + * @dataProvider provideLevelColors + */ + public function testWriteContentWithColors($level, $expectedColor) + { + $this->createHandler(); + $this->handler->handle($this->getRecord($level, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/%22color%22%3A%22'.$expectedColor.'/', $content); + } + + public function testWriteContentWithPlainTextMessage() + { + $this->createHandler('myToken', 'channel1', 'Monolog', false); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/text=test1/', $content); + } + + public function provideLevelColors() + { + return array( + array(Logger::DEBUG, urlencode(SlackRecord::COLOR_DEFAULT)), + array(Logger::INFO, SlackRecord::COLOR_GOOD), + array(Logger::NOTICE, SlackRecord::COLOR_GOOD), + array(Logger::WARNING, SlackRecord::COLOR_WARNING), + array(Logger::ERROR, SlackRecord::COLOR_DANGER), + array(Logger::CRITICAL, SlackRecord::COLOR_DANGER), + array(Logger::ALERT, SlackRecord::COLOR_DANGER), + array(Logger::EMERGENCY,SlackRecord::COLOR_DANGER), + ); + } + + private function createHandler($token = 'myToken', $channel = 'channel1', $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeExtra = false) + { + $constructorArgs = array($token, $channel, $username, $useAttachment, $iconEmoji, Logger::DEBUG, true, $useShortAttachment, $includeExtra); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\SlackHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php new file mode 100644 index 0000000..cc9b7ba --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\Slack\SlackRecord; + +/** + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @coversDefaultClass Monolog\Handler\SlackWebhookHandler + */ +class SlackWebhookHandlerTest extends TestCase +{ + const WEBHOOK_URL = 'https://hooks.slack.com/services/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E'; + + /** + * @covers ::__construct + * @covers ::getSlackRecord + */ + public function testConstructorMinimal() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $slackRecord = $handler->getSlackRecord(); + $this->assertInstanceOf('Monolog\Handler\Slack\SlackRecord', $slackRecord); + $this->assertEquals(array( + 'username' => 'Monolog', + 'text' => '', + 'attachments' => array( + array( + 'fallback' => 'test', + 'text' => 'test', + 'color' => SlackRecord::COLOR_WARNING, + 'fields' => array( + array( + 'title' => 'Level', + 'value' => 'WARNING', + 'short' => true, + ), + ), + 'title' => 'Message', + ), + ), + ), $slackRecord->getSlackData($this->getRecord())); + } + + /** + * @covers ::__construct + * @covers ::getSlackRecord + */ + public function testConstructorFull() + { + $handler = new SlackWebhookHandler( + self::WEBHOOK_URL, + 'test-channel', + 'test-username', + false, + ':ghost:', + false, + false, + Logger::DEBUG, + false + ); + + $slackRecord = $handler->getSlackRecord(); + $this->assertInstanceOf('Monolog\Handler\Slack\SlackRecord', $slackRecord); + $this->assertEquals(array( + 'username' => 'test-username', + 'text' => 'test', + 'channel' => 'test-channel', + 'icon_emoji' => ':ghost:', + ), $slackRecord->getSlackData($this->getRecord())); + } + + /** + * @covers ::getFormatter + */ + public function testGetFormatter() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $formatter = $handler->getFormatter(); + $this->assertInstanceOf('Monolog\Formatter\FormatterInterface', $formatter); + } + + /** + * @covers ::setFormatter + */ + public function testSetFormatter() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $formatter = new LineFormatter(); + $handler->setFormatter($formatter); + $this->assertSame($formatter, $handler->getFormatter()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php new file mode 100644 index 0000000..b1b02bd --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Haralan Dobrev + * @see https://slack.com/apps/A0F81R8ET-slackbot + * @coversDefaultClass Monolog\Handler\SlackbotHandler + */ +class SlackbotHandlerTest extends TestCase +{ + /** + * @covers ::__construct + */ + public function testConstructorMinimal() + { + $handler = new SlackbotHandler('test-team', 'test-token', 'test-channel'); + $this->assertInstanceOf('Monolog\Handler\AbstractProcessingHandler', $handler); + } + + /** + * @covers ::__construct + */ + public function testConstructorFull() + { + $handler = new SlackbotHandler( + 'test-team', + 'test-token', + 'test-channel', + Logger::DEBUG, + false + ); + $this->assertInstanceOf('Monolog\Handler\AbstractProcessingHandler', $handler); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php new file mode 100644 index 0000000..1f9c1f2 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php @@ -0,0 +1,309 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Pablo de Leon Belloc + */ +class SocketHandlerTest extends TestCase +{ + /** + * @var Monolog\Handler\SocketHandler + */ + private $handler; + + /** + * @var resource + */ + private $res; + + /** + * @expectedException UnexpectedValueException + */ + public function testInvalidHostname() + { + $this->createHandler('garbage://here'); + $this->writeRecord('data'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadConnectionTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setConnectionTimeout(-1); + } + + public function testSetConnectionTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setConnectionTimeout(10.1); + $this->assertEquals(10.1, $this->handler->getConnectionTimeout()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setTimeout(-1); + } + + public function testSetTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setTimeout(10.25); + $this->assertEquals(10.25, $this->handler->getTimeout()); + } + + public function testSetWritingTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setWritingTimeout(10.25); + $this->assertEquals(10.25, $this->handler->getWritingTimeout()); + } + + public function testSetConnectionString() + { + $this->createHandler('tcp://localhost:9090'); + $this->assertEquals('tcp://localhost:9090', $this->handler->getConnectionString()); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownOnFsockopenError() + { + $this->setMockHandler(array('fsockopen')); + $this->handler->expects($this->once()) + ->method('fsockopen') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownOnPfsockopenError() + { + $this->setMockHandler(array('pfsockopen')); + $this->handler->expects($this->once()) + ->method('pfsockopen') + ->will($this->returnValue(false)); + $this->handler->setPersistent(true); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownIfCannotSetTimeout() + { + $this->setMockHandler(array('streamSetTimeout')); + $this->handler->expects($this->once()) + ->method('streamSetTimeout') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsOnIfFwriteReturnsFalse() + { + $this->setMockHandler(array('fwrite')); + + $callback = function ($arg) { + $map = array( + 'Hello world' => 6, + 'world' => false, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(2)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsIfStreamTimesOut() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $callback = function ($arg) { + $map = array( + 'Hello world' => 6, + 'world' => 5, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(1)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + $this->handler->expects($this->exactly(1)) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => true))); + + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsOnIncompleteWrite() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $res = $this->res; + $callback = function ($string) use ($res) { + fclose($res); + + return strlen('Hello'); + }; + + $this->handler->expects($this->exactly(1)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + $this->handler->expects($this->exactly(1)) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => false))); + + $this->writeRecord('Hello world'); + } + + public function testWriteWithMemoryFile() + { + $this->setMockHandler(); + $this->writeRecord('test1'); + $this->writeRecord('test2'); + $this->writeRecord('test3'); + fseek($this->res, 0); + $this->assertEquals('test1test2test3', fread($this->res, 1024)); + } + + public function testWriteWithMock() + { + $this->setMockHandler(array('fwrite')); + + $callback = function ($arg) { + $map = array( + 'Hello world' => 6, + 'world' => 5, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(2)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + + $this->writeRecord('Hello world'); + } + + public function testClose() + { + $this->setMockHandler(); + $this->writeRecord('Hello world'); + $this->assertInternalType('resource', $this->res); + $this->handler->close(); + $this->assertFalse(is_resource($this->res), "Expected resource to be closed after closing handler"); + } + + public function testCloseDoesNotClosePersistentSocket() + { + $this->setMockHandler(); + $this->handler->setPersistent(true); + $this->writeRecord('Hello world'); + $this->assertTrue(is_resource($this->res)); + $this->handler->close(); + $this->assertTrue(is_resource($this->res)); + } + + /** + * @expectedException \RuntimeException + */ + public function testAvoidInfiniteLoopWhenNoDataIsWrittenForAWritingTimeoutSeconds() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $this->handler->expects($this->any()) + ->method('fwrite') + ->will($this->returnValue(0)); + + $this->handler->expects($this->any()) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => false))); + + $this->handler->setWritingTimeout(1); + + $this->writeRecord('Hello world'); + } + + private function createHandler($connectionString) + { + $this->handler = new SocketHandler($connectionString); + $this->handler->setFormatter($this->getIdentityFormatter()); + } + + private function writeRecord($string) + { + $this->handler->handle($this->getRecord(Logger::WARNING, $string)); + } + + private function setMockHandler(array $methods = array()) + { + $this->res = fopen('php://memory', 'a'); + + $defaultMethods = array('fsockopen', 'pfsockopen', 'streamSetTimeout'); + $newMethods = array_diff($methods, $defaultMethods); + + $finalMethods = array_merge($defaultMethods, $newMethods); + + $this->handler = $this->getMock( + '\Monolog\Handler\SocketHandler', $finalMethods, array('localhost:1234') + ); + + if (!in_array('fsockopen', $methods)) { + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + } + + if (!in_array('pfsockopen', $methods)) { + $this->handler->expects($this->any()) + ->method('pfsockopen') + ->will($this->returnValue($this->res)); + } + + if (!in_array('streamSetTimeout', $methods)) { + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + } + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php new file mode 100644 index 0000000..487030f --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class StreamHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWrite() + { + $handle = fopen('php://memory', 'a+'); + $handler = new StreamHandler($handle); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::WARNING, 'test')); + $handler->handle($this->getRecord(Logger::WARNING, 'test2')); + $handler->handle($this->getRecord(Logger::WARNING, 'test3')); + fseek($handle, 0); + $this->assertEquals('testtest2test3', fread($handle, 100)); + } + + /** + * @covers Monolog\Handler\StreamHandler::close + */ + public function testCloseKeepsExternalHandlersOpen() + { + $handle = fopen('php://memory', 'a+'); + $handler = new StreamHandler($handle); + $this->assertTrue(is_resource($handle)); + $handler->close(); + $this->assertTrue(is_resource($handle)); + } + + /** + * @covers Monolog\Handler\StreamHandler::close + */ + public function testClose() + { + $handler = new StreamHandler('php://memory'); + $handler->handle($this->getRecord(Logger::WARNING, 'test')); + $streamProp = new \ReflectionProperty('Monolog\Handler\StreamHandler', 'stream'); + $streamProp->setAccessible(true); + $handle = $streamProp->getValue($handler); + + $this->assertTrue(is_resource($handle)); + $handler->close(); + $this->assertFalse(is_resource($handle)); + } + + /** + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteCreatesTheStreamResource() + { + $handler = new StreamHandler('php://memory'); + $handler->handle($this->getRecord()); + } + + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteLocking() + { + $temp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'monolog_locked_log'; + $handler = new StreamHandler($temp, Logger::DEBUG, true, null, true); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException LogicException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteMissingResource() + { + $handler = new StreamHandler(null); + $handler->handle($this->getRecord()); + } + + public function invalidArgumentProvider() + { + return array( + array(1), + array(array()), + array(array('bogus://url')), + ); + } + + /** + * @dataProvider invalidArgumentProvider + * @expectedException InvalidArgumentException + * @covers Monolog\Handler\StreamHandler::__construct + */ + public function testWriteInvalidArgument($invalidArgument) + { + $handler = new StreamHandler($invalidArgument); + } + + /** + * @expectedException UnexpectedValueException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteInvalidResource() + { + $handler = new StreamHandler('bogus://url'); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException UnexpectedValueException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingResource() + { + $handler = new StreamHandler('ftp://foo/bar/baz/'.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingPath() + { + $handler = new StreamHandler(sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingFileResource() + { + $handler = new StreamHandler('file://'.sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException Exception + * @expectedExceptionMessageRegExp /There is no existing directory at/ + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingAndNotCreatablePath() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Permissions checks can not run on windows'); + } + $handler = new StreamHandler('/foo/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException Exception + * @expectedExceptionMessageRegExp /There is no existing directory at/ + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingAndNotCreatableFileResource() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Permissions checks can not run on windows'); + } + $handler = new StreamHandler('file:///foo/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php new file mode 100644 index 0000000..8588691 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class SwiftMailerHandlerTest extends TestCase +{ + /** @var \Swift_Mailer|\PHPUnit_Framework_MockObject_MockObject */ + private $mailer; + + public function setUp() + { + $this->mailer = $this + ->getMockBuilder('Swift_Mailer') + ->disableOriginalConstructor() + ->getMock(); + } + + public function testMessageCreationIsLazyWhenUsingCallback() + { + $this->mailer->expects($this->never()) + ->method('send'); + + $callback = function () { + throw new \RuntimeException('Swift_Message creation callback should not have been called in this test'); + }; + $handler = new SwiftMailerHandler($this->mailer, $callback); + + $records = array( + $this->getRecord(Logger::DEBUG), + $this->getRecord(Logger::INFO), + ); + $handler->handleBatch($records); + } + + public function testMessageCanBeCustomizedGivenLoggedData() + { + // Wire Mailer to expect a specific Swift_Message with a customized Subject + $expectedMessage = new \Swift_Message(); + $this->mailer->expects($this->once()) + ->method('send') + ->with($this->callback(function ($value) use ($expectedMessage) { + return $value instanceof \Swift_Message + && $value->getSubject() === 'Emergency' + && $value === $expectedMessage; + })); + + // Callback dynamically changes subject based on number of logged records + $callback = function ($content, array $records) use ($expectedMessage) { + $subject = count($records) > 0 ? 'Emergency' : 'Normal'; + $expectedMessage->setSubject($subject); + + return $expectedMessage; + }; + $handler = new SwiftMailerHandler($this->mailer, $callback); + + // Logging 1 record makes this an Emergency + $records = array( + $this->getRecord(Logger::EMERGENCY), + ); + $handler->handleBatch($records); + } + + public function testMessageSubjectFormatting() + { + // Wire Mailer to expect a specific Swift_Message with a customized Subject + $messageTemplate = new \Swift_Message(); + $messageTemplate->setSubject('Alert: %level_name% %message%'); + $receivedMessage = null; + + $this->mailer->expects($this->once()) + ->method('send') + ->with($this->callback(function ($value) use (&$receivedMessage) { + $receivedMessage = $value; + return true; + })); + + $handler = new SwiftMailerHandler($this->mailer, $messageTemplate); + + $records = array( + $this->getRecord(Logger::EMERGENCY), + ); + $handler->handleBatch($records); + + $this->assertEquals('Alert: EMERGENCY test', $receivedMessage->getSubject()); + } + + public function testMessageHaveUniqueId() + { + $messageTemplate = \Swift_Message::newInstance(); + $handler = new SwiftMailerHandler($this->mailer, $messageTemplate); + + $method = new \ReflectionMethod('Monolog\Handler\SwiftMailerHandler', 'buildMessage'); + $method->setAccessible(true); + $method->invokeArgs($handler, array($messageTemplate, array())); + + $builtMessage1 = $method->invoke($handler, $messageTemplate, array()); + $builtMessage2 = $method->invoke($handler, $messageTemplate, array()); + + $this->assertFalse($builtMessage1->getId() === $builtMessage2->getId(), 'Two different messages have the same id'); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php new file mode 100644 index 0000000..8f9e46b --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +class SyslogHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Handler\SyslogHandler::__construct + */ + public function testConstruct() + { + $handler = new SyslogHandler('test'); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', LOG_USER); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', 'user'); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', LOG_USER, Logger::DEBUG, true, LOG_PERROR); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + } + + /** + * @covers Monolog\Handler\SyslogHandler::__construct + */ + public function testConstructInvalidFacility() + { + $this->setExpectedException('UnexpectedValueException'); + $handler = new SyslogHandler('test', 'unknown'); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php new file mode 100644 index 0000000..aa4a334 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +/** + * @requires extension sockets + */ +class SyslogUdpHandlerTest extends TestCase +{ + /** + * @expectedException UnexpectedValueException + */ + public function testWeValidateFacilities() + { + $handler = new SyslogUdpHandler("ip", null, "invalidFacility"); + } + + public function testWeSplitIntoLines() + { + $handler = new SyslogUdpHandler("127.0.0.1", 514, "authpriv"); + $handler->setFormatter(new \Monolog\Formatter\ChromePHPFormatter()); + + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('write'), array('lol', 'lol')); + $socket->expects($this->at(0)) + ->method('write') + ->with("lol", "<".(LOG_AUTHPRIV + LOG_WARNING).">1 "); + $socket->expects($this->at(1)) + ->method('write') + ->with("hej", "<".(LOG_AUTHPRIV + LOG_WARNING).">1 "); + + $handler->setSocket($socket); + + $handler->handle($this->getRecordWithMessage("hej\nlol")); + } + + public function testSplitWorksOnEmptyMsg() + { + $handler = new SyslogUdpHandler("127.0.0.1", 514, "authpriv"); + $handler->setFormatter($this->getIdentityFormatter()); + + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('write'), array('lol', 'lol')); + $socket->expects($this->never()) + ->method('write'); + + $handler->setSocket($socket); + + $handler->handle($this->getRecordWithMessage(null)); + } + + protected function getRecordWithMessage($msg) + { + return array('message' => $msg, 'level' => \Monolog\Logger::WARNING, 'context' => null, 'extra' => array(), 'channel' => 'lol'); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php new file mode 100644 index 0000000..bfb8d3d --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\TestHandler + */ +class TestHandlerTest extends TestCase +{ + /** + * @dataProvider methodProvider + */ + public function testHandler($method, $level) + { + $handler = new TestHandler; + $record = $this->getRecord($level, 'test'.$method); + $this->assertFalse($handler->hasRecords($level)); + $this->assertFalse($handler->hasRecord($record, $level)); + $this->assertFalse($handler->{'has'.$method}($record), 'has'.$method); + $this->assertFalse($handler->{'has'.$method.'ThatContains'}('test'), 'has'.$method.'ThatContains'); + $this->assertFalse($handler->{'has'.$method.'ThatPasses'}(function ($rec) { + return true; + }), 'has'.$method.'ThatPasses'); + $this->assertFalse($handler->{'has'.$method.'ThatMatches'}('/test\w+/')); + $this->assertFalse($handler->{'has'.$method.'Records'}(), 'has'.$method.'Records'); + $handler->handle($record); + + $this->assertFalse($handler->{'has'.$method}('bar'), 'has'.$method); + $this->assertTrue($handler->hasRecords($level)); + $this->assertTrue($handler->hasRecord($record, $level)); + $this->assertTrue($handler->{'has'.$method}($record), 'has'.$method); + $this->assertTrue($handler->{'has'.$method}('test'.$method), 'has'.$method); + $this->assertTrue($handler->{'has'.$method.'ThatContains'}('test'), 'has'.$method.'ThatContains'); + $this->assertTrue($handler->{'has'.$method.'ThatPasses'}(function ($rec) { + return true; + }), 'has'.$method.'ThatPasses'); + $this->assertTrue($handler->{'has'.$method.'ThatMatches'}('/test\w+/')); + $this->assertTrue($handler->{'has'.$method.'Records'}(), 'has'.$method.'Records'); + + $records = $handler->getRecords(); + unset($records[0]['formatted']); + $this->assertEquals(array($record), $records); + } + + public function methodProvider() + { + return array( + array('Emergency', Logger::EMERGENCY), + array('Alert' , Logger::ALERT), + array('Critical' , Logger::CRITICAL), + array('Error' , Logger::ERROR), + array('Warning' , Logger::WARNING), + array('Info' , Logger::INFO), + array('Notice' , Logger::NOTICE), + array('Debug' , Logger::DEBUG), + ); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php new file mode 100644 index 0000000..fa524d0 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Handler\SyslogUdp\UdpSocket; + +/** + * @requires extension sockets + */ +class UdpSocketTest extends TestCase +{ + public function testWeDoNotTruncateShortMessages() + { + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('send'), array('lol', 'lol')); + + $socket->expects($this->at(0)) + ->method('send') + ->with("HEADER: The quick brown fox jumps over the lazy dog"); + + $socket->write("The quick brown fox jumps over the lazy dog", "HEADER: "); + } + + public function testLongMessagesAreTruncated() + { + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('send'), array('lol', 'lol')); + + $truncatedString = str_repeat("derp", 16254).'d'; + + $socket->expects($this->exactly(1)) + ->method('send') + ->with("HEADER" . $truncatedString); + + $longString = str_repeat("derp", 20000); + + $socket->write($longString, "HEADER"); + } + + public function testDoubleCloseDoesNotError() + { + $socket = new UdpSocket('127.0.0.1', 514); + $socket->close(); + $socket->close(); + } + + /** + * @expectedException LogicException + */ + public function testWriteAfterCloseErrors() + { + $socket = new UdpSocket('127.0.0.1', 514); + $socket->close(); + $socket->write('foo', "HEADER"); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php new file mode 100644 index 0000000..8d37a1f --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class WhatFailureGroupHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorOnlyTakesHandler() + { + new WhatFailureGroupHandler(array(new TestHandler(), "foo")); + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::__construct + * @covers Monolog\Handler\WhatFailureGroupHandler::handle + */ + public function testHandle() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new WhatFailureGroupHandler($testHandlers); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handleBatch + */ + public function testHandleBatch() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new WhatFailureGroupHandler($testHandlers); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::isHandling + */ + public function testIsHandling() + { + $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING)); + $handler = new WhatFailureGroupHandler($testHandlers); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new WhatFailureGroupHandler(array($test)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handle + */ + public function testHandleException() + { + $test = new TestHandler(); + $exception = new ExceptionTestHandler(); + $handler = new WhatFailureGroupHandler(array($exception, $test, $exception)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} + +class ExceptionTestHandler extends TestHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + parent::handle($record); + + throw new \Exception("ExceptionTestHandler::handle"); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php b/src/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php new file mode 100644 index 0000000..69b001e --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; + +class ZendMonitorHandlerTest extends TestCase +{ + protected $zendMonitorHandler; + + public function setUp() + { + if (!function_exists('zend_monitor_custom_event')) { + $this->markTestSkipped('ZendServer is not installed'); + } + } + + /** + * @covers Monolog\Handler\ZendMonitorHandler::write + */ + public function testWrite() + { + $record = $this->getRecord(); + $formatterResult = array( + 'message' => $record['message'], + ); + + $zendMonitor = $this->getMockBuilder('Monolog\Handler\ZendMonitorHandler') + ->setMethods(array('writeZendMonitorCustomEvent', 'getDefaultFormatter')) + ->getMock(); + + $formatterMock = $this->getMockBuilder('Monolog\Formatter\NormalizerFormatter') + ->disableOriginalConstructor() + ->getMock(); + + $formatterMock->expects($this->once()) + ->method('format') + ->will($this->returnValue($formatterResult)); + + $zendMonitor->expects($this->once()) + ->method('getDefaultFormatter') + ->will($this->returnValue($formatterMock)); + + $levelMap = $zendMonitor->getLevelMap(); + + $zendMonitor->expects($this->once()) + ->method('writeZendMonitorCustomEvent') + ->with($levelMap[$record['level']], $record['message'], $formatterResult); + + $zendMonitor->handle($record); + } + + /** + * @covers Monolog\Handler\ZendMonitorHandler::getDefaultFormatter + */ + public function testGetDefaultFormatterReturnsNormalizerFormatter() + { + $zendMonitor = new ZendMonitorHandler(); + $this->assertInstanceOf('Monolog\Formatter\NormalizerFormatter', $zendMonitor->getDefaultFormatter()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/LoggerTest.php b/src/vendor/monolog/monolog/tests/Monolog/LoggerTest.php new file mode 100644 index 0000000..1ecc34a --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/LoggerTest.php @@ -0,0 +1,548 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Processor\WebProcessor; +use Monolog\Handler\TestHandler; + +class LoggerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Logger::getName + */ + public function testGetName() + { + $logger = new Logger('foo'); + $this->assertEquals('foo', $logger->getName()); + } + + /** + * @covers Monolog\Logger::getLevelName + */ + public function testGetLevelName() + { + $this->assertEquals('ERROR', Logger::getLevelName(Logger::ERROR)); + } + + /** + * @covers Monolog\Logger::withName + */ + public function testWithName() + { + $first = new Logger('first', array($handler = new TestHandler())); + $second = $first->withName('second'); + + $this->assertSame('first', $first->getName()); + $this->assertSame('second', $second->getName()); + $this->assertSame($handler, $second->popHandler()); + } + + /** + * @covers Monolog\Logger::toMonologLevel + */ + public function testConvertPSR3ToMonologLevel() + { + $this->assertEquals(Logger::toMonologLevel('debug'), 100); + $this->assertEquals(Logger::toMonologLevel('info'), 200); + $this->assertEquals(Logger::toMonologLevel('notice'), 250); + $this->assertEquals(Logger::toMonologLevel('warning'), 300); + $this->assertEquals(Logger::toMonologLevel('error'), 400); + $this->assertEquals(Logger::toMonologLevel('critical'), 500); + $this->assertEquals(Logger::toMonologLevel('alert'), 550); + $this->assertEquals(Logger::toMonologLevel('emergency'), 600); + } + + /** + * @covers Monolog\Logger::getLevelName + * @expectedException InvalidArgumentException + */ + public function testGetLevelNameThrows() + { + Logger::getLevelName(5); + } + + /** + * @covers Monolog\Logger::__construct + */ + public function testChannel() + { + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->addWarning('test'); + list($record) = $handler->getRecords(); + $this->assertEquals('foo', $record['channel']); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testLog() + { + $logger = new Logger(__METHOD__); + + $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle')); + $handler->expects($this->once()) + ->method('handle'); + $logger->pushHandler($handler); + + $this->assertTrue($logger->addWarning('test')); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testLogNotHandled() + { + $logger = new Logger(__METHOD__); + + $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle'), array(Logger::ERROR)); + $handler->expects($this->never()) + ->method('handle'); + $logger->pushHandler($handler); + + $this->assertFalse($logger->addWarning('test')); + } + + public function testHandlersInCtor() + { + $handler1 = new TestHandler; + $handler2 = new TestHandler; + $logger = new Logger(__METHOD__, array($handler1, $handler2)); + + $this->assertEquals($handler1, $logger->popHandler()); + $this->assertEquals($handler2, $logger->popHandler()); + } + + public function testProcessorsInCtor() + { + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + $logger = new Logger(__METHOD__, array(), array($processor1, $processor2)); + + $this->assertEquals($processor1, $logger->popProcessor()); + $this->assertEquals($processor2, $logger->popProcessor()); + } + + /** + * @covers Monolog\Logger::pushHandler + * @covers Monolog\Logger::popHandler + * @expectedException LogicException + */ + public function testPushPopHandler() + { + $logger = new Logger(__METHOD__); + $handler1 = new TestHandler; + $handler2 = new TestHandler; + + $logger->pushHandler($handler1); + $logger->pushHandler($handler2); + + $this->assertEquals($handler2, $logger->popHandler()); + $this->assertEquals($handler1, $logger->popHandler()); + $logger->popHandler(); + } + + /** + * @covers Monolog\Logger::setHandlers + */ + public function testSetHandlers() + { + $logger = new Logger(__METHOD__); + $handler1 = new TestHandler; + $handler2 = new TestHandler; + + $logger->pushHandler($handler1); + $logger->setHandlers(array($handler2)); + + // handler1 has been removed + $this->assertEquals(array($handler2), $logger->getHandlers()); + + $logger->setHandlers(array( + "AMapKey" => $handler1, + "Woop" => $handler2, + )); + + // Keys have been scrubbed + $this->assertEquals(array($handler1, $handler2), $logger->getHandlers()); + } + + /** + * @covers Monolog\Logger::pushProcessor + * @covers Monolog\Logger::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = new Logger(__METHOD__); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Logger::pushProcessor + * @expectedException InvalidArgumentException + */ + public function testPushProcessorWithNonCallable() + { + $logger = new Logger(__METHOD__); + + $logger->pushProcessor(new \stdClass()); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsAreExecuted() + { + $logger = new Logger(__METHOD__); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->pushProcessor(function ($record) { + $record['extra']['win'] = true; + + return $record; + }); + $logger->addError('test'); + list($record) = $handler->getRecords(); + $this->assertTrue($record['extra']['win']); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsAreCalledOnlyOnce() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler->expects($this->any()) + ->method('handle') + ->will($this->returnValue(true)) + ; + $logger->pushHandler($handler); + + $processor = $this->getMockBuilder('Monolog\Processor\WebProcessor') + ->disableOriginalConstructor() + ->setMethods(array('__invoke')) + ->getMock() + ; + $processor->expects($this->once()) + ->method('__invoke') + ->will($this->returnArgument(0)) + ; + $logger->pushProcessor($processor); + + $logger->addError('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsNotCalledWhenNotHandled() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler); + $that = $this; + $logger->pushProcessor(function ($record) use ($that) { + $that->fail('The processor should not be called'); + }); + $logger->addAlert('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testHandlersNotCalledBeforeFirstHandling() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->never()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler2); + + $handler3 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler3->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler3->expects($this->never()) + ->method('handle') + ; + $logger->pushHandler($handler3); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testHandlersNotCalledBeforeFirstHandlingWithAssocArray() + { + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->never()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + + $handler3 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler3->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler3->expects($this->never()) + ->method('handle') + ; + + $logger = new Logger(__METHOD__, array('last' => $handler3, 'second' => $handler2, 'first' => $handler1)); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testBubblingWhenTheHandlerReturnsFalse() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler2); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testNotBubblingWhenTheHandlerReturnsTrue() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler1->expects($this->never()) + ->method('handle') + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(true)) + ; + $logger->pushHandler($handler2); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::isHandling + */ + public function testIsHandling() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + + $logger->pushHandler($handler1); + $this->assertFalse($logger->isHandling(Logger::DEBUG)); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + + $logger->pushHandler($handler2); + $this->assertTrue($logger->isHandling(Logger::DEBUG)); + } + + /** + * @dataProvider logMethodProvider + * @covers Monolog\Logger::addDebug + * @covers Monolog\Logger::addInfo + * @covers Monolog\Logger::addNotice + * @covers Monolog\Logger::addWarning + * @covers Monolog\Logger::addError + * @covers Monolog\Logger::addCritical + * @covers Monolog\Logger::addAlert + * @covers Monolog\Logger::addEmergency + * @covers Monolog\Logger::debug + * @covers Monolog\Logger::info + * @covers Monolog\Logger::notice + * @covers Monolog\Logger::warn + * @covers Monolog\Logger::err + * @covers Monolog\Logger::crit + * @covers Monolog\Logger::alert + * @covers Monolog\Logger::emerg + */ + public function testLogMethods($method, $expectedLevel) + { + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->{$method}('test'); + list($record) = $handler->getRecords(); + $this->assertEquals($expectedLevel, $record['level']); + } + + public function logMethodProvider() + { + return array( + // monolog methods + array('addDebug', Logger::DEBUG), + array('addInfo', Logger::INFO), + array('addNotice', Logger::NOTICE), + array('addWarning', Logger::WARNING), + array('addError', Logger::ERROR), + array('addCritical', Logger::CRITICAL), + array('addAlert', Logger::ALERT), + array('addEmergency', Logger::EMERGENCY), + + // ZF/Sf2 compat methods + array('debug', Logger::DEBUG), + array('info', Logger::INFO), + array('notice', Logger::NOTICE), + array('warn', Logger::WARNING), + array('err', Logger::ERROR), + array('crit', Logger::CRITICAL), + array('alert', Logger::ALERT), + array('emerg', Logger::EMERGENCY), + ); + } + + /** + * @dataProvider setTimezoneProvider + * @covers Monolog\Logger::setTimezone + */ + public function testSetTimezone($tz) + { + Logger::setTimezone($tz); + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->info('test'); + list($record) = $handler->getRecords(); + $this->assertEquals($tz, $record['datetime']->getTimezone()); + } + + public function setTimezoneProvider() + { + return array_map( + function ($tz) { return array(new \DateTimeZone($tz)); }, + \DateTimeZone::listIdentifiers() + ); + } + + /** + * @dataProvider useMicrosecondTimestampsProvider + * @covers Monolog\Logger::useMicrosecondTimestamps + * @covers Monolog\Logger::addRecord + */ + public function testUseMicrosecondTimestamps($micro, $assert) + { + $logger = new Logger('foo'); + $logger->useMicrosecondTimestamps($micro); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->info('test'); + list($record) = $handler->getRecords(); + $this->{$assert}('000000', $record['datetime']->format('u')); + } + + public function useMicrosecondTimestampsProvider() + { + return array( + // this has a very small chance of a false negative (1/10^6) + 'with microseconds' => array(true, 'assertNotSame'), + 'without microseconds' => array(false, PHP_VERSION_ID >= 70100 ? 'assertNotSame' : 'assertSame'), + ); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php b/src/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php new file mode 100644 index 0000000..5adb505 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class GitProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\GitProcessor::__invoke + */ + public function testProcessor() + { + $processor = new GitProcessor(); + $record = $processor($this->getRecord()); + + $this->assertArrayHasKey('git', $record['extra']); + $this->assertTrue(!is_array($record['extra']['git']['branch'])); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php b/src/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php new file mode 100644 index 0000000..0dd411d --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.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 Acme; + +class Tester +{ + public function test($handler, $record) + { + $handler->handle($record); + } +} + +function tester($handler, $record) +{ + $handler->handle($record); +} + +namespace Monolog\Processor; + +use Monolog\Logger; +use Monolog\TestCase; +use Monolog\Handler\TestHandler; + +class IntrospectionProcessorTest extends TestCase +{ + public function getHandler() + { + $processor = new IntrospectionProcessor(); + $handler = new TestHandler(); + $handler->pushProcessor($processor); + + return $handler; + } + + public function testProcessorFromClass() + { + $handler = $this->getHandler(); + $tester = new \Acme\Tester; + $tester->test($handler, $this->getRecord()); + list($record) = $handler->getRecords(); + $this->assertEquals(__FILE__, $record['extra']['file']); + $this->assertEquals(18, $record['extra']['line']); + $this->assertEquals('Acme\Tester', $record['extra']['class']); + $this->assertEquals('test', $record['extra']['function']); + } + + public function testProcessorFromFunc() + { + $handler = $this->getHandler(); + \Acme\tester($handler, $this->getRecord()); + list($record) = $handler->getRecords(); + $this->assertEquals(__FILE__, $record['extra']['file']); + $this->assertEquals(24, $record['extra']['line']); + $this->assertEquals(null, $record['extra']['class']); + $this->assertEquals('Acme\tester', $record['extra']['function']); + } + + public function testLevelTooLow() + { + $input = array( + 'level' => Logger::DEBUG, + 'extra' => array(), + ); + + $expected = $input; + + $processor = new IntrospectionProcessor(Logger::CRITICAL); + $actual = $processor($input); + + $this->assertEquals($expected, $actual); + } + + public function testLevelEqual() + { + $input = array( + 'level' => Logger::CRITICAL, + 'extra' => array(), + ); + + $expected = $input; + $expected['extra'] = array( + 'file' => null, + 'line' => null, + 'class' => 'ReflectionMethod', + 'function' => 'invokeArgs', + ); + + $processor = new IntrospectionProcessor(Logger::CRITICAL); + $actual = $processor($input); + + $this->assertEquals($expected, $actual); + } + + public function testLevelHigher() + { + $input = array( + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ); + + $expected = $input; + $expected['extra'] = array( + 'file' => null, + 'line' => null, + 'class' => 'ReflectionMethod', + 'function' => 'invokeArgs', + ); + + $processor = new IntrospectionProcessor(Logger::CRITICAL); + $actual = $processor($input); + + $this->assertEquals($expected, $actual); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php b/src/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php new file mode 100644 index 0000000..eb66614 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class MemoryPeakUsageProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessor() + { + $processor = new MemoryPeakUsageProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_peak_usage', $record['extra']); + $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_peak_usage']); + } + + /** + * @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessorWithoutFormatting() + { + $processor = new MemoryPeakUsageProcessor(true, false); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_peak_usage', $record['extra']); + $this->assertInternalType('int', $record['extra']['memory_peak_usage']); + $this->assertGreaterThan(0, $record['extra']['memory_peak_usage']); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php b/src/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php new file mode 100644 index 0000000..4692dbf --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class MemoryUsageProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MemoryUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessor() + { + $processor = new MemoryUsageProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_usage', $record['extra']); + $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_usage']); + } + + /** + * @covers Monolog\Processor\MemoryUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessorWithoutFormatting() + { + $processor = new MemoryUsageProcessor(true, false); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_usage', $record['extra']); + $this->assertInternalType('int', $record['extra']['memory_usage']); + $this->assertGreaterThan(0, $record['extra']['memory_usage']); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php b/src/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php new file mode 100644 index 0000000..11f2b35 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class MercurialProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MercurialProcessor::__invoke + */ + public function testProcessor() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + exec("where hg 2>NUL", $output, $result); + } else { + exec("which hg 2>/dev/null >/dev/null", $output, $result); + } + if ($result != 0) { + $this->markTestSkipped('hg is missing'); + return; + } + + `hg init`; + $processor = new MercurialProcessor(); + $record = $processor($this->getRecord()); + + $this->assertArrayHasKey('hg', $record['extra']); + $this->assertTrue(!is_array($record['extra']['hg']['branch'])); + $this->assertTrue(!is_array($record['extra']['hg']['revision'])); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php b/src/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php new file mode 100644 index 0000000..458d2a3 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class ProcessIdProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\ProcessIdProcessor::__invoke + */ + public function testProcessor() + { + $processor = new ProcessIdProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('process_id', $record['extra']); + $this->assertInternalType('int', $record['extra']['process_id']); + $this->assertGreaterThan(0, $record['extra']['process_id']); + $this->assertEquals(getmypid(), $record['extra']['process_id']); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php b/src/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php new file mode 100644 index 0000000..029a0c0 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.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 Monolog\Processor; + +class PsrLogMessageProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getPairs + */ + public function testReplacement($val, $expected) + { + $proc = new PsrLogMessageProcessor; + + $message = $proc(array( + 'message' => '{foo}', + 'context' => array('foo' => $val), + )); + $this->assertEquals($expected, $message['message']); + } + + public function getPairs() + { + return array( + array('foo', 'foo'), + array('3', '3'), + array(3, '3'), + array(null, ''), + array(true, '1'), + array(false, ''), + array(new \stdClass, '[object stdClass]'), + array(array(), '[array]'), + ); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php b/src/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php new file mode 100644 index 0000000..0d860c6 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class TagProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\TagProcessor::__invoke + */ + public function testProcessor() + { + $tags = array(1, 2, 3); + $processor = new TagProcessor($tags); + $record = $processor($this->getRecord()); + + $this->assertEquals($tags, $record['extra']['tags']); + } + + /** + * @covers Monolog\Processor\TagProcessor::__invoke + */ + public function testProcessorTagModification() + { + $tags = array(1, 2, 3); + $processor = new TagProcessor($tags); + + $record = $processor($this->getRecord()); + $this->assertEquals($tags, $record['extra']['tags']); + + $processor->setTags(array('a', 'b')); + $record = $processor($this->getRecord()); + $this->assertEquals(array('a', 'b'), $record['extra']['tags']); + + $processor->addTags(array('a', 'c', 'foo' => 'bar')); + $record = $processor($this->getRecord()); + $this->assertEquals(array('a', 'b', 'a', 'c', 'foo' => 'bar'), $record['extra']['tags']); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php b/src/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php new file mode 100644 index 0000000..5d13058 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class UidProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\UidProcessor::__invoke + */ + public function testProcessor() + { + $processor = new UidProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('uid', $record['extra']); + } + + public function testGetUid() + { + $processor = new UidProcessor(10); + $this->assertEquals(10, strlen($processor->getUid())); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php b/src/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php new file mode 100644 index 0000000..4105baf --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class WebProcessorTest extends TestCase +{ + public function testProcessor() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'HTTP_REFERER' => 'D', + 'SERVER_NAME' => 'F', + 'UNIQUE_ID' => 'G', + ); + + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertEquals($server['REQUEST_URI'], $record['extra']['url']); + $this->assertEquals($server['REMOTE_ADDR'], $record['extra']['ip']); + $this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']); + $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']); + $this->assertEquals($server['SERVER_NAME'], $record['extra']['server']); + $this->assertEquals($server['UNIQUE_ID'], $record['extra']['unique_id']); + } + + public function testProcessorDoNothingIfNoRequestUri() + { + $server = array( + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertEmpty($record['extra']); + } + + public function testProcessorReturnNullIfNoHttpReferer() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertNull($record['extra']['referrer']); + } + + public function testProcessorDoesNotAddUniqueIdIfNotPresent() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertFalse(isset($record['extra']['unique_id'])); + } + + public function testProcessorAddsOnlyRequestedExtraFields() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + + $processor = new WebProcessor($server, array('url', 'http_method')); + $record = $processor($this->getRecord()); + + $this->assertSame(array('url' => 'A', 'http_method' => 'C'), $record['extra']); + } + + public function testProcessorConfiguringOfExtraFields() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + + $processor = new WebProcessor($server, array('url' => 'REMOTE_ADDR')); + $record = $processor($this->getRecord()); + + $this->assertSame(array('url' => 'B'), $record['extra']); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testInvalidData() + { + new WebProcessor(new \stdClass); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php b/src/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php new file mode 100644 index 0000000..ab89944 --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\TestHandler; +use Monolog\Formatter\LineFormatter; +use Monolog\Processor\PsrLogMessageProcessor; +use Psr\Log\Test\LoggerInterfaceTest; + +class PsrLogCompatTest extends LoggerInterfaceTest +{ + private $handler; + + public function getLogger() + { + $logger = new Logger('foo'); + $logger->pushHandler($handler = new TestHandler); + $logger->pushProcessor(new PsrLogMessageProcessor); + $handler->setFormatter(new LineFormatter('%level_name% %message%')); + + $this->handler = $handler; + + return $logger; + } + + public function getLogs() + { + $convert = function ($record) { + $lower = function ($match) { + return strtolower($match[0]); + }; + + return preg_replace_callback('{^[A-Z]+}', $lower, $record['formatted']); + }; + + return array_map($convert, $this->handler->getRecords()); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/RegistryTest.php b/src/vendor/monolog/monolog/tests/Monolog/RegistryTest.php new file mode 100644 index 0000000..15fdfbd --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/RegistryTest.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +class RegistryTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + Registry::clear(); + } + + /** + * @dataProvider hasLoggerProvider + * @covers Monolog\Registry::hasLogger + */ + public function testHasLogger(array $loggersToAdd, array $loggersToCheck, array $expectedResult) + { + foreach ($loggersToAdd as $loggerToAdd) { + Registry::addLogger($loggerToAdd); + } + foreach ($loggersToCheck as $index => $loggerToCheck) { + $this->assertSame($expectedResult[$index], Registry::hasLogger($loggerToCheck)); + } + } + + public function hasLoggerProvider() + { + $logger1 = new Logger('test1'); + $logger2 = new Logger('test2'); + $logger3 = new Logger('test3'); + + return array( + // only instances + array( + array($logger1), + array($logger1, $logger2), + array(true, false), + ), + // only names + array( + array($logger1), + array('test1', 'test2'), + array(true, false), + ), + // mixed case + array( + array($logger1, $logger2), + array('test1', $logger2, 'test3', $logger3), + array(true, true, false, false), + ), + ); + } + + /** + * @covers Monolog\Registry::clear + */ + public function testClearClears() + { + Registry::addLogger(new Logger('test1'), 'log'); + Registry::clear(); + + $this->setExpectedException('\InvalidArgumentException'); + Registry::getInstance('log'); + } + + /** + * @dataProvider removedLoggerProvider + * @covers Monolog\Registry::addLogger + * @covers Monolog\Registry::removeLogger + */ + public function testRemovesLogger($loggerToAdd, $remove) + { + Registry::addLogger($loggerToAdd); + Registry::removeLogger($remove); + + $this->setExpectedException('\InvalidArgumentException'); + Registry::getInstance($loggerToAdd->getName()); + } + + public function removedLoggerProvider() + { + $logger1 = new Logger('test1'); + + return array( + array($logger1, $logger1), + array($logger1, 'test1'), + ); + } + + /** + * @covers Monolog\Registry::addLogger + * @covers Monolog\Registry::getInstance + * @covers Monolog\Registry::__callStatic + */ + public function testGetsSameLogger() + { + $logger1 = new Logger('test1'); + $logger2 = new Logger('test2'); + + Registry::addLogger($logger1, 'test1'); + Registry::addLogger($logger2); + + $this->assertSame($logger1, Registry::getInstance('test1')); + $this->assertSame($logger2, Registry::test2()); + } + + /** + * @expectedException \InvalidArgumentException + * @covers Monolog\Registry::getInstance + */ + public function testFailsOnNonExistantLogger() + { + Registry::getInstance('test1'); + } + + /** + * @covers Monolog\Registry::addLogger + */ + public function testReplacesLogger() + { + $log1 = new Logger('test1'); + $log2 = new Logger('test2'); + + Registry::addLogger($log1, 'log'); + + Registry::addLogger($log2, 'log', true); + + $this->assertSame($log2, Registry::getInstance('log')); + } + + /** + * @expectedException \InvalidArgumentException + * @covers Monolog\Registry::addLogger + */ + public function testFailsOnUnspecifiedReplacement() + { + $log1 = new Logger('test1'); + $log2 = new Logger('test2'); + + Registry::addLogger($log1, 'log'); + + Registry::addLogger($log2, 'log'); + } +} diff --git a/src/vendor/monolog/monolog/tests/Monolog/TestCase.php b/src/vendor/monolog/monolog/tests/Monolog/TestCase.php new file mode 100644 index 0000000..4eb7b4c --- /dev/null +++ b/src/vendor/monolog/monolog/tests/Monolog/TestCase.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 Monolog; + +class TestCase extends \PHPUnit_Framework_TestCase +{ + /** + * @return array Record + */ + protected function getRecord($level = Logger::WARNING, $message = 'test', $context = array()) + { + return array( + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))), + 'extra' => array(), + ); + } + + /** + * @return array + */ + protected function getMultipleRecords() + { + return array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error'), + ); + } + + /** + * @return Monolog\Formatter\FormatterInterface + */ + protected function getIdentityFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message']; })); + + return $formatter; + } +} diff --git a/src/vendor/nikic/fast-route/.hhconfig b/src/vendor/nikic/fast-route/.hhconfig new file mode 100644 index 0000000..0c2153c --- /dev/null +++ b/src/vendor/nikic/fast-route/.hhconfig @@ -0,0 +1 @@ +assume_php=false diff --git a/src/vendor/nikic/fast-route/.travis.yml b/src/vendor/nikic/fast-route/.travis.yml new file mode 100644 index 0000000..e16ed63 --- /dev/null +++ b/src/vendor/nikic/fast-route/.travis.yml @@ -0,0 +1,12 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +matrix: + allow_failures: + - php: 7.0 diff --git a/src/vendor/nikic/fast-route/FastRoute.hhi b/src/vendor/nikic/fast-route/FastRoute.hhi new file mode 100644 index 0000000..c34bb8e --- /dev/null +++ b/src/vendor/nikic/fast-route/FastRoute.hhi @@ -0,0 +1,126 @@ +; + } + + class RouteCollector { + public function __construct(RouteParser $routeParser, DataGenerator $dataGenerator); + public function addRoute(mixed $httpMethod, string $route, mixed $handler): void; + public function getData(): array; + } + + class Route { + public function __construct(string $httpMethod, mixed $handler, string $regex, array $variables); + public function matches(string $str): bool; + } + + interface DataGenerator { + public function addRoute(string $httpMethod, array $routeData, mixed $handler); + public function getData(): array; + } + + interface Dispatcher { + const int NOT_FOUND = 0; + const int FOUND = 1; + const int METHOD_NOT_ALLOWED = 2; + public function dispatch(string $httpMethod, string $uri): array; + } + + function simpleDispatcher( + (function(RouteCollector): void) $routeDefinitionCallback, + shape( + 'routeParser' => ?classname, + 'dataGenerator' => ?classname, + 'dispatcher' => ?classname, + 'routeCollector' => ?classname, + ) $options = shape()): Dispatcher; + + function cachedDispatcher( + (function(RouteCollector): void) $routeDefinitionCallback, + shape( + 'routeParser' => ?classname, + 'dataGenerator' => ?classname, + 'dispatcher' => ?classname, + 'routeCollector' => ?classname, + 'cacheDisabled' => ?bool, + 'cacheFile' => ?string, + ) $options = shape()): Dispatcher; +} + +namespace FastRoute\DataGenerator { + abstract class RegexBasedAbstract implements \FastRoute\DataGenerator { + protected abstract function getApproxChunkSize(); + protected abstract function processChunk($regexToRoutesMap); + + public function addRoute(string $httpMethod, array $routeData, mixed $handler): void; + public function getData(): array; + } + + class CharCountBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } + + class GroupCountBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } + + class GroupPosBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } + + class MarkBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } +} + +namespace FastRoute\Dispatcher { + abstract class RegexBasedAbstract implements \FastRoute\Dispatcher { + protected abstract function dispatchVariableRoute(array $routeData, string $uri): array; + + public function dispatch(string $httpMethod, string $uri): array; + } + + class GroupPosBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } + + class GroupCountBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } + + class CharCountBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } + + class MarkBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } +} + +namespace FastRoute\RouteParser { + class Std implements \FastRoute\RouteParser { + const string VARIABLE_REGEX = <<<'REGEX' +\{ + \s* ([a-zA-Z][a-zA-Z0-9_]*) \s* + (?: + : \s* ([^{}]*(?:\{(?-1)\}[^{}]*)*) + )? +\} +REGEX; + const string DEFAULT_DISPATCH_REGEX = '[^/]+'; + public function parse(string $route): array; + } +} diff --git a/src/vendor/nikic/fast-route/LICENSE b/src/vendor/nikic/fast-route/LICENSE new file mode 100644 index 0000000..478e764 --- /dev/null +++ b/src/vendor/nikic/fast-route/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2013 by Nikita Popov. + +Some 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. + + * The names of the contributors may not 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/src/vendor/nikic/fast-route/README.md b/src/vendor/nikic/fast-route/README.md new file mode 100644 index 0000000..f812a2a --- /dev/null +++ b/src/vendor/nikic/fast-route/README.md @@ -0,0 +1,273 @@ +FastRoute - Fast request router for PHP +======================================= + +This library provides a fast implementation of a regular expression based router. [Blog post explaining how the +implementation works and why it is fast.][blog_post] + +Install +------- + +To install with composer: + +```sh +composer require nikic/fast-route +``` + +Requires PHP 5.4 or newer. + +Usage +----- + +Here's a basic usage example: + +```php +addRoute('GET', '/users', 'get_all_users_handler'); + // {id} must be a number (\d+) + $r->addRoute('GET', '/user/{id:\d+}', 'get_user_handler'); + // The /{title} suffix is optional + $r->addRoute('GET', '/articles/{id:\d+}[/{title}]', 'get_article_handler'); +}); + +// Fetch method and URI from somewhere +$httpMethod = $_SERVER['REQUEST_METHOD']; +$uri = $_SERVER['REQUEST_URI']; + +// Strip query string (?foo=bar) and decode URI +if (false !== $pos = strpos($uri, '?')) { + $uri = substr($uri, 0, $pos); +} +$uri = rawurldecode($uri); + +$routeInfo = $dispatcher->dispatch($httpMethod, $uri); +switch ($routeInfo[0]) { + case FastRoute\Dispatcher::NOT_FOUND: + // ... 404 Not Found + break; + case FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $allowedMethods = $routeInfo[1]; + // ... 405 Method Not Allowed + break; + case FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + $vars = $routeInfo[2]; + // ... call $handler with $vars + break; +} +``` + +### Defining routes + +The routes are defined by calling the `FastRoute\simpleDispatcher()` function, which accepts +a callable taking a `FastRoute\RouteCollector` instance. The routes are added by calling +`addRoute()` on the collector instance: + +```php +$r->addRoute($method, $routePattern, $handler); +``` + +The `$method` is an uppercase HTTP method string for which a certain route should match. It +is possible to specify multiple valid methods using an array: + +```php +// These two calls +$r->addRoute('GET', '/test', 'handler'); +$r->addRoute('POST', '/test', 'handler'); +// Are equivalent to this one call +$r->addRoute(['GET', 'POST'], '/test', 'handler'); +``` + +By default the `$routePattern` uses a syntax where `{foo}` specifies a placeholder with name `foo` +and matching the regex `[^/]+`. To adjust the pattern the placeholder matches, you can specify +a custom pattern by writing `{bar:[0-9]+}`. Some examples: + +```php +// Matches /user/42, but not /user/xyz +$r->addRoute('GET', '/user/{id:\d+}', 'handler'); + +// Matches /user/foobar, but not /user/foo/bar +$r->addRoute('GET', '/user/{name}', 'handler'); + +// Matches /user/foo/bar as well +$r->addRoute('GET', '/user/{name:.+}', 'handler'); +``` + +Custom patterns for route placeholders cannot use capturing groups. For example `{lang:(en|de)}` +is not a valid placeholder, because `()` is a capturing group. Instead you can use either +`{lang:en|de}` or `{lang:(?:en|de)}`. + +Furthermore parts of the route enclosed in `[...]` are considered optional, so that `/foo[bar]` +will match both `/foo` and `/foobar`. Optional parts are only supported in a trailing position, +not in the middle of a route. + +```php +// This route +$r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'handler'); +// Is equivalent to these two routes +$r->addRoute('GET', '/user/{id:\d+}', 'handler'); +$r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler'); + +// Multiple nested optional parts are possible as well +$r->addRoute('GET', '/user[/{id:\d+}[/{name}]]', 'handler'); + +// This route is NOT valid, because optional parts can only occur at the end +$r->addRoute('GET', '/user[/{id:\d+}]/{name}', 'handler'); +``` + +The `$handler` parameter does not necessarily have to be a callback, it could also be a controller +class name or any other kind of data you wish to associate with the route. FastRoute only tells you +which handler corresponds to your URI, how you interpret it is up to you. + +### Caching + +The reason `simpleDispatcher` accepts a callback for defining the routes is to allow seamless +caching. By using `cachedDispatcher` instead of `simpleDispatcher` you can cache the generated +routing data and construct the dispatcher from the cached information: + +```php +addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); +}, [ + 'cacheFile' => __DIR__ . '/route.cache', /* required */ + 'cacheDisabled' => IS_DEBUG_ENABLED, /* optional, enabled by default */ +]); +``` + +The second parameter to the function is an options array, which can be used to specify the cache +file location, among other things. + +### Dispatching a URI + +A URI is dispatched by calling the `dispatch()` method of the created dispatcher. This method +accepts the HTTP method and a URI. Getting those two bits of information (and normalizing them +appropriately) is your job - this library is not bound to the PHP web SAPIs. + +The `dispatch()` method returns an array whose first element contains a status code. It is one +of `Dispatcher::NOT_FOUND`, `Dispatcher::METHOD_NOT_ALLOWED` and `Dispatcher::FOUND`. For the +method not allowed status the second array element contains a list of HTTP methods allowed for +the supplied URI. For example: + + [FastRoute\Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']] + +> **NOTE:** The HTTP specification requires that a `405 Method Not Allowed` response include the +`Allow:` header to detail available methods for the requested resource. Applications using FastRoute +should use the second array element to add this header when relaying a 405 response. + +For the found status the second array element is the handler that was associated with the route +and the third array element is a dictionary of placeholder names to their values. For example: + + /* Routing against GET /user/nikic/42 */ + + [FastRoute\Dispatcher::FOUND, 'handler0', ['name' => 'nikic', 'id' => '42']] + +### Overriding the route parser and dispatcher + +The routing process makes use of three components: A route parser, a data generator and a +dispatcher. The three components adhere to the following interfaces: + +```php + 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', +]); +``` + +The above options array corresponds to the defaults. By replacing `GroupCountBased` by +`GroupPosBased` you could switch to a different dispatching strategy. + +### A Note on HEAD Requests + +The HTTP spec requires servers to [support both GET and HEAD methods][2616-511]: + +> The methods GET and HEAD MUST be supported by all general-purpose servers + +To avoid forcing users to manually register HEAD routes for each resource we fallback to matching an +available GET route for a given resource. The PHP web SAPI transparently removes the entity body +from HEAD responses so this behavior has no effect on the vast majority of users. + +However, implementers using FastRoute outside the web SAPI environment (e.g. a custom server) MUST +NOT send entity bodies generated in response to HEAD requests. If you are a non-SAPI user this is +*your responsibility*; FastRoute has no purview to prevent you from breaking HTTP in such cases. + +Finally, note that applications MAY always specify their own HEAD method route for a given +resource to bypass this behavior entirely. + +### Credits + +This library is based on a router that [Levi Morrison][levi] implemented for the Aerys server. + +A large number of tests, as well as HTTP compliance considerations, were provided by [Daniel Lowrey][rdlowrey]. + + +[2616-511]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 "RFC 2616 Section 5.1.1" +[blog_post]: http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html +[levi]: https://github.com/morrisonlevi +[rdlowrey]: https://github.com/rdlowrey diff --git a/src/vendor/nikic/fast-route/composer.json b/src/vendor/nikic/fast-route/composer.json new file mode 100644 index 0000000..62aad22 --- /dev/null +++ b/src/vendor/nikic/fast-route/composer.json @@ -0,0 +1,21 @@ +{ + "name": "nikic/fast-route", + "description": "Fast request router for PHP", + "keywords": ["routing", "router"], + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "require": { + "php": ">=5.4.0" + }, + "autoload": { + "psr-4": { + "FastRoute\\": "src/" + }, + "files": ["src/functions.php"] + } +} diff --git a/src/vendor/nikic/fast-route/phpunit.xml b/src/vendor/nikic/fast-route/phpunit.xml new file mode 100644 index 0000000..3c807b6 --- /dev/null +++ b/src/vendor/nikic/fast-route/phpunit.xml @@ -0,0 +1,24 @@ + + + + + + ./test/ + + + + + + ./src/ + + + diff --git a/src/vendor/nikic/fast-route/src/BadRouteException.php b/src/vendor/nikic/fast-route/src/BadRouteException.php new file mode 100644 index 0000000..7e38479 --- /dev/null +++ b/src/vendor/nikic/fast-route/src/BadRouteException.php @@ -0,0 +1,6 @@ + $route) { + $suffixLen++; + $suffix .= "\t"; + + $regexes[] = '(?:' . $regex . '/(\t{' . $suffixLen . '})\t{' . ($count - $suffixLen) . '})'; + $routeMap[$suffix] = [$route->handler, $route->variables]; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'suffix' => '/' . $suffix, 'routeMap' => $routeMap]; + } +} diff --git a/src/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php b/src/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php new file mode 100644 index 0000000..d51807f --- /dev/null +++ b/src/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php @@ -0,0 +1,28 @@ + $route) { + $numVariables = count($route->variables); + $numGroups = max($numGroups, $numVariables); + + $regexes[] = $regex . str_repeat('()', $numGroups - $numVariables); + $routeMap[$numGroups + 1] = [$route->handler, $route->variables]; + + ++$numGroups; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} + diff --git a/src/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php b/src/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php new file mode 100644 index 0000000..4152f7a --- /dev/null +++ b/src/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php @@ -0,0 +1,25 @@ + $route) { + $regexes[] = $regex; + $routeMap[$offset] = [$route->handler, $route->variables]; + + $offset += count($route->variables); + } + + $regex = '~^(?:' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} + diff --git a/src/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php b/src/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php new file mode 100644 index 0000000..61359f5 --- /dev/null +++ b/src/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php @@ -0,0 +1,25 @@ + $route) { + $regexes[] = $regex . '(*MARK:' . $markName . ')'; + $routeMap[$markName] = [$route->handler, $route->variables]; + + ++$markName; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} + diff --git a/src/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php b/src/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php new file mode 100644 index 0000000..713d897 --- /dev/null +++ b/src/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php @@ -0,0 +1,144 @@ +isStaticRoute($routeData)) { + $this->addStaticRoute($httpMethod, $routeData, $handler); + } else { + $this->addVariableRoute($httpMethod, $routeData, $handler); + } + } + + public function getData() { + if (empty($this->methodToRegexToRoutesMap)) { + return [$this->staticRoutes, []]; + } + + return [$this->staticRoutes, $this->generateVariableRouteData()]; + } + + private function generateVariableRouteData() { + $data = []; + foreach ($this->methodToRegexToRoutesMap as $method => $regexToRoutesMap) { + $chunkSize = $this->computeChunkSize(count($regexToRoutesMap)); + $chunks = array_chunk($regexToRoutesMap, $chunkSize, true); + $data[$method] = array_map([$this, 'processChunk'], $chunks); + } + return $data; + } + + private function computeChunkSize($count) { + $numParts = max(1, round($count / $this->getApproxChunkSize())); + return ceil($count / $numParts); + } + + private function isStaticRoute($routeData) { + return count($routeData) === 1 && is_string($routeData[0]); + } + + private function addStaticRoute($httpMethod, $routeData, $handler) { + $routeStr = $routeData[0]; + + if (isset($this->staticRoutes[$httpMethod][$routeStr])) { + throw new BadRouteException(sprintf( + 'Cannot register two routes matching "%s" for method "%s"', + $routeStr, $httpMethod + )); + } + + if (isset($this->methodToRegexToRoutesMap[$httpMethod])) { + foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) { + if ($route->matches($routeStr)) { + throw new BadRouteException(sprintf( + 'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"', + $routeStr, $route->regex, $httpMethod + )); + } + } + } + + $this->staticRoutes[$httpMethod][$routeStr] = $handler; + } + + private function addVariableRoute($httpMethod, $routeData, $handler) { + list($regex, $variables) = $this->buildRegexForRoute($routeData); + + if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) { + throw new BadRouteException(sprintf( + 'Cannot register two routes matching "%s" for method "%s"', + $regex, $httpMethod + )); + } + + $this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route( + $httpMethod, $handler, $regex, $variables + ); + } + + private function buildRegexForRoute($routeData) { + $regex = ''; + $variables = []; + foreach ($routeData as $part) { + if (is_string($part)) { + $regex .= preg_quote($part, '~'); + continue; + } + + list($varName, $regexPart) = $part; + + if (isset($variables[$varName])) { + throw new BadRouteException(sprintf( + 'Cannot use the same placeholder "%s" twice', $varName + )); + } + + if ($this->regexHasCapturingGroups($regexPart)) { + throw new BadRouteException(sprintf( + 'Regex "%s" for parameter "%s" contains a capturing group', + $regexPart, $varName + )); + } + + $variables[$varName] = $varName; + $regex .= '(' . $regexPart . ')'; + } + + return [$regex, $variables]; + } + + private function regexHasCapturingGroups($regex) { + if (false === strpos($regex, '(')) { + // Needs to have at least a ( to contain a capturing group + return false; + } + + // Semi-accurate detection for capturing groups + return preg_match( + '~ + (?: + \(\?\( + | \[ [^\]\\\\]* (?: \\\\ . [^\]\\\\]* )* \] + | \\\\ . + ) (*SKIP)(*FAIL) | + \( + (?! + \? (?! <(?![!=]) | P< | \' ) + | \* + ) + ~x', + $regex + ); + } +} diff --git a/src/vendor/nikic/fast-route/src/Dispatcher.php b/src/vendor/nikic/fast-route/src/Dispatcher.php new file mode 100644 index 0000000..ea98009 --- /dev/null +++ b/src/vendor/nikic/fast-route/src/Dispatcher.php @@ -0,0 +1,25 @@ + 'value', ...]] + * + * @param string $httpMethod + * @param string $uri + * + * @return array + */ + public function dispatch($httpMethod, $uri); +} diff --git a/src/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php b/src/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php new file mode 100644 index 0000000..22ba240 --- /dev/null +++ b/src/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php @@ -0,0 +1,28 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri . $data['suffix'], $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][end($matches)]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/src/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php b/src/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php new file mode 100644 index 0000000..0abd322 --- /dev/null +++ b/src/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php @@ -0,0 +1,28 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][count($matches)]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/src/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php b/src/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php new file mode 100644 index 0000000..32227d4 --- /dev/null +++ b/src/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php @@ -0,0 +1,30 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + // find first non-empty match + for ($i = 1; '' === $matches[$i]; ++$i); + + list($handler, $varNames) = $data['routeMap'][$i]; + + $vars = []; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[$i++]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/src/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php b/src/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php new file mode 100644 index 0000000..fefa711 --- /dev/null +++ b/src/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php @@ -0,0 +1,28 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][$matches['MARK']]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/src/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php b/src/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php new file mode 100644 index 0000000..8823b9b --- /dev/null +++ b/src/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php @@ -0,0 +1,80 @@ +staticRouteMap[$httpMethod][$uri])) { + $handler = $this->staticRouteMap[$httpMethod][$uri]; + return [self::FOUND, $handler, []]; + } + + $varRouteData = $this->variableRouteData; + if (isset($varRouteData[$httpMethod])) { + $result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + + // For HEAD requests, attempt fallback to GET + if ($httpMethod === 'HEAD') { + if (isset($this->staticRouteMap['GET'][$uri])) { + $handler = $this->staticRouteMap['GET'][$uri]; + return [self::FOUND, $handler, []]; + } + if (isset($varRouteData['GET'])) { + $result = $this->dispatchVariableRoute($varRouteData['GET'], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + } + + // If nothing else matches, try fallback routes + if (isset($this->staticRouteMap['*'][$uri])) { + $handler = $this->staticRouteMap['*'][$uri]; + return [self::FOUND, $handler, []]; + } + if (isset($varRouteData['*'])) { + $result = $this->dispatchVariableRoute($varRouteData['*'], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + + // Find allowed methods for this URI by matching against all other HTTP methods as well + $allowedMethods = []; + + foreach ($this->staticRouteMap as $method => $uriMap) { + if ($method !== $httpMethod && isset($uriMap[$uri])) { + $allowedMethods[] = $method; + } + } + + foreach ($varRouteData as $method => $routeData) { + if ($method === $httpMethod) { + continue; + } + + $result = $this->dispatchVariableRoute($routeData, $uri); + if ($result[0] === self::FOUND) { + $allowedMethods[] = $method; + } + } + + // If there are no allowed methods the route simply does not exist + if ($allowedMethods) { + return [self::METHOD_NOT_ALLOWED, $allowedMethods]; + } else { + return [self::NOT_FOUND]; + } + } +} diff --git a/src/vendor/nikic/fast-route/src/Route.php b/src/vendor/nikic/fast-route/src/Route.php new file mode 100644 index 0000000..d71ded1 --- /dev/null +++ b/src/vendor/nikic/fast-route/src/Route.php @@ -0,0 +1,38 @@ +httpMethod = $httpMethod; + $this->handler = $handler; + $this->regex = $regex; + $this->variables = $variables; + } + + /** + * Tests whether this route matches the given string. + * + * @param string $str + * + * @return bool + */ + public function matches($str) { + $regex = '~^' . $this->regex . '$~'; + return (bool) preg_match($regex, $str); + } +} + diff --git a/src/vendor/nikic/fast-route/src/RouteCollector.php b/src/vendor/nikic/fast-route/src/RouteCollector.php new file mode 100644 index 0000000..4386bbf --- /dev/null +++ b/src/vendor/nikic/fast-route/src/RouteCollector.php @@ -0,0 +1,46 @@ +routeParser = $routeParser; + $this->dataGenerator = $dataGenerator; + } + + /** + * Adds a route to the collection. + * + * The syntax used in the $route string depends on the used route parser. + * + * @param string|string[] $httpMethod + * @param string $route + * @param mixed $handler + */ + public function addRoute($httpMethod, $route, $handler) { + $routeDatas = $this->routeParser->parse($route); + foreach ((array) $httpMethod as $method) { + foreach ($routeDatas as $routeData) { + $this->dataGenerator->addRoute($method, $routeData, $handler); + } + } + } + + /** + * Returns the collected route data, as provided by the data generator. + * + * @return array + */ + public function getData() { + return $this->dataGenerator->getData(); + } +} diff --git a/src/vendor/nikic/fast-route/src/RouteParser.php b/src/vendor/nikic/fast-route/src/RouteParser.php new file mode 100644 index 0000000..c089c31 --- /dev/null +++ b/src/vendor/nikic/fast-route/src/RouteParser.php @@ -0,0 +1,36 @@ + $segment) { + if ($segment === '' && $n !== 0) { + throw new BadRouteException("Empty optional part"); + } + + $currentRoute .= $segment; + $routeDatas[] = $this->parsePlaceholders($currentRoute); + } + return $routeDatas; + } + + /** + * Parses a route string that does not contain optional segments. + */ + private function parsePlaceholders($route) { + if (!preg_match_all( + '~' . self::VARIABLE_REGEX . '~x', $route, $matches, + PREG_OFFSET_CAPTURE | PREG_SET_ORDER + )) { + return [$route]; + } + + $offset = 0; + $routeData = []; + foreach ($matches as $set) { + if ($set[0][1] > $offset) { + $routeData[] = substr($route, $offset, $set[0][1] - $offset); + } + $routeData[] = [ + $set[1][0], + isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX + ]; + $offset = $set[0][1] + strlen($set[0][0]); + } + + if ($offset != strlen($route)) { + $routeData[] = substr($route, $offset); + } + + return $routeData; + } +} diff --git a/src/vendor/nikic/fast-route/src/bootstrap.php b/src/vendor/nikic/fast-route/src/bootstrap.php new file mode 100644 index 0000000..add216c --- /dev/null +++ b/src/vendor/nikic/fast-route/src/bootstrap.php @@ -0,0 +1,12 @@ + 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', + 'routeCollector' => 'FastRoute\\RouteCollector', + ]; + + /** @var RouteCollector $routeCollector */ + $routeCollector = new $options['routeCollector']( + new $options['routeParser'], new $options['dataGenerator'] + ); + $routeDefinitionCallback($routeCollector); + + return new $options['dispatcher']($routeCollector->getData()); + } + + /** + * @param callable $routeDefinitionCallback + * @param array $options + * + * @return Dispatcher + */ + function cachedDispatcher(callable $routeDefinitionCallback, array $options = []) { + $options += [ + 'routeParser' => 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', + 'routeCollector' => 'FastRoute\\RouteCollector', + 'cacheDisabled' => false, + ]; + + if (!isset($options['cacheFile'])) { + throw new \LogicException('Must specify "cacheFile" option'); + } + + if (!$options['cacheDisabled'] && file_exists($options['cacheFile'])) { + $dispatchData = require $options['cacheFile']; + if (!is_array($dispatchData)) { + throw new \RuntimeException('Invalid cache file "' . $options['cacheFile'] . '"'); + } + return new $options['dispatcher']($dispatchData); + } + + $routeCollector = new $options['routeCollector']( + new $options['routeParser'], new $options['dataGenerator'] + ); + $routeDefinitionCallback($routeCollector); + + /** @var RouteCollector $routeCollector */ + $dispatchData = $routeCollector->getData(); + file_put_contents( + $options['cacheFile'], + ' $this->getDataGeneratorClass(), + 'dispatcher' => $this->getDispatcherClass() + ]; + } + + /** + * @dataProvider provideFoundDispatchCases + */ + public function testFoundDispatches($method, $uri, $callback, $handler, $argDict) { + $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); + $info = $dispatcher->dispatch($method, $uri); + $this->assertSame($dispatcher::FOUND, $info[0]); + $this->assertSame($handler, $info[1]); + $this->assertSame($argDict, $info[2]); + } + + /** + * @dataProvider provideNotFoundDispatchCases + */ + public function testNotFoundDispatches($method, $uri, $callback) { + $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); + $routeInfo = $dispatcher->dispatch($method, $uri); + $this->assertFalse(isset($routeInfo[1]), + "NOT_FOUND result must only contain a single element in the returned info array" + ); + $this->assertSame($dispatcher::NOT_FOUND, $routeInfo[0]); + } + + /** + * @dataProvider provideMethodNotAllowedDispatchCases + */ + public function testMethodNotAllowedDispatches($method, $uri, $callback, $availableMethods) { + $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); + $routeInfo = $dispatcher->dispatch($method, $uri); + $this->assertTrue(isset($routeInfo[1]), + "METHOD_NOT_ALLOWED result must return an array of allowed methods at index 1" + ); + + list($routedStatus, $methodArray) = $dispatcher->dispatch($method, $uri); + $this->assertSame($dispatcher::METHOD_NOT_ALLOWED, $routedStatus); + $this->assertSame($availableMethods, $methodArray); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Cannot use the same placeholder "test" twice + */ + public function testDuplicateVariableNameError() { + \FastRoute\simpleDispatcher(function(RouteCollector $r) { + $r->addRoute('GET', '/foo/{test}/{test:\d+}', 'handler0'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Cannot register two routes matching "/user/([^/]+)" for method "GET" + */ + public function testDuplicateVariableRoute() { + \FastRoute\simpleDispatcher(function(RouteCollector $r) { + $r->addRoute('GET', '/user/{id}', 'handler0'); // oops, forgot \d+ restriction ;) + $r->addRoute('GET', '/user/{name}', 'handler1'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Cannot register two routes matching "/user" for method "GET" + */ + public function testDuplicateStaticRoute() { + \FastRoute\simpleDispatcher(function(RouteCollector $r) { + $r->addRoute('GET', '/user', 'handler0'); + $r->addRoute('GET', '/user', 'handler1'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Static route "/user/nikic" is shadowed by previously defined variable route "/user/([^/]+)" for method "GET" + */ + public function testShadowedStaticRoute() { + \FastRoute\simpleDispatcher(function(RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('GET', '/user/nikic', 'handler1'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Regex "(en|de)" for parameter "lang" contains a capturing group + */ + public function testCapturing() { + \FastRoute\simpleDispatcher(function(RouteCollector $r) { + $r->addRoute('GET', '/{lang:(en|de)}', 'handler0'); + }, $this->generateDispatcherOptions()); + } + + public function provideFoundDispatchCases() { + $cases = []; + + // 0 --------------------------------------------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + }; + + $method = 'GET'; + $uri = '/resource/123/456'; + $handler = 'handler0'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 1 --------------------------------------------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/handler0', 'handler0'); + $r->addRoute('GET', '/handler1', 'handler1'); + $r->addRoute('GET', '/handler2', 'handler2'); + }; + + $method = 'GET'; + $uri = '/handler2'; + $handler = 'handler2'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 2 --------------------------------------------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); + }; + + $method = 'GET'; + $uri = '/user/rdlowrey'; + $handler = 'handler2'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 3 --------------------------------------------------------------------------------------> + + // reuse $callback from #2 + + $method = 'GET'; + $uri = '/user/12345'; + $handler = 'handler1'; + $argDict = ['id' => '12345']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 4 --------------------------------------------------------------------------------------> + + // reuse $callback from #3 + + $method = 'GET'; + $uri = '/user/NaN'; + $handler = 'handler2'; + $argDict = ['name' => 'NaN']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 5 --------------------------------------------------------------------------------------> + + // reuse $callback from #4 + + $method = 'GET'; + $uri = '/user/rdlowrey/12345'; + $handler = 'handler0'; + $argDict = ['name' => 'rdlowrey', 'id' => '12345']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 6 --------------------------------------------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/12345/extension', 'handler1'); + $r->addRoute('GET', '/user/{id:[0-9]+}.{extension}', 'handler2'); + + }; + + $method = 'GET'; + $uri = '/user/12345.svg'; + $handler = 'handler2'; + $argDict = ['id' => '12345', 'extension' => 'svg']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 7 ----- Test GET method fallback on HEAD route miss ------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/static0', 'handler2'); + $r->addRoute('GET', '/static1', 'handler3'); + $r->addRoute('HEAD', '/static1', 'handler4'); + }; + + $method = 'HEAD'; + $uri = '/user/rdlowrey'; + $handler = 'handler0'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 8 ----- Test GET method fallback on HEAD route miss ------------------------------------> + + // reuse $callback from #7 + + $method = 'HEAD'; + $uri = '/user/rdlowrey/1234'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey', 'id' => '1234']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 9 ----- Test GET method fallback on HEAD route miss ------------------------------------> + + // reuse $callback from #8 + + $method = 'HEAD'; + $uri = '/static0'; + $handler = 'handler2'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 10 ---- Test existing HEAD route used if available (no fallback) -----------------------> + + // reuse $callback from #9 + + $method = 'HEAD'; + $uri = '/static1'; + $handler = 'handler4'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 11 ---- More specified routes are not shadowed by less specific of another method ------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1'); + }; + + $method = 'POST'; + $uri = '/user/rdlowrey'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 12 ---- Handler of more specific routes is used, if it occurs first --------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1'); + $r->addRoute('POST', '/user/{name}', 'handler2'); + }; + + $method = 'POST'; + $uri = '/user/rdlowrey'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 13 ---- Route with constant suffix -----------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('GET', '/user/{name}/edit', 'handler1'); + }; + + $method = 'GET'; + $uri = '/user/rdlowrey/edit'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 14 ---- Handle multiple methods with the same handler ----------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost'); + $r->addRoute(['DELETE'], '/user', 'handlerDelete'); + $r->addRoute([], '/user', 'handlerNone'); + }; + + $argDict = []; + $cases[] = ['GET', '/user', $callback, 'handlerGetPost', $argDict]; + $cases[] = ['POST', '/user', $callback, 'handlerGetPost', $argDict]; + $cases[] = ['DELETE', '/user', $callback, 'handlerDelete', $argDict]; + + // 15 ---- + + $callback = function(RouteCollector $r) { + $r->addRoute('POST', '/user.json', 'handler0'); + $r->addRoute('GET', '/{entity}.json', 'handler1'); + }; + + $cases[] = ['GET', '/user.json', $callback, 'handler1', ['entity' => 'user']]; + + // 16 ---- + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '', 'handler0'); + }; + + $cases[] = ['GET', '', $callback, 'handler0', []]; + + // 17 ---- + + $callback = function(RouteCollector $r) { + $r->addRoute('HEAD', '/a/{foo}', 'handler0'); + $r->addRoute('GET', '/b/{foo}', 'handler1'); + }; + + $cases[] = ['HEAD', '/b/bar', $callback, 'handler1', ['foo' => 'bar']]; + + // 18 ---- + + $callback = function(RouteCollector $r) { + $r->addRoute('HEAD', '/a', 'handler0'); + $r->addRoute('GET', '/b', 'handler1'); + }; + + $cases[] = ['HEAD', '/b', $callback, 'handler1', []]; + + // 19 ---- + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/foo', 'handler0'); + $r->addRoute('HEAD', '/{bar}', 'handler1'); + }; + + $cases[] = ['HEAD', '/foo', $callback, 'handler1', ['bar' => 'foo']]; + + // 20 ---- + + $callback = function(RouteCollector $r) { + $r->addRoute('*', '/user', 'handler0'); + $r->addRoute('*', '/{user}', 'handler1'); + $r->addRoute('GET', '/user', 'handler2'); + }; + + $cases[] = ['GET', '/user', $callback, 'handler2', []]; + + // 21 ---- + + $callback = function(RouteCollector $r) { + $r->addRoute('*', '/user', 'handler0'); + $r->addRoute('GET', '/user', 'handler1'); + }; + + $cases[] = ['POST', '/user', $callback, 'handler0', []]; + + // 22 ---- + + $cases[] = ['HEAD', '/user', $callback, 'handler1', []]; + + // 23 ---- + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/{bar}', 'handler0'); + $r->addRoute('*', '/foo', 'handler1'); + }; + + $cases[] = ['GET', '/foo', $callback, 'handler0', ['bar' => 'foo']]; + + // x --------------------------------------------------------------------------------------> + + return $cases; + } + + public function provideNotFoundDispatchCases() { + $cases = []; + + // 0 --------------------------------------------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + }; + + $method = 'GET'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 1 --------------------------------------------------------------------------------------> + + // reuse callback from #0 + $method = 'POST'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 2 --------------------------------------------------------------------------------------> + + // reuse callback from #1 + $method = 'PUT'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 3 --------------------------------------------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/handler0', 'handler0'); + $r->addRoute('GET', '/handler1', 'handler1'); + $r->addRoute('GET', '/handler2', 'handler2'); + }; + + $method = 'GET'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 4 --------------------------------------------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); + }; + + $method = 'GET'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 5 --------------------------------------------------------------------------------------> + + // reuse callback from #4 + $method = 'GET'; + $uri = '/user/rdlowrey/12345/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 6 --------------------------------------------------------------------------------------> + + // reuse callback from #5 + $method = 'HEAD'; + + $cases[] = array($method, $uri, $callback); + + // x --------------------------------------------------------------------------------------> + + return $cases; + } + + public function provideMethodNotAllowedDispatchCases() { + $cases = []; + + // 0 --------------------------------------------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + }; + + $method = 'POST'; + $uri = '/resource/123/456'; + $allowedMethods = ['GET']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 1 --------------------------------------------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + $r->addRoute('POST', '/resource/123/456', 'handler1'); + $r->addRoute('PUT', '/resource/123/456', 'handler2'); + $r->addRoute('*', '/', 'handler3'); + }; + + $method = 'DELETE'; + $uri = '/resource/123/456'; + $allowedMethods = ['GET', 'POST', 'PUT']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 2 --------------------------------------------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('POST', '/user/{name}/{id:[0-9]+}', 'handler1'); + $r->addRoute('PUT', '/user/{name}/{id:[0-9]+}', 'handler2'); + $r->addRoute('PATCH', '/user/{name}/{id:[0-9]+}', 'handler3'); + }; + + $method = 'DELETE'; + $uri = '/user/rdlowrey/42'; + $allowedMethods = ['GET', 'POST', 'PUT', 'PATCH']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 3 --------------------------------------------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute('POST', '/user/{name}', 'handler1'); + $r->addRoute('PUT', '/user/{name:[a-z]+}', 'handler2'); + $r->addRoute('PATCH', '/user/{name:[a-z]+}', 'handler3'); + }; + + $method = 'GET'; + $uri = '/user/rdlowrey'; + $allowedMethods = ['POST', 'PUT', 'PATCH']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 4 --------------------------------------------------------------------------------------> + + $callback = function(RouteCollector $r) { + $r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost'); + $r->addRoute(['DELETE'], '/user', 'handlerDelete'); + $r->addRoute([], '/user', 'handlerNone'); + }; + + $cases[] = ['PUT', '/user', $callback, ['GET', 'POST', 'DELETE']]; + + // 5 + + $callback = function(RouteCollector $r) { + $r->addRoute('POST', '/user.json', 'handler0'); + $r->addRoute('GET', '/{entity}.json', 'handler1'); + }; + + $cases[] = ['PUT', '/user.json', $callback, ['POST', 'GET']]; + + // x --------------------------------------------------------------------------------------> + + return $cases; + } + +} diff --git a/src/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php b/src/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php new file mode 100644 index 0000000..74820fc --- /dev/null +++ b/src/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php @@ -0,0 +1,13 @@ +markTestSkipped('PHP 5.6 required for MARK support'); + } + } + + protected function getDispatcherClass() { + return 'FastRoute\\Dispatcher\\MarkBased'; + } + + protected function getDataGeneratorClass() { + return 'FastRoute\\DataGenerator\\MarkBased'; + } +} diff --git a/src/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php b/src/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php new file mode 100644 index 0000000..7bc6ebb --- /dev/null +++ b/src/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php @@ -0,0 +1,39 @@ +markTestSkipped("HHVM only"); + } + if (!version_compare(HHVM_VERSION, '3.9.0', '>=')) { + $this->markTestSkipped('classname requires HHVM 3.9+'); + } + + // The typechecker recurses the whole tree, so it makes sure + // that everything in fixtures/ is valid when this runs. + + $output = array(); + $exit_code = null; + exec( + 'hh_server --check '.escapeshellarg(__DIR__.'/../../').' 2>&1', + $output, + $exit_code + ); + if ($exit_code === self::SERVER_ALREADY_RUNNING_CODE) { + $this->assertTrue( + $recurse, + "Typechecker still running after running hh_client stop" + ); + // Server already running - 3.10 => 3.11 regression: + // https://github.com/facebook/hhvm/issues/6646 + exec('hh_client stop 2>/dev/null'); + $this->testTypechecks(/* recurse = */ false); + return; + + } + $this->assertSame(0, $exit_code, implode("\n", $output)); + } +} diff --git a/src/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php b/src/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php new file mode 100644 index 0000000..05a9af2 --- /dev/null +++ b/src/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php @@ -0,0 +1,29 @@ + {}, + shape( + 'routeParser' => \FastRoute\RouteParser\Std::class, + 'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class, + 'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class, + 'routeCollector' => \FastRoute\RouteCollector::class, + ), + ); +} + +function all_options_cached(): \FastRoute\Dispatcher { + return \FastRoute\cachedDispatcher( + $collector ==> {}, + shape( + 'routeParser' => \FastRoute\RouteParser\Std::class, + 'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class, + 'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class, + 'routeCollector' => \FastRoute\RouteCollector::class, + 'cacheFile' => '/dev/null', + 'cacheDisabled' => false, + ), + ); +} diff --git a/src/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php b/src/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php new file mode 100644 index 0000000..61eb541 --- /dev/null +++ b/src/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php @@ -0,0 +1,11 @@ + {}, shape()); +} + +function empty_options_cached(): \FastRoute\Dispatcher { + return \FastRoute\cachedDispatcher($collector ==> {}, shape()); +} diff --git a/src/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php b/src/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php new file mode 100644 index 0000000..44b5422 --- /dev/null +++ b/src/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php @@ -0,0 +1,11 @@ + {}); +} + +function no_options_cached(): \FastRoute\Dispatcher { + return \FastRoute\cachedDispatcher($collector ==> {}); +} diff --git a/src/vendor/nikic/fast-route/test/RouteParser/StdTest.php b/src/vendor/nikic/fast-route/test/RouteParser/StdTest.php new file mode 100644 index 0000000..41f194b --- /dev/null +++ b/src/vendor/nikic/fast-route/test/RouteParser/StdTest.php @@ -0,0 +1,147 @@ +parse($routeString); + $this->assertSame($expectedRouteDatas, $routeDatas); + } + + /** @dataProvider provideTestParseError */ + public function testParseError($routeString, $expectedExceptionMessage) { + $parser = new Std(); + $this->setExpectedException('FastRoute\\BadRouteException', $expectedExceptionMessage); + $parser->parse($routeString); + } + + public function provideTestParse() { + return [ + [ + '/test', + [ + ['/test'], + ] + ], + [ + '/test/{param}', + [ + ['/test/', ['param', '[^/]+']], + ] + ], + [ + '/te{ param }st', + [ + ['/te', ['param', '[^/]+'], 'st'] + ] + ], + [ + '/test/{param1}/test2/{param2}', + [ + ['/test/', ['param1', '[^/]+'], '/test2/', ['param2', '[^/]+']] + ] + ], + [ + '/test/{param:\d+}', + [ + ['/test/', ['param', '\d+']] + ] + ], + [ + '/test/{ param : \d{1,9} }', + [ + ['/test/', ['param', '\d{1,9}']] + ] + ], + [ + '/test[opt]', + [ + ['/test'], + ['/testopt'], + ] + ], + [ + '/test[/{param}]', + [ + ['/test'], + ['/test/', ['param', '[^/]+']], + ] + ], + [ + '/{param}[opt]', + [ + ['/', ['param', '[^/]+']], + ['/', ['param', '[^/]+'], 'opt'] + ] + ], + [ + '/test[/{name}[/{id:[0-9]+}]]', + [ + ['/test'], + ['/test/', ['name', '[^/]+']], + ['/test/', ['name', '[^/]+'], '/', ['id', '[0-9]+']], + ] + ], + [ + '', + [ + [''], + ] + ], + [ + '[test]', + [ + [''], + ['test'], + ] + ], + [ + '/{foo-bar}', + [ + ['/', ['foo-bar', '[^/]+']] + ] + ], + [ + '/{_foo:.*}', + [ + ['/', ['_foo', '.*']] + ] + ], + ]; + } + + public function provideTestParseError() { + return [ + [ + '/test[opt', + "Number of opening '[' and closing ']' does not match" + ], + [ + '/test[opt[opt2]', + "Number of opening '[' and closing ']' does not match" + ], + [ + '/testopt]', + "Number of opening '[' and closing ']' does not match" + ], + [ + '/test[]', + "Empty optional part" + ], + [ + '/test[[opt]]', + "Empty optional part" + ], + [ + '[[test]]', + "Empty optional part" + ], + [ + '/test[/opt]/required', + "Optional segments can only occur at the end of a route" + ], + ]; + } +} diff --git a/src/vendor/nikic/fast-route/test/bootstrap.php b/src/vendor/nikic/fast-route/test/bootstrap.php new file mode 100644 index 0000000..27e6d4c --- /dev/null +++ b/src/vendor/nikic/fast-route/test/bootstrap.php @@ -0,0 +1,11 @@ +> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi + +script: + - cd ext/pimple + - if [ "$PIMPLE_EXT" == "yes" ]; then yes n | make test | tee output ; grep -E 'Tests failed +. +0' output; fi + - cd ../.. + - phpunit + +matrix: + exclude: + - php: hhvm + env: PIMPLE_EXT=yes diff --git a/src/vendor/pimple/pimple/CHANGELOG b/src/vendor/pimple/pimple/CHANGELOG new file mode 100644 index 0000000..cc67997 --- /dev/null +++ b/src/vendor/pimple/pimple/CHANGELOG @@ -0,0 +1,35 @@ +* 3.0.2 (2015-09-11) + + * refactored the C extension + * minor non-significant changes + +* 3.0.1 (2015-07-30) + + * simplified some code + * fixed a segfault in the C extension + +* 3.0.0 (2014-07-24) + + * removed the Pimple class alias (use Pimple\Container instead) + +* 2.1.1 (2014-07-24) + + * fixed compiler warnings for the C extension + * fixed code when dealing with circular references + +* 2.1.0 (2014-06-24) + + * moved the Pimple to Pimple\Container (with a BC layer -- Pimple is now a + deprecated alias which will be removed in Pimple 3.0) + * added Pimple\ServiceProviderInterface (and Pimple::register()) + +* 2.0.0 (2014-02-10) + + * changed extend to automatically re-assign the extended service and keep it as shared or factory + (to keep BC, extend still returns the extended service) + * changed services to be shared by default (use factory() for factory + services) + +* 1.0.0 + + * initial version diff --git a/src/vendor/pimple/pimple/LICENSE b/src/vendor/pimple/pimple/LICENSE new file mode 100644 index 0000000..d7949e2 --- /dev/null +++ b/src/vendor/pimple/pimple/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2009-2015 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/pimple/pimple/README.rst b/src/vendor/pimple/pimple/README.rst new file mode 100644 index 0000000..93fb35a --- /dev/null +++ b/src/vendor/pimple/pimple/README.rst @@ -0,0 +1,201 @@ +Pimple +====== + +.. caution:: + + This is the documentation for Pimple 3.x. If you are using Pimple 1.x, read + the `Pimple 1.x documentation`_. Reading the Pimple 1.x code is also a good + way to learn more about how to create a simple Dependency Injection + Container (recent versions of Pimple are more focused on performance). + +Pimple is a small Dependency Injection Container for PHP. + +Installation +------------ + +Before using Pimple in your project, add it to your ``composer.json`` file: + +.. code-block:: bash + + $ ./composer.phar require pimple/pimple ~3.0 + +Alternatively, Pimple is also available as a PHP C extension: + +.. code-block:: bash + + $ git clone https://github.com/silexphp/Pimple + $ cd Pimple/ext/pimple + $ phpize + $ ./configure + $ make + $ make install + +Usage +----- + +Creating a container is a matter of creating a ``Container`` instance: + +.. code-block:: php + + use Pimple\Container; + + $container = new Container(); + +As many other dependency injection containers, Pimple manages two different +kind of data: **services** and **parameters**. + +Defining Services +~~~~~~~~~~~~~~~~~ + +A service is an object that does something as part of a larger system. Examples +of services: a database connection, a templating engine, or a mailer. Almost +any **global** object can be a service. + +Services are defined by **anonymous functions** that return an instance of an +object: + +.. code-block:: php + + // define some services + $container['session_storage'] = function ($c) { + return new SessionStorage('SESSION_ID'); + }; + + $container['session'] = function ($c) { + return new Session($c['session_storage']); + }; + +Notice that the anonymous function has access to the current container +instance, allowing references to other services or parameters. + +As objects are only created when you get them, the order of the definitions +does not matter. + +Using the defined services is also very easy: + +.. code-block:: php + + // get the session object + $session = $container['session']; + + // the above call is roughly equivalent to the following code: + // $storage = new SessionStorage('SESSION_ID'); + // $session = new Session($storage); + +Defining Factory Services +~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, each time you get a service, Pimple returns the **same instance** +of it. If you want a different instance to be returned for all calls, wrap your +anonymous function with the ``factory()`` method + +.. code-block:: php + + $container['session'] = $container->factory(function ($c) { + return new Session($c['session_storage']); + }); + +Now, each call to ``$container['session']`` returns a new instance of the +session. + +Defining Parameters +~~~~~~~~~~~~~~~~~~~ + +Defining a parameter allows to ease the configuration of your container from +the outside and to store global values: + +.. code-block:: php + + // define some parameters + $container['cookie_name'] = 'SESSION_ID'; + $container['session_storage_class'] = 'SessionStorage'; + +If you change the ``session_storage`` service definition like below: + +.. code-block:: php + + $container['session_storage'] = function ($c) { + return new $c['session_storage_class']($c['cookie_name']); + }; + +You can now easily change the cookie name by overriding the +``session_storage_class`` parameter instead of redefining the service +definition. + +Protecting Parameters +~~~~~~~~~~~~~~~~~~~~~ + +Because Pimple sees anonymous functions as service definitions, you need to +wrap anonymous functions with the ``protect()`` method to store them as +parameters: + +.. code-block:: php + + $container['random_func'] = $container->protect(function () { + return rand(); + }); + +Modifying Services after Definition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In some cases you may want to modify a service definition after it has been +defined. You can use the ``extend()`` method to define additional code to be +run on your service just after it is created: + +.. code-block:: php + + $container['session_storage'] = function ($c) { + return new $c['session_storage_class']($c['cookie_name']); + }; + + $container->extend('session_storage', function ($storage, $c) { + $storage->...(); + + return $storage; + }); + +The first argument is the name of the service to extend, the second a function +that gets access to the object instance and the container. + +Extending a Container +~~~~~~~~~~~~~~~~~~~~~ + +If you use the same libraries over and over, you might want to reuse some +services from one project to the next one; package your services into a +**provider** by implementing ``Pimple\ServiceProviderInterface``: + +.. code-block:: php + + use Pimple\Container; + + class FooProvider implements Pimple\ServiceProviderInterface + { + public function register(Container $pimple) + { + // register some services and parameters + // on $pimple + } + } + +Then, register the provider on a Container: + +.. code-block:: php + + $pimple->register(new FooProvider()); + +Fetching the Service Creation Function +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you access an object, Pimple automatically calls the anonymous function +that you defined, which creates the service object for you. If you want to get +raw access to this function, you can use the ``raw()`` method: + +.. code-block:: php + + $container['session'] = function ($c) { + return new Session($c['session_storage']); + }; + + $sessionFunction = $container->raw('session'); + +.. _Pimple 1.x documentation: https://github.com/silexphp/Pimple/tree/1.1 diff --git a/src/vendor/pimple/pimple/composer.json b/src/vendor/pimple/pimple/composer.json new file mode 100644 index 0000000..a5268f1 --- /dev/null +++ b/src/vendor/pimple/pimple/composer.json @@ -0,0 +1,25 @@ +{ + "name": "pimple/pimple", + "type": "library", + "description": "Pimple, a simple Dependency Injection Container", + "keywords": ["dependency injection", "container"], + "homepage": "http://pimple.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-0": { "Pimple": "src/" } + }, + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + } +} diff --git a/src/vendor/pimple/pimple/ext/pimple/.gitignore b/src/vendor/pimple/pimple/ext/pimple/.gitignore new file mode 100644 index 0000000..1861088 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/.gitignore @@ -0,0 +1,30 @@ +*.sw* +.deps +Makefile +Makefile.fragments +Makefile.global +Makefile.objects +acinclude.m4 +aclocal.m4 +build/ +config.cache +config.guess +config.h +config.h.in +config.log +config.nice +config.status +config.sub +configure +configure.in +install-sh +libtool +ltmain.sh +missing +mkinstalldirs +run-tests.php +*.loT +.libs/ +modules/ +*.la +*.lo diff --git a/src/vendor/pimple/pimple/ext/pimple/README.md b/src/vendor/pimple/pimple/ext/pimple/README.md new file mode 100644 index 0000000..7b39eb2 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/README.md @@ -0,0 +1,12 @@ +This is Pimple 2 implemented in C + +* PHP >= 5.3 +* Not tested under Windows, might work + +Install +======= + + > phpize + > ./configure + > make + > make install diff --git a/src/vendor/pimple/pimple/ext/pimple/config.m4 b/src/vendor/pimple/pimple/ext/pimple/config.m4 new file mode 100644 index 0000000..c9ba17d --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/config.m4 @@ -0,0 +1,63 @@ +dnl $Id$ +dnl config.m4 for extension pimple + +dnl Comments in this file start with the string 'dnl'. +dnl Remove where necessary. This file will not work +dnl without editing. + +dnl If your extension references something external, use with: + +dnl PHP_ARG_WITH(pimple, for pimple support, +dnl Make sure that the comment is aligned: +dnl [ --with-pimple Include pimple support]) + +dnl Otherwise use enable: + +PHP_ARG_ENABLE(pimple, whether to enable pimple support, +dnl Make sure that the comment is aligned: +[ --enable-pimple Enable pimple support]) + +if test "$PHP_PIMPLE" != "no"; then + dnl Write more examples of tests here... + + dnl # --with-pimple -> check with-path + dnl SEARCH_PATH="/usr/local /usr" # you might want to change this + dnl SEARCH_FOR="/include/pimple.h" # you most likely want to change this + dnl if test -r $PHP_PIMPLE/$SEARCH_FOR; then # path given as parameter + dnl PIMPLE_DIR=$PHP_PIMPLE + dnl else # search default path list + dnl AC_MSG_CHECKING([for pimple files in default path]) + dnl for i in $SEARCH_PATH ; do + dnl if test -r $i/$SEARCH_FOR; then + dnl PIMPLE_DIR=$i + dnl AC_MSG_RESULT(found in $i) + dnl fi + dnl done + dnl fi + dnl + dnl if test -z "$PIMPLE_DIR"; then + dnl AC_MSG_RESULT([not found]) + dnl AC_MSG_ERROR([Please reinstall the pimple distribution]) + dnl fi + + dnl # --with-pimple -> add include path + dnl PHP_ADD_INCLUDE($PIMPLE_DIR/include) + + dnl # --with-pimple -> check for lib and symbol presence + dnl LIBNAME=pimple # you may want to change this + dnl LIBSYMBOL=pimple # you most likely want to change this + + dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, + dnl [ + dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $PIMPLE_DIR/lib, PIMPLE_SHARED_LIBADD) + dnl AC_DEFINE(HAVE_PIMPLELIB,1,[ ]) + dnl ],[ + dnl AC_MSG_ERROR([wrong pimple lib version or lib not found]) + dnl ],[ + dnl -L$PIMPLE_DIR/lib -lm + dnl ]) + dnl + dnl PHP_SUBST(PIMPLE_SHARED_LIBADD) + + PHP_NEW_EXTENSION(pimple, pimple.c, $ext_shared) +fi diff --git a/src/vendor/pimple/pimple/ext/pimple/config.w32 b/src/vendor/pimple/pimple/ext/pimple/config.w32 new file mode 100644 index 0000000..39857b3 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/config.w32 @@ -0,0 +1,13 @@ +// $Id$ +// vim:ft=javascript + +// If your extension references something external, use ARG_WITH +// ARG_WITH("pimple", "for pimple support", "no"); + +// Otherwise, use ARG_ENABLE +// ARG_ENABLE("pimple", "enable pimple support", "no"); + +if (PHP_PIMPLE != "no") { + EXTENSION("pimple", "pimple.c"); +} + diff --git a/src/vendor/pimple/pimple/ext/pimple/php_pimple.h b/src/vendor/pimple/pimple/ext/pimple/php_pimple.h new file mode 100644 index 0000000..49431f0 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/php_pimple.h @@ -0,0 +1,121 @@ + +/* + * This file is part of Pimple. + * + * Copyright (c) 2014 Fabien Potencier + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PHP_PIMPLE_H +#define PHP_PIMPLE_H + +extern zend_module_entry pimple_module_entry; +#define phpext_pimple_ptr &pimple_module_entry + +#ifdef PHP_WIN32 +# define PHP_PIMPLE_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_PIMPLE_API __attribute__ ((visibility("default"))) +#else +# define PHP_PIMPLE_API +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#define PIMPLE_VERSION "3.0.2" +#define PIMPLE_NS "Pimple" + +#define PIMPLE_DEFAULT_ZVAL_CACHE_NUM 5 +#define PIMPLE_DEFAULT_ZVAL_VALUES_NUM 10 + +zend_module_entry *get_module(void); + +PHP_MINIT_FUNCTION(pimple); +PHP_MINFO_FUNCTION(pimple); + +PHP_METHOD(Pimple, __construct); +PHP_METHOD(Pimple, factory); +PHP_METHOD(Pimple, protect); +PHP_METHOD(Pimple, raw); +PHP_METHOD(Pimple, extend); +PHP_METHOD(Pimple, keys); +PHP_METHOD(Pimple, register); +PHP_METHOD(Pimple, offsetSet); +PHP_METHOD(Pimple, offsetUnset); +PHP_METHOD(Pimple, offsetGet); +PHP_METHOD(Pimple, offsetExists); + +PHP_METHOD(PimpleClosure, invoker); + +typedef struct _pimple_bucket_value { + zval *value; /* Must be the first element */ + zval *raw; + zend_object_handle handle_num; + enum { + PIMPLE_IS_PARAM = 0, + PIMPLE_IS_SERVICE = 2 + } type; + zend_bool initialized; + zend_fcall_info_cache fcc; +} pimple_bucket_value; + +typedef struct _pimple_object { + zend_object zobj; + HashTable values; + HashTable factories; + HashTable protected; +} pimple_object; + +typedef struct _pimple_closure_object { + zend_object zobj; + zval *callable; + zval *factory; +} pimple_closure_object; + +static const char sensiolabs_logo[] = ""; + +static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC); +static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC); + +static void pimple_bucket_dtor(pimple_bucket_value *bucket); +static void pimple_free_bucket(pimple_bucket_value *bucket); + +static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC); +static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC); +static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC); +static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC); +static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC); +static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC); + +static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC); +static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC); +static zend_function *pimple_closure_get_constructor(zval * TSRMLS_DC); +static int pimple_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, union _zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC); + +#ifdef ZTS +#define PIMPLE_G(v) TSRMG(pimple_globals_id, zend_pimple_globals *, v) +#else +#define PIMPLE_G(v) (pimple_globals.v) +#endif + +#endif /* PHP_PIMPLE_H */ + diff --git a/src/vendor/pimple/pimple/ext/pimple/pimple.c b/src/vendor/pimple/pimple/ext/pimple/pimple.c new file mode 100644 index 0000000..239c01d --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/pimple.c @@ -0,0 +1,922 @@ + +/* + * This file is part of Pimple. + * + * Copyright (c) 2014 Fabien Potencier + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_pimple.h" +#include "pimple_compat.h" +#include "zend_interfaces.h" +#include "zend.h" +#include "Zend/zend_closures.h" +#include "ext/spl/spl_exceptions.h" +#include "Zend/zend_exceptions.h" +#include "main/php_output.h" +#include "SAPI.h" + +static zend_class_entry *pimple_ce; +static zend_object_handlers pimple_object_handlers; +static zend_class_entry *pimple_closure_ce; +static zend_class_entry *pimple_serviceprovider_ce; +static zend_object_handlers pimple_closure_object_handlers; +static zend_internal_function pimple_closure_invoker_function; + +#define FETCH_DIM_HANDLERS_VARS pimple_object *pimple_obj = NULL; \ + ulong index; \ + pimple_obj = (pimple_object *)zend_object_store_get_object(object TSRMLS_CC); \ + +#define PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS do { \ + if (ce != pimple_ce) { \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetget"), (void **)&function); \ + if (function->common.scope != ce) { /* if the function is not defined in this actual class */ \ + pimple_object_handlers.read_dimension = pimple_object_read_dimension; /* then overwrite the handler to use custom one */ \ + } \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetset"), (void **)&function); \ + if (function->common.scope != ce) { \ + pimple_object_handlers.write_dimension = pimple_object_write_dimension; \ + } \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetexists"), (void **)&function); \ + if (function->common.scope != ce) { \ + pimple_object_handlers.has_dimension = pimple_object_has_dimension; \ + } \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetunset"), (void **)&function); \ + if (function->common.scope != ce) { \ + pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \ + } \ + } else { \ + pimple_object_handlers.read_dimension = pimple_object_read_dimension; \ + pimple_object_handlers.write_dimension = pimple_object_write_dimension; \ + pimple_object_handlers.has_dimension = pimple_object_has_dimension; \ + pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \ + }\ + } while(0); + +#define PIMPLE_CALL_CB do { \ + zend_fcall_info_argn(&fci TSRMLS_CC, 1, &object); \ + fci.size = sizeof(fci); \ + fci.object_ptr = retval->fcc.object_ptr; \ + fci.function_name = retval->value; \ + fci.no_separation = 1; \ + fci.retval_ptr_ptr = &retval_ptr_ptr; \ +\ + zend_call_function(&fci, &retval->fcc TSRMLS_CC); \ + efree(fci.params); \ + if (EG(exception)) { \ + return EG(uninitialized_zval_ptr); \ + } \ + } while(0); + +ZEND_BEGIN_ARG_INFO_EX(arginfo___construct, 0, 0, 0) +ZEND_ARG_ARRAY_INFO(0, value, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetset, 0, 0, 2) +ZEND_ARG_INFO(0, offset) +ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetget, 0, 0, 1) +ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetexists, 0, 0, 1) +ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetunset, 0, 0, 1) +ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_factory, 0, 0, 1) +ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_protect, 0, 0, 1) +ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_raw, 0, 0, 1) +ZEND_ARG_INFO(0, id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_extend, 0, 0, 2) +ZEND_ARG_INFO(0, id) +ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_register, 0, 0, 1) +ZEND_ARG_OBJ_INFO(0, provider, Pimple\\ServiceProviderInterface, 0) +ZEND_ARG_ARRAY_INFO(0, values, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_serviceprovider_register, 0, 0, 1) +ZEND_ARG_OBJ_INFO(0, pimple, Pimple\\Container, 0) +ZEND_END_ARG_INFO() + +static const zend_function_entry pimple_ce_functions[] = { + PHP_ME(Pimple, __construct, arginfo___construct, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, factory, arginfo_factory, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, protect, arginfo_protect, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, raw, arginfo_raw, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, extend, arginfo_extend, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, keys, arginfo_keys, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, register, arginfo_register, ZEND_ACC_PUBLIC) + + PHP_ME(Pimple, offsetSet, arginfo_offsetset, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, offsetGet, arginfo_offsetget, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, offsetExists, arginfo_offsetexists, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, offsetUnset, arginfo_offsetunset, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static const zend_function_entry pimple_serviceprovider_iface_ce_functions[] = { + PHP_ABSTRACT_ME(ServiceProviderInterface, register, arginfo_serviceprovider_register) + PHP_FE_END +}; + +static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC) +{ + zend_object_std_dtor(&obj->zobj TSRMLS_CC); + if (obj->factory) { + zval_ptr_dtor(&obj->factory); + } + if (obj->callable) { + zval_ptr_dtor(&obj->callable); + } + efree(obj); +} + +static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC) +{ + zend_hash_destroy(&obj->factories); + zend_hash_destroy(&obj->protected); + zend_hash_destroy(&obj->values); + zend_object_std_dtor(&obj->zobj TSRMLS_CC); + efree(obj); +} + +static void pimple_free_bucket(pimple_bucket_value *bucket) +{ + if (bucket->raw) { + zval_ptr_dtor(&bucket->raw); + } +} + +static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC) +{ + zend_object_value retval; + pimple_closure_object *pimple_closure_obj = NULL; + + pimple_closure_obj = ecalloc(1, sizeof(pimple_closure_object)); + ZEND_OBJ_INIT(&pimple_closure_obj->zobj, ce); + + pimple_closure_object_handlers.get_constructor = pimple_closure_get_constructor; + retval.handlers = &pimple_closure_object_handlers; + retval.handle = zend_objects_store_put(pimple_closure_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_closure_free_object_storage, NULL TSRMLS_CC); + + return retval; +} + +static zend_function *pimple_closure_get_constructor(zval *obj TSRMLS_DC) +{ + zend_error(E_ERROR, "Pimple\\ContainerClosure is an internal class and cannot be instantiated"); + + return NULL; +} + +static int pimple_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, union _zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC) +{ + *zobj_ptr = obj; + *ce_ptr = Z_OBJCE_P(obj); + *fptr_ptr = (zend_function *)&pimple_closure_invoker_function; + + return SUCCESS; +} + +static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC) +{ + zend_object_value retval; + pimple_object *pimple_obj = NULL; + zend_function *function = NULL; + + pimple_obj = emalloc(sizeof(pimple_object)); + ZEND_OBJ_INIT(&pimple_obj->zobj, ce); + + PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS + + retval.handlers = &pimple_object_handlers; + retval.handle = zend_objects_store_put(pimple_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_free_object_storage, NULL TSRMLS_CC); + + zend_hash_init(&pimple_obj->factories, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0); + zend_hash_init(&pimple_obj->protected, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0); + zend_hash_init(&pimple_obj->values, PIMPLE_DEFAULT_ZVAL_VALUES_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0); + + return retval; +} + +static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + pimple_bucket_value pimple_value = {0}, *found_value = NULL; + ulong hash; + + pimple_zval_to_pimpleval(value, &pimple_value TSRMLS_CC); + + if (!offset) {/* $p[] = 'foo' when not overloaded */ + zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL); + Z_ADDREF_P(value); + return; + } + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + hash = zend_hash_func(Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + zend_hash_quick_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void **)&found_value); + if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) { + pimple_free_bucket(&pimple_value); + zend_throw_exception_ex(spl_ce_RuntimeException, 0 TSRMLS_CC, "Cannot override frozen service \"%s\".", Z_STRVAL_P(offset)); + return; + } + if (zend_hash_quick_update(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) { + pimple_free_bucket(&pimple_value); + return; + } + Z_ADDREF_P(value); + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + zend_hash_index_find(&pimple_obj->values, index, (void **)&found_value); + if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) { + pimple_free_bucket(&pimple_value); + zend_throw_exception_ex(spl_ce_RuntimeException, 0 TSRMLS_CC, "Cannot override frozen service \"%ld\".", index); + return; + } + if (zend_hash_index_update(&pimple_obj->values, index, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) { + pimple_free_bucket(&pimple_value); + return; + } + Z_ADDREF_P(value); + break; + case IS_NULL: /* $p[] = 'foo' when overloaded */ + zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL); + Z_ADDREF_P(value); + break; + default: + pimple_free_bucket(&pimple_value); + zend_error(E_WARNING, "Unsupported offset type"); + } +} + +static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + zend_symtable_del(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + zend_symtable_del(&pimple_obj->factories, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + zend_symtable_del(&pimple_obj->protected, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + zend_hash_index_del(&pimple_obj->values, index); + zend_hash_index_del(&pimple_obj->factories, index); + zend_hash_index_del(&pimple_obj->protected, index); + break; + default: + zend_error(E_WARNING, "Unsupported offset type"); + } +} + +static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + pimple_bucket_value *retval = NULL; + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == SUCCESS) { + switch (check_empty) { + case 0: /* isset */ + return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;) */ + case 1: /* empty */ + default: + return zend_is_true(retval->value); + } + } + return 0; + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == SUCCESS) { + switch (check_empty) { + case 0: /* isset */ + return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;)*/ + case 1: /* empty */ + default: + return zend_is_true(retval->value); + } + } + return 0; + break; + default: + zend_error(E_WARNING, "Unsupported offset type"); + return 0; + } +} + +static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + pimple_bucket_value *retval = NULL; + zend_fcall_info fci = {0}; + zval *retval_ptr_ptr = NULL; + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == FAILURE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset)); + return EG(uninitialized_zval_ptr); + } + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == FAILURE) { + return EG(uninitialized_zval_ptr); + } + break; + case IS_NULL: /* $p[][3] = 'foo' first dim access */ + return EG(uninitialized_zval_ptr); + break; + default: + zend_error(E_WARNING, "Unsupported offset type"); + return EG(uninitialized_zval_ptr); + } + + if(retval->type == PIMPLE_IS_PARAM) { + return retval->value; + } + + if (zend_hash_index_exists(&pimple_obj->protected, retval->handle_num)) { + /* Service is protected, return the value every time */ + return retval->value; + } + + if (zend_hash_index_exists(&pimple_obj->factories, retval->handle_num)) { + /* Service is a factory, call it everytime and never cache its result */ + PIMPLE_CALL_CB + Z_DELREF_P(retval_ptr_ptr); /* fetch dim addr will increment refcount */ + return retval_ptr_ptr; + } + + if (retval->initialized == 1) { + /* Service has already been called, return its cached value */ + return retval->value; + } + + ALLOC_INIT_ZVAL(retval->raw); + MAKE_COPY_ZVAL(&retval->value, retval->raw); + + PIMPLE_CALL_CB + + retval->initialized = 1; + zval_ptr_dtor(&retval->value); + retval->value = retval_ptr_ptr; + + return retval->value; +} + +static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC) +{ + if (Z_TYPE_P(_zval) != IS_OBJECT) { + return FAILURE; + } + + if (_pimple_bucket_value->fcc.called_scope) { + return SUCCESS; + } + + if (Z_OBJ_HANDLER_P(_zval, get_closure) && Z_OBJ_HANDLER_P(_zval, get_closure)(_zval, &_pimple_bucket_value->fcc.calling_scope, &_pimple_bucket_value->fcc.function_handler, &_pimple_bucket_value->fcc.object_ptr TSRMLS_CC) == SUCCESS) { + _pimple_bucket_value->fcc.called_scope = _pimple_bucket_value->fcc.calling_scope; + return SUCCESS; + } else { + return FAILURE; + } +} + +static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC) +{ + _pimple_bucket_value->value = _zval; + + if (Z_TYPE_P(_zval) != IS_OBJECT) { + return PIMPLE_IS_PARAM; + } + + if (pimple_zval_is_valid_callback(_zval, _pimple_bucket_value TSRMLS_CC) == SUCCESS) { + _pimple_bucket_value->type = PIMPLE_IS_SERVICE; + _pimple_bucket_value->handle_num = Z_OBJ_HANDLE_P(_zval); + } + + return PIMPLE_IS_SERVICE; +} + +static void pimple_bucket_dtor(pimple_bucket_value *bucket) +{ + zval_ptr_dtor(&bucket->value); + pimple_free_bucket(bucket); +} + +PHP_METHOD(Pimple, protect) +{ + zval *protected = NULL; + pimple_object *pobj = NULL; + pimple_bucket_value bucket = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &protected) == FAILURE) { + return; + } + + if (pimple_zval_is_valid_callback(protected, &bucket TSRMLS_CC) == FAILURE) { + pimple_free_bucket(&bucket); + zend_throw_exception(spl_ce_InvalidArgumentException, "Callable is not a Closure or invokable object.", 0 TSRMLS_CC); + return; + } + + pimple_zval_to_pimpleval(protected, &bucket TSRMLS_CC); + pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC); + + if (zend_hash_index_update(&pobj->protected, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) { + Z_ADDREF_P(protected); + RETURN_ZVAL(protected, 1 , 0); + } else { + pimple_free_bucket(&bucket); + } + RETURN_FALSE; +} + +PHP_METHOD(Pimple, raw) +{ + zval *offset = NULL; + pimple_object *pobj = NULL; + pimple_bucket_value *value = NULL; + ulong index; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + pobj = zend_object_store_get_object(getThis() TSRMLS_CC); + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset)); + RETURN_NULL(); + } + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) { + RETURN_NULL(); + } + break; + case IS_NULL: + default: + zend_error(E_WARNING, "Unsupported offset type"); + } + + if (value->raw) { + RETVAL_ZVAL(value->raw, 1, 0); + } else { + RETVAL_ZVAL(value->value, 1, 0); + } +} + +PHP_METHOD(Pimple, extend) +{ + zval *offset = NULL, *callable = NULL, *pimple_closure_obj = NULL; + pimple_bucket_value bucket = {0}, *value = NULL; + pimple_object *pobj = NULL; + pimple_closure_object *pcobj = NULL; + ulong index; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &callable) == FAILURE) { + return; + } + + pobj = zend_object_store_get_object(getThis() TSRMLS_CC); + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset)); + RETURN_NULL(); + } + if (value->type != PIMPLE_IS_SERVICE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" does not contain an object definition.", Z_STRVAL_P(offset)); + RETURN_NULL(); + } + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%ld\" is not defined.", index); + RETURN_NULL(); + } + if (value->type != PIMPLE_IS_SERVICE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%ld\" does not contain an object definition.", index); + RETURN_NULL(); + } + break; + case IS_NULL: + default: + zend_error(E_WARNING, "Unsupported offset type"); + } + + if (pimple_zval_is_valid_callback(callable, &bucket TSRMLS_CC) == FAILURE) { + pimple_free_bucket(&bucket); + zend_throw_exception(spl_ce_InvalidArgumentException, "Extension service definition is not a Closure or invokable object.", 0 TSRMLS_CC); + RETURN_NULL(); + } + pimple_free_bucket(&bucket); + + ALLOC_INIT_ZVAL(pimple_closure_obj); + object_init_ex(pimple_closure_obj, pimple_closure_ce); + + pcobj = zend_object_store_get_object(pimple_closure_obj TSRMLS_CC); + pcobj->callable = callable; + pcobj->factory = value->value; + Z_ADDREF_P(callable); + Z_ADDREF_P(value->value); + + if (zend_hash_index_exists(&pobj->factories, value->handle_num)) { + pimple_zval_to_pimpleval(pimple_closure_obj, &bucket TSRMLS_CC); + zend_hash_index_del(&pobj->factories, value->handle_num); + zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL); + Z_ADDREF_P(pimple_closure_obj); + } + + pimple_object_write_dimension(getThis(), offset, pimple_closure_obj TSRMLS_CC); + + RETVAL_ZVAL(pimple_closure_obj, 1, 1); +} + +PHP_METHOD(Pimple, keys) +{ + HashPosition pos; + pimple_object *pobj = NULL; + zval **value = NULL; + zval *endval = NULL; + char *str_index = NULL; + int str_len; + ulong num_index; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + pobj = zend_object_store_get_object(getThis() TSRMLS_CC); + array_init_size(return_value, zend_hash_num_elements(&pobj->values)); + + zend_hash_internal_pointer_reset_ex(&pobj->values, &pos); + + while(zend_hash_get_current_data_ex(&pobj->values, (void **)&value, &pos) == SUCCESS) { + MAKE_STD_ZVAL(endval); + switch (zend_hash_get_current_key_ex(&pobj->values, &str_index, (uint *)&str_len, &num_index, 0, &pos)) { + case HASH_KEY_IS_STRING: + ZVAL_STRINGL(endval, str_index, str_len - 1, 1); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL); + break; + case HASH_KEY_IS_LONG: + ZVAL_LONG(endval, num_index); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL); + break; + } + zend_hash_move_forward_ex(&pobj->values, &pos); + } +} + +PHP_METHOD(Pimple, factory) +{ + zval *factory = NULL; + pimple_object *pobj = NULL; + pimple_bucket_value bucket = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &factory) == FAILURE) { + return; + } + + if (pimple_zval_is_valid_callback(factory, &bucket TSRMLS_CC) == FAILURE) { + pimple_free_bucket(&bucket); + zend_throw_exception(spl_ce_InvalidArgumentException, "Service definition is not a Closure or invokable object.", 0 TSRMLS_CC); + return; + } + + pimple_zval_to_pimpleval(factory, &bucket TSRMLS_CC); + pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC); + + if (zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) { + Z_ADDREF_P(factory); + RETURN_ZVAL(factory, 1 , 0); + } else { + pimple_free_bucket(&bucket); + } + + RETURN_FALSE; +} + +PHP_METHOD(Pimple, offsetSet) +{ + zval *offset = NULL, *value = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &value) == FAILURE) { + return; + } + + pimple_object_write_dimension(getThis(), offset, value TSRMLS_CC); +} + +PHP_METHOD(Pimple, offsetGet) +{ + zval *offset = NULL, *retval = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + retval = pimple_object_read_dimension(getThis(), offset, 0 TSRMLS_CC); + + RETVAL_ZVAL(retval, 1, 0); +} + +PHP_METHOD(Pimple, offsetUnset) +{ + zval *offset = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + pimple_object_unset_dimension(getThis(), offset TSRMLS_CC); +} + +PHP_METHOD(Pimple, offsetExists) +{ + zval *offset = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + RETVAL_BOOL(pimple_object_has_dimension(getThis(), offset, 1 TSRMLS_CC)); +} + +PHP_METHOD(Pimple, register) +{ + zval *provider; + zval **data; + zval *retval = NULL; + zval key; + + HashTable *array = NULL; + HashPosition pos; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|h", &provider, pimple_serviceprovider_ce, &array) == FAILURE) { + return; + } + + RETVAL_ZVAL(getThis(), 1, 0); + + zend_call_method_with_1_params(&provider, Z_OBJCE_P(provider), NULL, "register", &retval, getThis()); + + if (retval) { + zval_ptr_dtor(&retval); + } + + if (!array) { + return; + } + + zend_hash_internal_pointer_reset_ex(array, &pos); + + while(zend_hash_get_current_data_ex(array, (void **)&data, &pos) == SUCCESS) { + zend_hash_get_current_key_zval_ex(array, &key, &pos); + pimple_object_write_dimension(getThis(), &key, *data TSRMLS_CC); + zend_hash_move_forward_ex(array, &pos); + } +} + +PHP_METHOD(Pimple, __construct) +{ + zval *values = NULL, **pData = NULL, offset; + HashPosition pos; + char *str_index = NULL; + zend_uint str_length; + ulong num_index; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!", &values) == FAILURE || !values) { + return; + } + + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(values), &pos); + while (zend_hash_has_more_elements_ex(Z_ARRVAL_P(values), &pos) == SUCCESS) { + zend_hash_get_current_data_ex(Z_ARRVAL_P(values), (void **)&pData, &pos); + zend_hash_get_current_key_ex(Z_ARRVAL_P(values), &str_index, &str_length, &num_index, 0, &pos); + INIT_ZVAL(offset); + if (zend_hash_get_current_key_type_ex(Z_ARRVAL_P(values), &pos) == HASH_KEY_IS_LONG) { + ZVAL_LONG(&offset, num_index); + } else { + ZVAL_STRINGL(&offset, str_index, (str_length - 1), 0); + } + pimple_object_write_dimension(getThis(), &offset, *pData TSRMLS_CC); + zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos); + } +} + +/* + * This is PHP code snippet handling extend()s calls : + + $extended = function ($c) use ($callable, $factory) { + return $callable($factory($c), $c); + }; + + */ +PHP_METHOD(PimpleClosure, invoker) +{ + pimple_closure_object *pcobj = NULL; + zval *arg = NULL, *retval = NULL, *newretval = NULL; + zend_fcall_info fci = {0}; + zval **args[2]; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg) == FAILURE) { + return; + } + + pcobj = zend_object_store_get_object(getThis() TSRMLS_CC); + + fci.function_name = pcobj->factory; + args[0] = &arg; + zend_fcall_info_argp(&fci TSRMLS_CC, 1, args); + fci.retval_ptr_ptr = &retval; + fci.size = sizeof(fci); + + if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) { + efree(fci.params); + return; /* Should here return default zval */ + } + + efree(fci.params); + memset(&fci, 0, sizeof(fci)); + fci.size = sizeof(fci); + + fci.function_name = pcobj->callable; + args[0] = &retval; + args[1] = &arg; + zend_fcall_info_argp(&fci TSRMLS_CC, 2, args); + fci.retval_ptr_ptr = &newretval; + + if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) { + efree(fci.params); + zval_ptr_dtor(&retval); + return; + } + + efree(fci.params); + zval_ptr_dtor(&retval); + + RETVAL_ZVAL(newretval, 1 ,1); +} + +PHP_MINIT_FUNCTION(pimple) +{ + zend_class_entry tmp_pimple_ce, tmp_pimple_closure_ce, tmp_pimple_serviceprovider_iface_ce; + INIT_NS_CLASS_ENTRY(tmp_pimple_ce, PIMPLE_NS, "Container", pimple_ce_functions); + INIT_NS_CLASS_ENTRY(tmp_pimple_closure_ce, PIMPLE_NS, "ContainerClosure", NULL); + INIT_NS_CLASS_ENTRY(tmp_pimple_serviceprovider_iface_ce, PIMPLE_NS, "ServiceProviderInterface", pimple_serviceprovider_iface_ce_functions); + + tmp_pimple_ce.create_object = pimple_object_create; + tmp_pimple_closure_ce.create_object = pimple_closure_object_create; + + pimple_ce = zend_register_internal_class(&tmp_pimple_ce TSRMLS_CC); + zend_class_implements(pimple_ce TSRMLS_CC, 1, zend_ce_arrayaccess); + + pimple_closure_ce = zend_register_internal_class(&tmp_pimple_closure_ce TSRMLS_CC); + pimple_closure_ce->ce_flags |= ZEND_ACC_FINAL_CLASS; + + pimple_serviceprovider_ce = zend_register_internal_interface(&tmp_pimple_serviceprovider_iface_ce TSRMLS_CC); + + memcpy(&pimple_closure_object_handlers, zend_get_std_object_handlers(), sizeof(*zend_get_std_object_handlers())); + pimple_object_handlers = std_object_handlers; + pimple_closure_object_handlers.get_closure = pimple_closure_get_closure; + + pimple_closure_invoker_function.function_name = "Pimple closure internal invoker"; + pimple_closure_invoker_function.fn_flags |= ZEND_ACC_CLOSURE; + pimple_closure_invoker_function.handler = ZEND_MN(PimpleClosure_invoker); + pimple_closure_invoker_function.num_args = 1; + pimple_closure_invoker_function.required_num_args = 1; + pimple_closure_invoker_function.scope = pimple_closure_ce; + pimple_closure_invoker_function.type = ZEND_INTERNAL_FUNCTION; + pimple_closure_invoker_function.module = &pimple_module_entry; + + return SUCCESS; +} + +PHP_MINFO_FUNCTION(pimple) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "SensioLabs Pimple C support", "enabled"); + php_info_print_table_row(2, "Pimple supported version", PIMPLE_VERSION); + php_info_print_table_end(); + + php_info_print_box_start(0); + php_write((void *)ZEND_STRL("SensioLabs Pimple C support developed by Julien Pauli") TSRMLS_CC); + if (!sapi_module.phpinfo_as_text) { + php_write((void *)ZEND_STRL(sensiolabs_logo) TSRMLS_CC); + } + php_info_print_box_end(); +} + +zend_module_entry pimple_module_entry = { + STANDARD_MODULE_HEADER, + "pimple", + NULL, + PHP_MINIT(pimple), + NULL, + NULL, + NULL, + PHP_MINFO(pimple), + PIMPLE_VERSION, + STANDARD_MODULE_PROPERTIES +}; + +#ifdef COMPILE_DL_PIMPLE +ZEND_GET_MODULE(pimple) +#endif diff --git a/src/vendor/pimple/pimple/ext/pimple/pimple_compat.h b/src/vendor/pimple/pimple/ext/pimple/pimple_compat.h new file mode 100644 index 0000000..d234e17 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/pimple_compat.h @@ -0,0 +1,81 @@ + +/* + * This file is part of Pimple. + * + * Copyright (c) 2014 Fabien Potencier + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PIMPLE_COMPAT_H_ +#define PIMPLE_COMPAT_H_ + +#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */ + +#define PHP_5_0_X_API_NO 220040412 +#define PHP_5_1_X_API_NO 220051025 +#define PHP_5_2_X_API_NO 220060519 +#define PHP_5_3_X_API_NO 220090626 +#define PHP_5_4_X_API_NO 220100525 +#define PHP_5_5_X_API_NO 220121212 +#define PHP_5_6_X_API_NO 220131226 + +#define IS_PHP_56 ZEND_EXTENSION_API_NO == PHP_5_6_X_API_NO +#define IS_AT_LEAST_PHP_56 ZEND_EXTENSION_API_NO >= PHP_5_6_X_API_NO + +#define IS_PHP_55 ZEND_EXTENSION_API_NO == PHP_5_5_X_API_NO +#define IS_AT_LEAST_PHP_55 ZEND_EXTENSION_API_NO >= PHP_5_5_X_API_NO + +#define IS_PHP_54 ZEND_EXTENSION_API_NO == PHP_5_4_X_API_NO +#define IS_AT_LEAST_PHP_54 ZEND_EXTENSION_API_NO >= PHP_5_4_X_API_NO + +#define IS_PHP_53 ZEND_EXTENSION_API_NO == PHP_5_3_X_API_NO +#define IS_AT_LEAST_PHP_53 ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + +#if IS_PHP_53 +#define object_properties_init(obj, ce) do { \ + zend_hash_copy(obj->properties, &ce->default_properties, zval_copy_property_ctor(ce), NULL, sizeof(zval *)); \ + } while (0); +#endif + +#define ZEND_OBJ_INIT(obj, ce) do { \ + zend_object_std_init(obj, ce TSRMLS_CC); \ + object_properties_init((obj), (ce)); \ + } while(0); + +#if IS_PHP_53 || IS_PHP_54 +static void zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos) { + Bucket *p; + + p = pos ? (*pos) : ht->pInternalPointer; + + if (!p) { + Z_TYPE_P(key) = IS_NULL; + } else if (p->nKeyLength) { + Z_TYPE_P(key) = IS_STRING; + Z_STRVAL_P(key) = estrndup(p->arKey, p->nKeyLength - 1); + Z_STRLEN_P(key) = p->nKeyLength - 1; + } else { + Z_TYPE_P(key) = IS_LONG; + Z_LVAL_P(key) = p->h; + } +} +#endif + +#endif /* PIMPLE_COMPAT_H_ */ diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/001.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/001.phpt new file mode 100644 index 0000000..0809ea2 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/001.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test for read_dim/write_dim handlers +--SKIPIF-- + +--FILE-- + + +--EXPECTF-- +foo +42 +foo2 +foo99 +baz +strstr \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/002.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/002.phpt new file mode 100644 index 0000000..7b56d2c --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/002.phpt @@ -0,0 +1,15 @@ +--TEST-- +Test for constructor +--SKIPIF-- + +--FILE-- +'foo')); +var_dump($p[42]); +?> +--EXPECT-- +NULL +string(3) "foo" diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/003.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/003.phpt new file mode 100644 index 0000000..a22cfa3 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/003.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test empty dimensions +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(42) +string(3) "bar" \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/004.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/004.phpt new file mode 100644 index 0000000..1e1d251 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/004.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test has/unset dim handlers +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(42) +NULL +bool(true) +bool(false) +bool(true) +bool(true) \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/005.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/005.phpt new file mode 100644 index 0000000..0479ee0 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/005.phpt @@ -0,0 +1,27 @@ +--TEST-- +Test simple class inheritance +--SKIPIF-- + +--FILE-- +someAttr; +?> +--EXPECT-- +string(3) "hit" +foo +fooAttr \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/006.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/006.phpt new file mode 100644 index 0000000..cfe8a11 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/006.phpt @@ -0,0 +1,51 @@ +--TEST-- +Test complex class inheritance +--SKIPIF-- + +--FILE-- + 'bar', 88 => 'baz'); + +$p = new TestPimple($defaultValues); +$p[42] = 'foo'; +var_dump($p[42]); +var_dump($p[0]); +?> +--EXPECT-- +string(13) "hit offsetset" +string(27) "hit offsetget in TestPimple" +string(25) "hit offsetget in MyPimple" +string(3) "foo" +string(27) "hit offsetget in TestPimple" +string(25) "hit offsetget in MyPimple" +string(3) "baz" \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/007.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/007.phpt new file mode 100644 index 0000000..5aac683 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/007.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test for read_dim/write_dim handlers +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +foo +42 \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/008.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/008.phpt new file mode 100644 index 0000000..db7eeec --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/008.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test frozen services +--SKIPIF-- + +--FILE-- + +--EXPECTF-- diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/009.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/009.phpt new file mode 100644 index 0000000..bb05ea2 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/009.phpt @@ -0,0 +1,13 @@ +--TEST-- +Test service is called as callback, and only once +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/010.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/010.phpt new file mode 100644 index 0000000..badce01 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/010.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test service is called as callback for every callback type +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +callme +called +Foo::bar +array(2) { + [0]=> + string(3) "Foo" + [1]=> + string(3) "bar" +} \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/011.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/011.phpt new file mode 100644 index 0000000..6682ab8 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/011.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test service callback throwing an exception +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +all right! \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/012.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/012.phpt new file mode 100644 index 0000000..4c6ac48 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/012.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test service factory +--SKIPIF-- + +--FILE-- +factory($f = function() { var_dump('called-1'); return 'ret-1';}); + +$p[] = $f; + +$p[] = function () { var_dump('called-2'); return 'ret-2'; }; + +var_dump($p[0]); +var_dump($p[0]); +var_dump($p[1]); +var_dump($p[1]); +?> +--EXPECTF-- +string(8) "called-1" +string(5) "ret-1" +string(8) "called-1" +string(5) "ret-1" +string(8) "called-2" +string(5) "ret-2" +string(5) "ret-2" \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/013.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/013.phpt new file mode 100644 index 0000000..f419958 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/013.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test keys() +--SKIPIF-- + +--FILE-- +keys()); + +$p['foo'] = 'bar'; +$p[] = 'foo'; + +var_dump($p->keys()); + +unset($p['foo']); + +var_dump($p->keys()); +?> +--EXPECTF-- +array(0) { +} +array(2) { + [0]=> + string(3) "foo" + [1]=> + int(0) +} +array(1) { + [0]=> + int(0) +} \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/014.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/014.phpt new file mode 100644 index 0000000..ac93721 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/014.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test raw() +--SKIPIF-- + +--FILE-- +raw('foo')); +var_dump($p[42]); + +unset($p['foo']); + +try { + $p->raw('foo'); + echo "expected exception"; +} catch (InvalidArgumentException $e) { } +--EXPECTF-- +string(8) "called-2" +string(5) "ret-2" +object(Closure)#%i (0) { +} +string(8) "called-2" +string(5) "ret-2" \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/015.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/015.phpt new file mode 100644 index 0000000..314f008 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/015.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test protect() +--SKIPIF-- + +--FILE-- +protect($f); + +var_dump($p['foo']); +--EXPECTF-- +object(Closure)#%i (0) { +} \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/016.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/016.phpt new file mode 100644 index 0000000..e55edb0 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/016.phpt @@ -0,0 +1,24 @@ +--TEST-- +Test extend() +--SKIPIF-- + +--FILE-- +extend(12, function ($w) { var_dump($w); return 'bar'; }); /* $callable in code above */ + +var_dump($c('param')); +--EXPECTF-- +string(5) "param" +string(3) "foo" +string(3) "bar" \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/017.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/017.phpt new file mode 100644 index 0000000..bac23ce --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/017.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test extend() with exception in service extension +--SKIPIF-- + +--FILE-- +extend(12, function ($w) { throw new BadMethodCallException; }); + +try { + $p[12]; + echo "Exception expected"; +} catch (BadMethodCallException $e) { } +--EXPECTF-- diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt new file mode 100644 index 0000000..8f881d6 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test extend() with exception in service factory +--SKIPIF-- + +--FILE-- +extend(12, function ($w) { return 'foobar'; }); + +try { + $p[12]; + echo "Exception expected"; +} catch (BadMethodCallException $e) { } +--EXPECTF-- diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/018.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/018.phpt new file mode 100644 index 0000000..27c12a1 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/018.phpt @@ -0,0 +1,23 @@ +--TEST-- +Test register() +--SKIPIF-- + +--FILE-- +register(new Foo, array(42 => 'bar')); + +var_dump($p[42]); +--EXPECTF-- +object(Pimple\Container)#1 (0) { +} +string(3) "bar" \ No newline at end of file diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/019.phpt b/src/vendor/pimple/pimple/ext/pimple/tests/019.phpt new file mode 100644 index 0000000..28a9aec --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/019.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test register() returns static and is a fluent interface +--SKIPIF-- + +--FILE-- +register(new Foo)); +--EXPECTF-- +bool(true) diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/bench.phpb b/src/vendor/pimple/pimple/ext/pimple/tests/bench.phpb new file mode 100644 index 0000000..8f983e6 --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/bench.phpb @@ -0,0 +1,51 @@ +factory($factory); + +$p['factory'] = $factory; + +echo $p['factory']; +echo $p['factory']; +echo $p['factory']; + +} + +echo microtime(true) - $time; diff --git a/src/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb b/src/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb new file mode 100644 index 0000000..aec541f --- /dev/null +++ b/src/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb @@ -0,0 +1,25 @@ + diff --git a/src/vendor/pimple/pimple/phpunit.xml.dist b/src/vendor/pimple/pimple/phpunit.xml.dist new file mode 100644 index 0000000..5c8d487 --- /dev/null +++ b/src/vendor/pimple/pimple/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + + + ./src/Pimple/Tests + + + diff --git a/src/vendor/pimple/pimple/src/Pimple/Container.php b/src/vendor/pimple/pimple/src/Pimple/Container.php new file mode 100644 index 0000000..c976431 --- /dev/null +++ b/src/vendor/pimple/pimple/src/Pimple/Container.php @@ -0,0 +1,282 @@ +factories = new \SplObjectStorage(); + $this->protected = new \SplObjectStorage(); + + foreach ($values as $key => $value) { + $this->offsetSet($key, $value); + } + } + + /** + * Sets a parameter or an object. + * + * Objects must be defined as Closures. + * + * Allowing any PHP callable leads to difficult to debug problems + * as function names (strings) are callable (creating a function with + * the same name as an existing parameter would break your container). + * + * @param string $id The unique identifier for the parameter or object + * @param mixed $value The value of the parameter or a closure to define an object + * + * @throws \RuntimeException Prevent override of a frozen service + */ + public function offsetSet($id, $value) + { + if (isset($this->frozen[$id])) { + throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id)); + } + + $this->values[$id] = $value; + $this->keys[$id] = true; + } + + /** + * Gets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object + * + * @return mixed The value of the parameter or an object + * + * @throws \InvalidArgumentException if the identifier is not defined + */ + public function offsetGet($id) + { + if (!isset($this->keys[$id])) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + if ( + isset($this->raw[$id]) + || !is_object($this->values[$id]) + || isset($this->protected[$this->values[$id]]) + || !method_exists($this->values[$id], '__invoke') + ) { + return $this->values[$id]; + } + + if (isset($this->factories[$this->values[$id]])) { + return $this->values[$id]($this); + } + + $raw = $this->values[$id]; + $val = $this->values[$id] = $raw($this); + $this->raw[$id] = $raw; + + $this->frozen[$id] = true; + + return $val; + } + + /** + * Checks if a parameter or an object is set. + * + * @param string $id The unique identifier for the parameter or object + * + * @return bool + */ + public function offsetExists($id) + { + return isset($this->keys[$id]); + } + + /** + * Unsets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object + */ + public function offsetUnset($id) + { + if (isset($this->keys[$id])) { + if (is_object($this->values[$id])) { + unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]); + } + + unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]); + } + } + + /** + * Marks a callable as being a factory service. + * + * @param callable $callable A service definition to be used as a factory + * + * @return callable The passed callable + * + * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object + */ + public function factory($callable) + { + if (!method_exists($callable, '__invoke')) { + throw new \InvalidArgumentException('Service definition is not a Closure or invokable object.'); + } + + $this->factories->attach($callable); + + return $callable; + } + + /** + * Protects a callable from being interpreted as a service. + * + * This is useful when you want to store a callable as a parameter. + * + * @param callable $callable A callable to protect from being evaluated + * + * @return callable The passed callable + * + * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object + */ + public function protect($callable) + { + if (!method_exists($callable, '__invoke')) { + throw new \InvalidArgumentException('Callable is not a Closure or invokable object.'); + } + + $this->protected->attach($callable); + + return $callable; + } + + /** + * Gets a parameter or the closure defining an object. + * + * @param string $id The unique identifier for the parameter or object + * + * @return mixed The value of the parameter or the closure defining an object + * + * @throws \InvalidArgumentException if the identifier is not defined + */ + public function raw($id) + { + if (!isset($this->keys[$id])) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + if (isset($this->raw[$id])) { + return $this->raw[$id]; + } + + return $this->values[$id]; + } + + /** + * Extends an object definition. + * + * Useful when you want to extend an existing object definition, + * without necessarily loading that object. + * + * @param string $id The unique identifier for the object + * @param callable $callable A service definition to extend the original + * + * @return callable The wrapped callable + * + * @throws \InvalidArgumentException if the identifier is not defined or not a service definition + */ + public function extend($id, $callable) + { + if (!isset($this->keys[$id])) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id)); + } + + if (!is_object($callable) || !method_exists($callable, '__invoke')) { + throw new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.'); + } + + $factory = $this->values[$id]; + + $extended = function ($c) use ($callable, $factory) { + return $callable($factory($c), $c); + }; + + if (isset($this->factories[$factory])) { + $this->factories->detach($factory); + $this->factories->attach($extended); + } + + return $this[$id] = $extended; + } + + /** + * Returns all defined value names. + * + * @return array An array of value names + */ + public function keys() + { + return array_keys($this->values); + } + + /** + * Registers a service provider. + * + * @param ServiceProviderInterface $provider A ServiceProviderInterface instance + * @param array $values An array of values that customizes the provider + * + * @return static + */ + public function register(ServiceProviderInterface $provider, array $values = array()) + { + $provider->register($this); + + foreach ($values as $key => $value) { + $this[$key] = $value; + } + + return $this; + } +} diff --git a/src/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php b/src/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php new file mode 100644 index 0000000..c004594 --- /dev/null +++ b/src/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php @@ -0,0 +1,46 @@ +value = $value; + + return $service; + } +} diff --git a/src/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php b/src/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php new file mode 100644 index 0000000..33cd4e5 --- /dev/null +++ b/src/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php @@ -0,0 +1,34 @@ +factory(function () { + return new Service(); + }); + } +} diff --git a/src/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php b/src/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php new file mode 100644 index 0000000..d71b184 --- /dev/null +++ b/src/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php @@ -0,0 +1,35 @@ + + */ +class Service +{ + public $value; +} diff --git a/src/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php b/src/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php new file mode 100644 index 0000000..8e5c4c7 --- /dev/null +++ b/src/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php @@ -0,0 +1,76 @@ + + */ +class PimpleServiceProviderInterfaceTest extends \PHPUnit_Framework_TestCase +{ + public function testProvider() + { + $pimple = new Container(); + + $pimpleServiceProvider = new Fixtures\PimpleServiceProvider(); + $pimpleServiceProvider->register($pimple); + + $this->assertEquals('value', $pimple['param']); + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + + $serviceOne = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } + + public function testProviderWithRegisterMethod() + { + $pimple = new Container(); + + $pimple->register(new Fixtures\PimpleServiceProvider(), array( + 'anotherParameter' => 'anotherValue', + )); + + $this->assertEquals('value', $pimple['param']); + $this->assertEquals('anotherValue', $pimple['anotherParameter']); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + + $serviceOne = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } +} diff --git a/src/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php b/src/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php new file mode 100644 index 0000000..918f620 --- /dev/null +++ b/src/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php @@ -0,0 +1,440 @@ + + */ +class PimpleTest extends \PHPUnit_Framework_TestCase +{ + public function testWithString() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + + $this->assertEquals('value', $pimple['param']); + } + + public function testWithClosure() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + } + + public function testServicesShouldBeDifferent() + { + $pimple = new Container(); + $pimple['service'] = $pimple->factory(function () { + return new Fixtures\Service(); + }); + + $serviceOne = $pimple['service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } + + public function testShouldPassContainerAsParameter() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $pimple['container'] = function ($container) { + return $container; + }; + + $this->assertNotSame($pimple, $pimple['service']); + $this->assertSame($pimple, $pimple['container']); + } + + public function testIsset() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + $pimple['null'] = null; + + $this->assertTrue(isset($pimple['param'])); + $this->assertTrue(isset($pimple['service'])); + $this->assertTrue(isset($pimple['null'])); + $this->assertFalse(isset($pimple['non_existent'])); + } + + public function testConstructorInjection() + { + $params = array('param' => 'value'); + $pimple = new Container($params); + + $this->assertSame($params['param'], $pimple['param']); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testOffsetGetValidatesKeyIsPresent() + { + $pimple = new Container(); + echo $pimple['foo']; + } + + public function testOffsetGetHonorsNullValues() + { + $pimple = new Container(); + $pimple['foo'] = null; + $this->assertNull($pimple['foo']); + } + + public function testUnset() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + unset($pimple['param'], $pimple['service']); + $this->assertFalse(isset($pimple['param'])); + $this->assertFalse(isset($pimple['service'])); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testShare($service) + { + $pimple = new Container(); + $pimple['shared_service'] = $service; + + $serviceOne = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertSame($serviceOne, $serviceTwo); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testProtect($service) + { + $pimple = new Container(); + $pimple['protected'] = $pimple->protect($service); + + $this->assertSame($service, $pimple['protected']); + } + + public function testGlobalFunctionNameAsParameterValue() + { + $pimple = new Container(); + $pimple['global_function'] = 'strlen'; + $this->assertSame('strlen', $pimple['global_function']); + } + + public function testRaw() + { + $pimple = new Container(); + $pimple['service'] = $definition = $pimple->factory(function () { return 'foo'; }); + $this->assertSame($definition, $pimple->raw('service')); + } + + public function testRawHonorsNullValues() + { + $pimple = new Container(); + $pimple['foo'] = null; + $this->assertNull($pimple->raw('foo')); + } + + public function testFluentRegister() + { + $pimple = new Container(); + $this->assertSame($pimple, $pimple->register($this->getMock('Pimple\ServiceProviderInterface'))); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testRawValidatesKeyIsPresent() + { + $pimple = new Container(); + $pimple->raw('foo'); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testExtend($service) + { + $pimple = new Container(); + $pimple['shared_service'] = function () { + return new Fixtures\Service(); + }; + $pimple['factory_service'] = $pimple->factory(function () { + return new Fixtures\Service(); + }); + + $pimple->extend('shared_service', $service); + $serviceOne = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + $serviceTwo = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + $this->assertSame($serviceOne, $serviceTwo); + $this->assertSame($serviceOne->value, $serviceTwo->value); + + $pimple->extend('factory_service', $service); + $serviceOne = $pimple['factory_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + $serviceTwo = $pimple['factory_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + $this->assertNotSame($serviceOne, $serviceTwo); + $this->assertNotSame($serviceOne->value, $serviceTwo->value); + } + + public function testExtendDoesNotLeakWithFactories() + { + if (extension_loaded('pimple')) { + $this->markTestSkipped('Pimple extension does not support this test'); + } + $pimple = new Container(); + + $pimple['foo'] = $pimple->factory(function () { return; }); + $pimple['foo'] = $pimple->extend('foo', function ($foo, $pimple) { return; }); + unset($pimple['foo']); + + $p = new \ReflectionProperty($pimple, 'values'); + $p->setAccessible(true); + $this->assertEmpty($p->getValue($pimple)); + + $p = new \ReflectionProperty($pimple, 'factories'); + $p->setAccessible(true); + $this->assertCount(0, $p->getValue($pimple)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testExtendValidatesKeyIsPresent() + { + $pimple = new Container(); + $pimple->extend('foo', function () {}); + } + + public function testKeys() + { + $pimple = new Container(); + $pimple['foo'] = 123; + $pimple['bar'] = 123; + + $this->assertEquals(array('foo', 'bar'), $pimple->keys()); + } + + /** @test */ + public function settingAnInvokableObjectShouldTreatItAsFactory() + { + $pimple = new Container(); + $pimple['invokable'] = new Fixtures\Invokable(); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['invokable']); + } + + /** @test */ + public function settingNonInvokableObjectShouldTreatItAsParameter() + { + $pimple = new Container(); + $pimple['non_invokable'] = new Fixtures\NonInvokable(); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\NonInvokable', $pimple['non_invokable']); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Service definition is not a Closure or invokable object. + */ + public function testFactoryFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple->factory($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Callable is not a Closure or invokable object. + */ + public function testProtectFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple->protect($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" does not contain an object definition. + */ + public function testExtendFailsForKeysNotContainingServiceDefinitions($service) + { + $pimple = new Container(); + $pimple['foo'] = $service; + $pimple->extend('foo', function () {}); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Extension service definition is not a Closure or invokable object. + */ + public function testExtendFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple['foo'] = function () {}; + $pimple->extend('foo', $service); + } + + /** + * Provider for invalid service definitions. + */ + public function badServiceDefinitionProvider() + { + return array( + array(123), + array(new Fixtures\NonInvokable()), + ); + } + + /** + * Provider for service definitions. + */ + public function serviceDefinitionProvider() + { + return array( + array(function ($value) { + $service = new Fixtures\Service(); + $service->value = $value; + + return $service; + }), + array(new Fixtures\Invokable()), + ); + } + + public function testDefiningNewServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['bar'] = function () { + return 'bar'; + }; + $this->assertSame('bar', $pimple['bar']); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Cannot override frozen service "foo". + */ + public function testOverridingServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['foo'] = function () { + return 'bar'; + }; + } + + public function testRemovingServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + unset($pimple['foo']); + $pimple['foo'] = function () { + return 'bar'; + }; + $this->assertSame('bar', $pimple['foo']); + } + + public function testExtendingService() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { + return "$foo.bar"; + }); + $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { + return "$foo.baz"; + }); + $this->assertSame('foo.bar.baz', $pimple['foo']); + } + + public function testExtendingServiceAfterOtherServiceFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $pimple['bar'] = function () { + return 'bar'; + }; + $foo = $pimple['foo']; + + $pimple['bar'] = $pimple->extend('bar', function ($bar, $app) { + return "$bar.baz"; + }); + $this->assertSame('bar.baz', $pimple['bar']); + } +} diff --git a/src/vendor/psr/http-message/CHANGELOG.md b/src/vendor/psr/http-message/CHANGELOG.md new file mode 100644 index 0000000..74b1ef9 --- /dev/null +++ b/src/vendor/psr/http-message/CHANGELOG.md @@ -0,0 +1,36 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.0.1 - 2016-08-06 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Updated all `@return self` annotation references in interfaces to use + `@return static`, which more closelly follows the semantics of the + specification. +- Updated the `MessageInterface::getHeaders()` return annotation to use the + value `string[][]`, indicating the format is a nested array of strings. +- Updated the `@link` annotation for `RequestInterface::withRequestTarget()` + to point to the correct section of RFC 7230. +- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation + to add the parameter name (`$uploadedFiles`). +- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()` + method to correctly reference the method parameter (it was referencing an + incorrect parameter name previously). + +## 1.0.0 - 2016-05-18 + +Initial stable release; reflects accepted PSR-7 specification. diff --git a/src/vendor/psr/http-message/LICENSE b/src/vendor/psr/http-message/LICENSE new file mode 100644 index 0000000..c2d8e45 --- /dev/null +++ b/src/vendor/psr/http-message/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 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/src/vendor/psr/http-message/README.md b/src/vendor/psr/http-message/README.md new file mode 100644 index 0000000..2818533 --- /dev/null +++ b/src/vendor/psr/http-message/README.md @@ -0,0 +1,13 @@ +PSR Http Message +================ + +This repository holds all interfaces/classes/traits related to +[PSR-7](http://www.php-fig.org/psr/psr-7/). + +Note that this is not a HTTP message implementation of its own. It is merely an +interface that describes a HTTP message. See the specification for more details. + +Usage +----- + +We'll certainly need some stuff in here. \ No newline at end of file diff --git a/src/vendor/psr/http-message/composer.json b/src/vendor/psr/http-message/composer.json new file mode 100644 index 0000000..b0d2937 --- /dev/null +++ b/src/vendor/psr/http-message/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/http-message", + "description": "Common interface for HTTP messages", + "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"], + "homepage": "https://github.com/php-fig/http-message", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/src/vendor/psr/http-message/src/MessageInterface.php b/src/vendor/psr/http-message/src/MessageInterface.php new file mode 100644 index 0000000..dd46e5e --- /dev/null +++ b/src/vendor/psr/http-message/src/MessageInterface.php @@ -0,0 +1,187 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name); + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name); + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value); + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value); + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader($name); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(); + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body); +} diff --git a/src/vendor/psr/http-message/src/RequestInterface.php b/src/vendor/psr/http-message/src/RequestInterface.php new file mode 100644 index 0000000..a96d4fd --- /dev/null +++ b/src/vendor/psr/http-message/src/RequestInterface.php @@ -0,0 +1,129 @@ +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(); + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return static + */ + public function withQueryParams(array $query); + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(); + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return static + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles); + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return static + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data); + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes(); + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return static + */ + public function withAttribute($name, $value); + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return static + */ + public function withoutAttribute($name); +} diff --git a/src/vendor/psr/http-message/src/StreamInterface.php b/src/vendor/psr/http-message/src/StreamInterface.php new file mode 100644 index 0000000..f68f391 --- /dev/null +++ b/src/vendor/psr/http-message/src/StreamInterface.php @@ -0,0 +1,158 @@ + + * [user-info@]host[:port] + * + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(); + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(); + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(); + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(); + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(); + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(); + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(); + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return static A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme); + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return static A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null); + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return static A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host); + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return static A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port); + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return static A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path); + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return static A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query); + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return static A new instance with the specified fragment. + */ + public function withFragment($fragment); + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(); +} diff --git a/src/vendor/psr/log/.gitignore b/src/vendor/psr/log/.gitignore new file mode 100644 index 0000000..22d0d82 --- /dev/null +++ b/src/vendor/psr/log/.gitignore @@ -0,0 +1 @@ +vendor diff --git a/src/vendor/psr/log/LICENSE b/src/vendor/psr/log/LICENSE new file mode 100644 index 0000000..474c952 --- /dev/null +++ b/src/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/src/vendor/psr/log/Psr/Log/AbstractLogger.php b/src/vendor/psr/log/Psr/Log/AbstractLogger.php new file mode 100644 index 0000000..90e721a --- /dev/null +++ b/src/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/src/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/src/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000..67f852d --- /dev/null +++ b/src/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/src/vendor/psr/log/Psr/Log/LoggerInterface.php b/src/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000..5ea7243 --- /dev/null +++ b/src/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/src/vendor/psr/log/Psr/Log/NullLogger.php b/src/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000..d8cd682 --- /dev/null +++ b/src/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/src/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/src/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php new file mode 100644 index 0000000..a0391a5 --- /dev/null +++ b/src/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/src/vendor/psr/log/README.md b/src/vendor/psr/log/README.md new file mode 100644 index 0000000..574bc1c --- /dev/null +++ b/src/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/src/vendor/psr/log/composer.json b/src/vendor/psr/log/composer.json new file mode 100644 index 0000000..87934d7 --- /dev/null +++ b/src/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/src/vendor/slim/php-view/.gitignore b/src/vendor/slim/php-view/.gitignore new file mode 100644 index 0000000..e9828c5 --- /dev/null +++ b/src/vendor/slim/php-view/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +composer.lock +phpunit.xml +vendor +.idea +composer.phar \ No newline at end of file diff --git a/src/vendor/slim/php-view/.travis.yml b/src/vendor/slim/php-view/.travis.yml new file mode 100644 index 0000000..2b00b8f --- /dev/null +++ b/src/vendor/slim/php-view/.travis.yml @@ -0,0 +1,11 @@ +language: php + +php: + - 5.5 + - 5.6 + - 7.0 + - hhvm + +before_script: composer install + +script: phpunit --coverage-text --configuration phpunit.xml.dist \ No newline at end of file diff --git a/src/vendor/slim/php-view/LICENSE.md b/src/vendor/slim/php-view/LICENSE.md new file mode 100644 index 0000000..6df4c84 --- /dev/null +++ b/src/vendor/slim/php-view/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Josh Lockhart + +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/src/vendor/slim/php-view/README.md b/src/vendor/slim/php-view/README.md new file mode 100644 index 0000000..09a18af --- /dev/null +++ b/src/vendor/slim/php-view/README.md @@ -0,0 +1,87 @@ +[![Build Status](https://travis-ci.org/geggleto/PHP-View.svg?branch=master)](https://travis-ci.org/geggleto/PHP-View) + +## PHP Renderer + +This is a renderer for rendering PHP view scripts into a PSR-7 Response object. It works well with Slim Framework 3. + + +### Cross-site scripting (XSS) risks + +Note that PHP-View has no built-in mitigation from XSS attacks. It is the developer's responsibility to use `htmlspecialchars()` or a component like [zend-escaper](https://github.com/zendframework/zend-escaper). Alternatively, consider [Twig-View](https://github.com/slimphp/Twig-View). + + + +## Templates +You may use `$this` inside your php templates. `$this` will be the actual PhpRenderer object will allow you to render sub-templates + +## Installation + +Install with [Composer](http://getcomposer.org): + + composer require slim/php-view + + +## Usage with Slim 3 + +```php +use Slim\Views\PhpRenderer; + +include "vendor/autoload.php"; + +$app = new Slim\App(); +$container = $app->getContainer(); +$container['renderer'] = new PhpRenderer("./templates"); + +$app->get('/hello/{name}', function ($request, $response, $args) { + return $this->renderer->render($response, "/hello.php", $args); +}); + +$app->run(); +``` + +## Usage with any PSR-7 Project +```php +//Construct the View +$phpView = new PhpRenderer("./path/to/templates"); + +//Render a Template +$response = $phpView->render(new Response(), "/path/to/template.php", $yourData); +``` + +## Template Variables + +You can now add variables to your renderer that will be available to all templates you render. + +```php +// via the constructor +$templateVariables = [ + "title" => "Title" +]; +$phpView = new PhpRenderer("./path/to/templates", $templateVariables); + +// or setter +$phpView->setAttributes($templateVariables); + +// or individually +$phpView->addAttribute($key, $value); +``` + +Data passed in via `->render()` takes precedence over attributes. +```php +$templateVariables = [ + "title" => "Title" +]; +$phpView = new PhpRenderer("./path/to/templates", $templateVariables); + +//... + +$phpView->render($response, $template, [ + "title" => "My Title" +]); +// In the view above, the $title will be "My Title" and not "Title" +``` + +## Exceptions +`\RuntimeException` - if template does not exist + +`\InvalidArgumentException` - if $data contains 'template' diff --git a/src/vendor/slim/php-view/composer.json b/src/vendor/slim/php-view/composer.json new file mode 100644 index 0000000..c470edf --- /dev/null +++ b/src/vendor/slim/php-view/composer.json @@ -0,0 +1,25 @@ +{ + "name": "slim/php-view", + "type" : "library", + "description": "Render PHP view scripts into a PSR-7 Response object.", + "keywords": ["slim","framework","view","template","php","phtml","renderer"], + "license": "MIT", + "authors": [ + { + "name": "Glenn Eggleton", + "email": "geggleto@gmail.com" + } + ], + "require": { + "psr/http-message": "^1.0" + }, + "autoload": { + "psr-4": { + "Slim\\Views\\": "src" + } + }, + "require-dev": { + "phpunit/phpunit": "^4.8", + "slim/slim" : "^3.0" + } +} diff --git a/src/vendor/slim/php-view/phpunit.xml.dist b/src/vendor/slim/php-view/phpunit.xml.dist new file mode 100644 index 0000000..3f730d7 --- /dev/null +++ b/src/vendor/slim/php-view/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + tests/ + + + + + + src/ + + + \ No newline at end of file diff --git a/src/vendor/slim/php-view/src/PhpRenderer.php b/src/vendor/slim/php-view/src/PhpRenderer.php new file mode 100644 index 0000000..f56927b --- /dev/null +++ b/src/vendor/slim/php-view/src/PhpRenderer.php @@ -0,0 +1,190 @@ +templatePath = rtrim($templatePath, '/\\') . '/'; + $this->attributes = $attributes; + } + + /** + * Render a template + * + * $data cannot contain template as a key + * + * throws RuntimeException if $templatePath . $template does not exist + * + * @param ResponseInterface $response + * @param string $template + * @param array $data + * + * @return ResponseInterface + * + * @throws \InvalidArgumentException + * @throws \RuntimeException + */ + public function render(ResponseInterface $response, $template, array $data = []) + { + $output = $this->fetch($template, $data); + + $response->getBody()->write($output); + + return $response; + } + + /** + * Get the attributes for the renderer + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set the attributes for the renderer + * + * @param array $attributes + */ + public function setAttributes(array $attributes) + { + $this->attributes = $attributes; + } + + /** + * Add an attribute + * + * @param $key + * @param $value + */ + public function addAttribute($key, $value) { + $this->attributes[$key] = $value; + } + + /** + * Retrieve an attribute + * + * @param $key + * @return mixed + */ + public function getAttribute($key) { + if (!isset($this->attributes[$key])) { + return false; + } + + return $this->attributes[$key]; + } + + /** + * Get the template path + * + * @return string + */ + public function getTemplatePath() + { + return $this->templatePath; + } + + /** + * Set the template path + * + * @param string $templatePath + */ + public function setTemplatePath($templatePath) + { + $this->templatePath = rtrim($templatePath, '/\\') . '/'; + } + + /** + * Renders a template and returns the result as a string + * + * cannot contain template as a key + * + * throws RuntimeException if $templatePath . $template does not exist + * + * @param $template + * @param array $data + * + * @return mixed + * + * @throws \InvalidArgumentException + * @throws \RuntimeException + */ + public function fetch($template, array $data = []) { + if (isset($data['template'])) { + throw new \InvalidArgumentException("Duplicate template key found"); + } + + if (!is_file($this->templatePath . $template)) { + throw new \RuntimeException("View cannot render `$template` because the template does not exist"); + } + + + /* + foreach ($data as $k=>$val) { + if (in_array($k, array_keys($this->attributes))) { + throw new \InvalidArgumentException("Duplicate key found in data and renderer attributes. " . $k); + } + } + */ + $data = array_merge($this->attributes, $data); + + try { + ob_start(); + $this->protectedIncludeScope($this->templatePath . $template, $data); + $output = ob_get_clean(); + } catch(\Throwable $e) { // PHP 7+ + ob_end_clean(); + throw $e; + } catch(\Exception $e) { // PHP < 7 + ob_end_clean(); + throw $e; + } + + return $output; + } + + /** + * @param string $template + * @param array $data + */ + protected function protectedIncludeScope ($template, array $data) { + extract($data); + include $template; + } +} diff --git a/src/vendor/slim/php-view/tests/PhpRendererTest.php b/src/vendor/slim/php-view/tests/PhpRendererTest.php new file mode 100644 index 0000000..575c638 --- /dev/null +++ b/src/vendor/slim/php-view/tests/PhpRendererTest.php @@ -0,0 +1,115 @@ +render($response, "testTemplate.php", array("hello" => "Hi")); + + $newResponse->getBody()->rewind(); + + $this->assertEquals("Hi", $newResponse->getBody()->getContents()); + } + + public function testRenderConstructor() { + $renderer = new \Slim\Views\PhpRenderer("tests"); + + $headers = new Headers(); + $body = new Body(fopen('php://temp', 'r+')); + $response = new Response(200, $headers, $body); + + $newResponse = $renderer->render($response, "testTemplate.php", array("hello" => "Hi")); + + $newResponse->getBody()->rewind(); + + $this->assertEquals("Hi", $newResponse->getBody()->getContents()); + } + + public function testAttributeMerging() { + + $renderer = new \Slim\Views\PhpRenderer("tests/", [ + "hello" => "Hello" + ]); + + $headers = new Headers(); + $body = new Body(fopen('php://temp', 'r+')); + $response = new Response(200, $headers, $body); + + $newResponse = $renderer->render($response, "testTemplate.php", [ + "hello" => "Hi" + ]); + $newResponse->getBody()->rewind(); + $this->assertEquals("Hi", $newResponse->getBody()->getContents()); + } + + public function testExceptionInTemplate() { + $renderer = new \Slim\Views\PhpRenderer("tests/"); + + $headers = new Headers(); + $body = new Body(fopen('php://temp', 'r+')); + $response = new Response(200, $headers, $body); + + try { + $newResponse = $renderer->render($response, "testException.php"); + } catch (Throwable $t) { // PHP 7+ + // Simulates an error template + $newResponse = $renderer->render($response, "testTemplate.php", [ + "hello" => "Hi" + ]); + } catch (Exception $e) { // PHP < 7 + // Simulates an error template + $newResponse = $renderer->render($response, "testTemplate.php", [ + "hello" => "Hi" + ]); + } + + $newResponse->getBody()->rewind(); + + $this->assertEquals("Hi", $newResponse->getBody()->getContents()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testExceptionForTemplateInData() { + + $renderer = new \Slim\Views\PhpRenderer("tests/"); + + $headers = new Headers(); + $body = new Body(fopen('php://temp', 'r+')); + $response = new Response(200, $headers, $body); + + $renderer->render($response, "testTemplate.php", [ + "template" => "Hi" + ]); + } + + /** + * @expectedException RuntimeException + */ + public function testTemplateNotFound() { + + $renderer = new \Slim\Views\PhpRenderer("tests/"); + + $headers = new Headers(); + $body = new Body(fopen('php://temp', 'r+')); + $response = new Response(200, $headers, $body); + + $renderer->render($response, "adfadftestTemplate.php", []); + } +} diff --git a/src/vendor/slim/php-view/tests/bootstrap.php b/src/vendor/slim/php-view/tests/bootstrap.php new file mode 100644 index 0000000..2ec4c51 --- /dev/null +++ b/src/vendor/slim/php-view/tests/bootstrap.php @@ -0,0 +1,9 @@ + diff --git a/src/vendor/slim/php-view/tests/testTemplate.php b/src/vendor/slim/php-view/tests/testTemplate.php new file mode 100644 index 0000000..71e8f6d --- /dev/null +++ b/src/vendor/slim/php-view/tests/testTemplate.php @@ -0,0 +1,8 @@ +container = $container; + } + + /** + * Enable access to the DI container by consumers of $app + * + * @return ContainerInterface + */ + public function getContainer() + { + return $this->container; + } + + /** + * Add middleware + * + * This method prepends new middleware to the app's middleware stack. + * + * @param callable|string $callable The callback routine + * + * @return static + */ + public function add($callable) + { + return $this->addMiddleware(new DeferredCallable($callable, $this->container)); + } + + /** + * Calling a non-existant method on App checks to see if there's an item + * in the container that is callable and if so, calls it. + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, $args) + { + if ($this->container->has($method)) { + $obj = $this->container->get($method); + if (is_callable($obj)) { + return call_user_func_array($obj, $args); + } + } + + throw new \BadMethodCallException("Method $method is not a valid method"); + } + + /******************************************************************************** + * Router proxy methods + *******************************************************************************/ + + /** + * Add GET route + * + * @param string $pattern The route URI pattern + * @param callable|string $callable The route callback routine + * + * @return \Slim\Interfaces\RouteInterface + */ + public function get($pattern, $callable) + { + return $this->map(['GET'], $pattern, $callable); + } + + /** + * Add POST route + * + * @param string $pattern The route URI pattern + * @param callable|string $callable The route callback routine + * + * @return \Slim\Interfaces\RouteInterface + */ + public function post($pattern, $callable) + { + return $this->map(['POST'], $pattern, $callable); + } + + /** + * Add PUT route + * + * @param string $pattern The route URI pattern + * @param callable|string $callable The route callback routine + * + * @return \Slim\Interfaces\RouteInterface + */ + public function put($pattern, $callable) + { + return $this->map(['PUT'], $pattern, $callable); + } + + /** + * Add PATCH route + * + * @param string $pattern The route URI pattern + * @param callable|string $callable The route callback routine + * + * @return \Slim\Interfaces\RouteInterface + */ + public function patch($pattern, $callable) + { + return $this->map(['PATCH'], $pattern, $callable); + } + + /** + * Add DELETE route + * + * @param string $pattern The route URI pattern + * @param callable|string $callable The route callback routine + * + * @return \Slim\Interfaces\RouteInterface + */ + public function delete($pattern, $callable) + { + return $this->map(['DELETE'], $pattern, $callable); + } + + /** + * Add OPTIONS route + * + * @param string $pattern The route URI pattern + * @param callable|string $callable The route callback routine + * + * @return \Slim\Interfaces\RouteInterface + */ + public function options($pattern, $callable) + { + return $this->map(['OPTIONS'], $pattern, $callable); + } + + /** + * Add route for any HTTP method + * + * @param string $pattern The route URI pattern + * @param callable|string $callable The route callback routine + * + * @return \Slim\Interfaces\RouteInterface + */ + public function any($pattern, $callable) + { + return $this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $pattern, $callable); + } + + /** + * Add route with multiple methods + * + * @param string[] $methods Numeric array of HTTP method names + * @param string $pattern The route URI pattern + * @param callable|string $callable The route callback routine + * + * @return RouteInterface + */ + public function map(array $methods, $pattern, $callable) + { + if ($callable instanceof Closure) { + $callable = $callable->bindTo($this->container); + } + + $route = $this->container->get('router')->map($methods, $pattern, $callable); + if (is_callable([$route, 'setContainer'])) { + $route->setContainer($this->container); + } + + if (is_callable([$route, 'setOutputBuffering'])) { + $route->setOutputBuffering($this->container->get('settings')['outputBuffering']); + } + + return $route; + } + + /** + * Route Groups + * + * This method accepts a route pattern and a callback. All route + * declarations in the callback will be prepended by the group(s) + * that it is in. + * + * @param string $pattern + * @param callable $callable + * + * @return RouteGroupInterface + */ + public function group($pattern, $callable) + { + /** @var RouteGroup $group */ + $group = $this->container->get('router')->pushGroup($pattern, $callable); + $group->setContainer($this->container); + $group($this); + $this->container->get('router')->popGroup(); + return $group; + } + + /******************************************************************************** + * Runner + *******************************************************************************/ + + /** + * Run application + * + * This method traverses the application middleware stack and then sends the + * resultant Response object to the HTTP client. + * + * @param bool|false $silent + * @return ResponseInterface + * + * @throws Exception + * @throws MethodNotAllowedException + * @throws NotFoundException + */ + public function run($silent = false) + { + $request = $this->container->get('request'); + $response = $this->container->get('response'); + + $response = $this->process($request, $response); + + if (!$silent) { + $this->respond($response); + } + + return $response; + } + + /** + * Process a request + * + * This method traverses the application middleware stack and then returns the + * resultant Response object. + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @return ResponseInterface + * + * @throws Exception + * @throws MethodNotAllowedException + * @throws NotFoundException + */ + public function process(ServerRequestInterface $request, ResponseInterface $response) + { + // Ensure basePath is set + $router = $this->container->get('router'); + if (is_callable([$request->getUri(), 'getBasePath']) && is_callable([$router, 'setBasePath'])) { + $router->setBasePath($request->getUri()->getBasePath()); + } + + // Dispatch the Router first if the setting for this is on + if ($this->container->get('settings')['determineRouteBeforeAppMiddleware'] === true) { + // Dispatch router (note: you won't be able to alter routes after this) + $request = $this->dispatchRouterAndPrepareRoute($request, $router); + } + + // Traverse middleware stack + try { + $response = $this->callMiddlewareStack($request, $response); + } catch (Exception $e) { + $response = $this->handleException($e, $request, $response); + } catch (Throwable $e) { + $response = $this->handlePhpError($e, $request, $response); + } + + $response = $this->finalize($response); + + return $response; + } + + /** + * Send the response the client + * + * @param ResponseInterface $response + */ + public function respond(ResponseInterface $response) + { + // Send response + if (!headers_sent()) { + // Status + header(sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + )); + + // Headers + foreach ($response->getHeaders() as $name => $values) { + foreach ($values as $value) { + header(sprintf('%s: %s', $name, $value), false); + } + } + } + + // Body + if (!$this->isEmptyResponse($response)) { + $body = $response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + $settings = $this->container->get('settings'); + $chunkSize = $settings['responseChunkSize']; + + $contentLength = $response->getHeaderLine('Content-Length'); + if (!$contentLength) { + $contentLength = $body->getSize(); + } + + + if (isset($contentLength)) { + $amountToRead = $contentLength; + while ($amountToRead > 0 && !$body->eof()) { + $data = $body->read(min($chunkSize, $amountToRead)); + echo $data; + + $amountToRead -= strlen($data); + + if (connection_status() != CONNECTION_NORMAL) { + break; + } + } + } else { + while (!$body->eof()) { + echo $body->read($chunkSize); + if (connection_status() != CONNECTION_NORMAL) { + break; + } + } + } + } + } + + /** + * Invoke application + * + * This method implements the middleware interface. It receives + * Request and Response objects, and it returns a Response object + * after compiling the routes registered in the Router and dispatching + * the Request object to the appropriate Route callback routine. + * + * @param ServerRequestInterface $request The most recent Request object + * @param ResponseInterface $response The most recent Response object + * + * @return ResponseInterface + * @throws MethodNotAllowedException + * @throws NotFoundException + */ + public function __invoke(ServerRequestInterface $request, ResponseInterface $response) + { + // Get the route info + $routeInfo = $request->getAttribute('routeInfo'); + + /** @var \Slim\Interfaces\RouterInterface $router */ + $router = $this->container->get('router'); + + // If router hasn't been dispatched or the URI changed then dispatch + if (null === $routeInfo || ($routeInfo['request'] !== [$request->getMethod(), (string) $request->getUri()])) { + $request = $this->dispatchRouterAndPrepareRoute($request, $router); + $routeInfo = $request->getAttribute('routeInfo'); + } + + if ($routeInfo[0] === Dispatcher::FOUND) { + $route = $router->lookupRoute($routeInfo[1]); + return $route->run($request, $response); + } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + if (!$this->container->has('notAllowedHandler')) { + throw new MethodNotAllowedException($request, $response, $routeInfo[1]); + } + /** @var callable $notAllowedHandler */ + $notAllowedHandler = $this->container->get('notAllowedHandler'); + return $notAllowedHandler($request, $response, $routeInfo[1]); + } + + if (!$this->container->has('notFoundHandler')) { + throw new NotFoundException($request, $response); + } + /** @var callable $notFoundHandler */ + $notFoundHandler = $this->container->get('notFoundHandler'); + return $notFoundHandler($request, $response); + } + + /** + * Perform a sub-request from within an application route + * + * This method allows you to prepare and initiate a sub-request, run within + * the context of the current request. This WILL NOT issue a remote HTTP + * request. Instead, it will route the provided URL, method, headers, + * cookies, body, and server variables against the set of registered + * application routes. The result response object is returned. + * + * @param string $method The request method (e.g., GET, POST, PUT, etc.) + * @param string $path The request URI path + * @param string $query The request URI query string + * @param array $headers The request headers (key-value array) + * @param array $cookies The request cookies (key-value array) + * @param string $bodyContent The request body + * @param ResponseInterface $response The response object (optional) + * @return ResponseInterface + */ + public function subRequest( + $method, + $path, + $query = '', + array $headers = [], + array $cookies = [], + $bodyContent = '', + ResponseInterface $response = null + ) { + $env = $this->container->get('environment'); + $uri = Uri::createFromEnvironment($env)->withPath($path)->withQuery($query); + $headers = new Headers($headers); + $serverParams = $env->all(); + $body = new Body(fopen('php://temp', 'r+')); + $body->write($bodyContent); + $body->rewind(); + $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); + + if (!$response) { + $response = $this->container->get('response'); + } + + return $this($request, $response); + } + + /** + * Dispatch the router to find the route. Prepare the route for use. + * + * @param ServerRequestInterface $request + * @param RouterInterface $router + * @return ServerRequestInterface + */ + protected function dispatchRouterAndPrepareRoute(ServerRequestInterface $request, RouterInterface $router) + { + $routeInfo = $router->dispatch($request); + + if ($routeInfo[0] === Dispatcher::FOUND) { + $routeArguments = []; + foreach ($routeInfo[2] as $k => $v) { + $routeArguments[$k] = urldecode($v); + } + + $route = $router->lookupRoute($routeInfo[1]); + $route->prepare($request, $routeArguments); + + // add route to the request's attributes in case a middleware or handler needs access to the route + $request = $request->withAttribute('route', $route); + } + + $routeInfo['request'] = [$request->getMethod(), (string) $request->getUri()]; + + return $request->withAttribute('routeInfo', $routeInfo); + } + + /** + * Finalize response + * + * @param ResponseInterface $response + * @return ResponseInterface + */ + protected function finalize(ResponseInterface $response) + { + // stop PHP sending a Content-Type automatically + ini_set('default_mimetype', ''); + + if ($this->isEmptyResponse($response)) { + return $response->withoutHeader('Content-Type')->withoutHeader('Content-Length'); + } + + // Add Content-Length header if `addContentLengthHeader` setting is set + if (isset($this->container->get('settings')['addContentLengthHeader']) && + $this->container->get('settings')['addContentLengthHeader'] == true) { + if (ob_get_length() > 0) { + throw new \RuntimeException("Unexpected data in output buffer. " . + "Maybe you have characters before an opening getBody()->getSize(); + if ($size !== null && !$response->hasHeader('Content-Length')) { + $response = $response->withHeader('Content-Length', (string) $size); + } + } + + return $response; + } + + /** + * Helper method, which returns true if the provided response must not output a body and false + * if the response could have a body. + * + * @see https://tools.ietf.org/html/rfc7231 + * + * @param ResponseInterface $response + * @return bool + */ + protected function isEmptyResponse(ResponseInterface $response) + { + if (method_exists($response, 'isEmpty')) { + return $response->isEmpty(); + } + + return in_array($response->getStatusCode(), [204, 205, 304]); + } + + /** + * Call relevant handler from the Container if needed. If it doesn't exist, + * then just re-throw. + * + * @param Exception $e + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * + * @return ResponseInterface + * @throws Exception if a handler is needed and not found + */ + protected function handleException(Exception $e, ServerRequestInterface $request, ResponseInterface $response) + { + if ($e instanceof MethodNotAllowedException) { + $handler = 'notAllowedHandler'; + $params = [$e->getRequest(), $e->getResponse(), $e->getAllowedMethods()]; + } elseif ($e instanceof NotFoundException) { + $handler = 'notFoundHandler'; + $params = [$e->getRequest(), $e->getResponse()]; + } elseif ($e instanceof SlimException) { + // This is a Stop exception and contains the response + return $e->getResponse(); + } else { + // Other exception, use $request and $response params + $handler = 'errorHandler'; + $params = [$request, $response, $e]; + } + + if ($this->container->has($handler)) { + $callable = $this->container->get($handler); + // Call the registered handler + return call_user_func_array($callable, $params); + } + + // No handlers found, so just throw the exception + throw $e; + } + + /** + * Call relevant handler from the Container if needed. If it doesn't exist, + * then just re-throw. + * + * @param Throwable $e + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @return ResponseInterface + * @throws Throwable + */ + protected function handlePhpError(Throwable $e, ServerRequestInterface $request, ResponseInterface $response) + { + $handler = 'phpErrorHandler'; + $params = [$request, $response, $e]; + + if ($this->container->has($handler)) { + $callable = $this->container->get($handler); + // Call the registered handler + return call_user_func_array($callable, $params); + } + + // No handlers found, so just throw the exception + throw $e; + } +} diff --git a/src/vendor/slim/slim/Slim/CallableResolver.php b/src/vendor/slim/slim/Slim/CallableResolver.php new file mode 100644 index 0000000..169ac48 --- /dev/null +++ b/src/vendor/slim/slim/Slim/CallableResolver.php @@ -0,0 +1,90 @@ +container = $container; + } + + /** + * Resolve toResolve into a closure that that the router can dispatch. + * + * If toResolve is of the format 'class:method', then try to extract 'class' + * from the container otherwise instantiate it and then dispatch 'method'. + * + * @param mixed $toResolve + * + * @return callable + * + * @throws RuntimeException if the callable does not exist + * @throws RuntimeException if the callable is not resolvable + */ + public function resolve($toResolve) + { + $resolved = $toResolve; + + if (!is_callable($toResolve) && is_string($toResolve)) { + // check for slim callable as "class:method" + $callablePattern = '!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!'; + if (preg_match($callablePattern, $toResolve, $matches)) { + $class = $matches[1]; + $method = $matches[2]; + + if ($this->container->has($class)) { + $resolved = [$this->container->get($class), $method]; + } else { + if (!class_exists($class)) { + throw new RuntimeException(sprintf('Callable %s does not exist', $class)); + } + $resolved = [new $class($this->container), $method]; + } + } else { + // check if string is something in the DIC that's callable or is a class name which + // has an __invoke() method + $class = $toResolve; + if ($this->container->has($class)) { + $resolved = $this->container->get($class); + } else { + if (!class_exists($class)) { + throw new RuntimeException(sprintf('Callable %s does not exist', $class)); + } + $resolved = new $class($this->container); + } + } + } + + if (!is_callable($resolved)) { + throw new RuntimeException(sprintf( + '%s is not resolvable', + is_array($toResolve) || is_object($toResolve) ? json_encode($toResolve) : $toResolve + )); + } + + return $resolved; + } +} diff --git a/src/vendor/slim/slim/Slim/CallableResolverAwareTrait.php b/src/vendor/slim/slim/Slim/CallableResolverAwareTrait.php new file mode 100644 index 0000000..f7ff485 --- /dev/null +++ b/src/vendor/slim/slim/Slim/CallableResolverAwareTrait.php @@ -0,0 +1,47 @@ +container instanceof ContainerInterface) { + return $callable; + } + + /** @var CallableResolverInterface $resolver */ + $resolver = $this->container->get('callableResolver'); + + return $resolver->resolve($callable); + } +} diff --git a/src/vendor/slim/slim/Slim/Collection.php b/src/vendor/slim/slim/Slim/Collection.php new file mode 100644 index 0000000..5d19734 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Collection.php @@ -0,0 +1,202 @@ +replace($items); + } + + /******************************************************************************** + * Collection interface + *******************************************************************************/ + + /** + * Set collection item + * + * @param string $key The data key + * @param mixed $value The data value + */ + public function set($key, $value) + { + $this->data[$key] = $value; + } + + /** + * Get collection item for key + * + * @param string $key The data key + * @param mixed $default The default value to return if data key does not exist + * + * @return mixed The key's value, or the default value + */ + public function get($key, $default = null) + { + return $this->has($key) ? $this->data[$key] : $default; + } + + /** + * Add item to collection, replacing existing items with the same data key + * + * @param array $items Key-value array of data to append to this collection + */ + public function replace(array $items) + { + foreach ($items as $key => $value) { + $this->set($key, $value); + } + } + + /** + * Get all items in collection + * + * @return array The collection's source data + */ + public function all() + { + return $this->data; + } + + /** + * Get collection keys + * + * @return array The collection's source data keys + */ + public function keys() + { + return array_keys($this->data); + } + + /** + * Does this collection have a given key? + * + * @param string $key The data key + * + * @return bool + */ + public function has($key) + { + return array_key_exists($key, $this->data); + } + + /** + * Remove item from collection + * + * @param string $key The data key + */ + public function remove($key) + { + unset($this->data[$key]); + } + + /** + * Remove all items from collection + */ + public function clear() + { + $this->data = []; + } + + /******************************************************************************** + * ArrayAccess interface + *******************************************************************************/ + + /** + * Does this collection have a given key? + * + * @param string $key The data key + * + * @return bool + */ + public function offsetExists($key) + { + return $this->has($key); + } + + /** + * Get collection item for key + * + * @param string $key The data key + * + * @return mixed The key's value, or the default value + */ + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Set collection item + * + * @param string $key The data key + * @param mixed $value The data value + */ + public function offsetSet($key, $value) + { + $this->set($key, $value); + } + + /** + * Remove item from collection + * + * @param string $key The data key + */ + public function offsetUnset($key) + { + $this->remove($key); + } + + /** + * Get number of items in collection + * + * @return int + */ + public function count() + { + return count($this->data); + } + + /******************************************************************************** + * IteratorAggregate interface + *******************************************************************************/ + + /** + * Get collection iterator + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->data); + } +} diff --git a/src/vendor/slim/slim/Slim/Container.php b/src/vendor/slim/slim/Slim/Container.php new file mode 100644 index 0000000..2d15a8d --- /dev/null +++ b/src/vendor/slim/slim/Slim/Container.php @@ -0,0 +1,179 @@ + '1.1', + 'responseChunkSize' => 4096, + 'outputBuffering' => 'append', + 'determineRouteBeforeAppMiddleware' => false, + 'displayErrorDetails' => false, + 'addContentLengthHeader' => true, + 'routerCacheFile' => false, + ]; + + /** + * Create new container + * + * @param array $values The parameters or objects. + */ + public function __construct(array $values = []) + { + parent::__construct($values); + + $userSettings = isset($values['settings']) ? $values['settings'] : []; + $this->registerDefaultServices($userSettings); + } + + /** + * This function registers the default services that Slim needs to work. + * + * All services are shared - that is, they are registered such that the + * same instance is returned on subsequent calls. + * + * @param array $userSettings Associative array of application settings + * + * @return void + */ + private function registerDefaultServices($userSettings) + { + $defaultSettings = $this->defaultSettings; + + /** + * This service MUST return an array or an + * instance of \ArrayAccess. + * + * @return array|\ArrayAccess + */ + $this['settings'] = function () use ($userSettings, $defaultSettings) { + return new Collection(array_merge($defaultSettings, $userSettings)); + }; + + $defaultProvider = new DefaultServicesProvider(); + $defaultProvider->register($this); + } + + /******************************************************************************** + * Methods to satisfy Interop\Container\ContainerInterface + *******************************************************************************/ + + /** + * Finds an entry of the container by its identifier and returns it. + * + * @param string $id Identifier of the entry to look for. + * + * @throws ContainerValueNotFoundException No entry was found for this identifier. + * @throws ContainerException Error while retrieving the entry. + * + * @return mixed Entry. + */ + public function get($id) + { + if (!$this->offsetExists($id)) { + throw new ContainerValueNotFoundException(sprintf('Identifier "%s" is not defined.', $id)); + } + try { + return $this->offsetGet($id); + } catch (\InvalidArgumentException $exception) { + if ($this->exceptionThrownByContainer($exception)) { + throw new SlimContainerException( + sprintf('Container error while retrieving "%s"', $id), + null, + $exception + ); + } else { + throw $exception; + } + } + } + + /** + * Tests whether an exception needs to be recast for compliance with Container-Interop. This will be if the + * exception was thrown by Pimple. + * + * @param \InvalidArgumentException $exception + * + * @return bool + */ + private function exceptionThrownByContainer(\InvalidArgumentException $exception) + { + $trace = $exception->getTrace()[0]; + + return $trace['class'] === PimpleContainer::class && $trace['function'] === 'offsetGet'; + } + + /** + * Returns true if the container can return an entry for the given identifier. + * Returns false otherwise. + * + * @param string $id Identifier of the entry to look for. + * + * @return boolean + */ + public function has($id) + { + return $this->offsetExists($id); + } + + + /******************************************************************************** + * Magic methods for convenience + *******************************************************************************/ + + public function __get($name) + { + return $this->get($name); + } + + public function __isset($name) + { + return $this->has($name); + } +} diff --git a/src/vendor/slim/slim/Slim/DefaultServicesProvider.php b/src/vendor/slim/slim/Slim/DefaultServicesProvider.php new file mode 100644 index 0000000..8a5c01a --- /dev/null +++ b/src/vendor/slim/slim/Slim/DefaultServicesProvider.php @@ -0,0 +1,209 @@ +get('environment')); + }; + } + + if (!isset($container['response'])) { + /** + * PSR-7 Response object + * + * @param Container $container + * + * @return ResponseInterface + */ + $container['response'] = function ($container) { + $headers = new Headers(['Content-Type' => 'text/html; charset=UTF-8']); + $response = new Response(200, $headers); + + return $response->withProtocolVersion($container->get('settings')['httpVersion']); + }; + } + + if (!isset($container['router'])) { + /** + * This service MUST return a SHARED instance + * of \Slim\Interfaces\RouterInterface. + * + * @param Container $container + * + * @return RouterInterface + */ + $container['router'] = function ($container) { + $routerCacheFile = false; + if (isset($container->get('settings')['routerCacheFile'])) { + $routerCacheFile = $container->get('settings')['routerCacheFile']; + } + + + $router = (new Router)->setCacheFile($routerCacheFile); + if (method_exists($router, 'setContainer')) { + $router->setContainer($container); + } + + return $router; + }; + } + + if (!isset($container['foundHandler'])) { + /** + * This service MUST return a SHARED instance + * of \Slim\Interfaces\InvocationStrategyInterface. + * + * @return InvocationStrategyInterface + */ + $container['foundHandler'] = function () { + return new RequestResponse; + }; + } + + if (!isset($container['phpErrorHandler'])) { + /** + * This service MUST return a callable + * that accepts three arguments: + * + * 1. Instance of \Psr\Http\Message\ServerRequestInterface + * 2. Instance of \Psr\Http\Message\ResponseInterface + * 3. Instance of \Error + * + * The callable MUST return an instance of + * \Psr\Http\Message\ResponseInterface. + * + * @param Container $container + * + * @return callable + */ + $container['phpErrorHandler'] = function ($container) { + return new PhpError($container->get('settings')['displayErrorDetails']); + }; + } + + if (!isset($container['errorHandler'])) { + /** + * This service MUST return a callable + * that accepts three arguments: + * + * 1. Instance of \Psr\Http\Message\ServerRequestInterface + * 2. Instance of \Psr\Http\Message\ResponseInterface + * 3. Instance of \Exception + * + * The callable MUST return an instance of + * \Psr\Http\Message\ResponseInterface. + * + * @param Container $container + * + * @return callable + */ + $container['errorHandler'] = function ($container) { + return new Error($container->get('settings')['displayErrorDetails']); + }; + } + + if (!isset($container['notFoundHandler'])) { + /** + * This service MUST return a callable + * that accepts two arguments: + * + * 1. Instance of \Psr\Http\Message\ServerRequestInterface + * 2. Instance of \Psr\Http\Message\ResponseInterface + * + * The callable MUST return an instance of + * \Psr\Http\Message\ResponseInterface. + * + * @return callable + */ + $container['notFoundHandler'] = function () { + return new NotFound; + }; + } + + if (!isset($container['notAllowedHandler'])) { + /** + * This service MUST return a callable + * that accepts three arguments: + * + * 1. Instance of \Psr\Http\Message\ServerRequestInterface + * 2. Instance of \Psr\Http\Message\ResponseInterface + * 3. Array of allowed HTTP methods + * + * The callable MUST return an instance of + * \Psr\Http\Message\ResponseInterface. + * + * @return callable + */ + $container['notAllowedHandler'] = function () { + return new NotAllowed; + }; + } + + if (!isset($container['callableResolver'])) { + /** + * Instance of \Slim\Interfaces\CallableResolverInterface + * + * @param Container $container + * + * @return CallableResolverInterface + */ + $container['callableResolver'] = function ($container) { + return new CallableResolver($container); + }; + } + } +} diff --git a/src/vendor/slim/slim/Slim/DeferredCallable.php b/src/vendor/slim/slim/Slim/DeferredCallable.php new file mode 100644 index 0000000..af5aed7 --- /dev/null +++ b/src/vendor/slim/slim/Slim/DeferredCallable.php @@ -0,0 +1,45 @@ +callable = $callable; + $this->container = $container; + } + + public function __invoke() + { + $callable = $this->resolveCallable($this->callable); + if ($callable instanceof Closure) { + $callable = $callable->bindTo($this->container); + } + + $args = func_get_args(); + + return call_user_func_array($callable, $args); + } +} diff --git a/src/vendor/slim/slim/Slim/Exception/ContainerException.php b/src/vendor/slim/slim/Slim/Exception/ContainerException.php new file mode 100644 index 0000000..3ba18cb --- /dev/null +++ b/src/vendor/slim/slim/Slim/Exception/ContainerException.php @@ -0,0 +1,20 @@ +allowedMethods = $allowedMethods; + } + + /** + * Get allowed methods + * + * @return string[] + */ + public function getAllowedMethods() + { + return $this->allowedMethods; + } +} diff --git a/src/vendor/slim/slim/Slim/Exception/NotFoundException.php b/src/vendor/slim/slim/Slim/Exception/NotFoundException.php new file mode 100644 index 0000000..65365eb --- /dev/null +++ b/src/vendor/slim/slim/Slim/Exception/NotFoundException.php @@ -0,0 +1,14 @@ +request = $request; + $this->response = $response; + } + + /** + * Get request + * + * @return ServerRequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Get response + * + * @return ResponseInterface + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/src/vendor/slim/slim/Slim/Handlers/AbstractError.php b/src/vendor/slim/slim/Slim/Handlers/AbstractError.php new file mode 100644 index 0000000..5a6cee3 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Handlers/AbstractError.php @@ -0,0 +1,99 @@ +displayErrorDetails = (bool) $displayErrorDetails; + } + + /** + * Write to the error log if displayErrorDetails is false + * + * @param \Exception|\Throwable $throwable + * + * @return void + */ + protected function writeToErrorLog($throwable) + { + if ($this->displayErrorDetails) { + return; + } + + $message = 'Slim Application Error:' . PHP_EOL; + $message .= $this->renderThrowableAsText($throwable); + while ($throwable = $throwable->getPrevious()) { + $message .= PHP_EOL . 'Previous error:' . PHP_EOL; + $message .= $this->renderThrowableAsText($throwable); + } + + $message .= PHP_EOL . 'View in rendered output by enabling the "displayErrorDetails" setting.' . PHP_EOL; + + $this->logError($message); + } + + /** + * Render error as Text. + * + * @param \Exception|\Throwable $throwable + * + * @return string + */ + protected function renderThrowableAsText($throwable) + { + $text = sprintf('Type: %s' . PHP_EOL, get_class($throwable)); + + if ($code = $throwable->getCode()) { + $text .= sprintf('Code: %s' . PHP_EOL, $code); + } + + if ($message = $throwable->getMessage()) { + $text .= sprintf('Message: %s' . PHP_EOL, htmlentities($message)); + } + + if ($file = $throwable->getFile()) { + $text .= sprintf('File: %s' . PHP_EOL, $file); + } + + if ($line = $throwable->getLine()) { + $text .= sprintf('Line: %s' . PHP_EOL, $line); + } + + if ($trace = $throwable->getTraceAsString()) { + $text .= sprintf('Trace: %s', $trace); + } + + return $text; + } + + /** + * Wraps the error_log function so that this can be easily tested + * + * @param $message + */ + protected function logError($message) + { + error_log($message); + } +} diff --git a/src/vendor/slim/slim/Slim/Handlers/AbstractHandler.php b/src/vendor/slim/slim/Slim/Handlers/AbstractHandler.php new file mode 100644 index 0000000..decdf72 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Handlers/AbstractHandler.php @@ -0,0 +1,59 @@ +getHeaderLine('Accept'); + $selectedContentTypes = array_intersect(explode(',', $acceptHeader), $this->knownContentTypes); + + if (count($selectedContentTypes)) { + return current($selectedContentTypes); + } + + // handle +json and +xml specially + if (preg_match('/\+(json|xml)/', $acceptHeader, $matches)) { + $mediaType = 'application/' . $matches[1]; + if (in_array($mediaType, $this->knownContentTypes)) { + return $mediaType; + } + } + + return 'text/html'; + } +} diff --git a/src/vendor/slim/slim/Slim/Handlers/Error.php b/src/vendor/slim/slim/Slim/Handlers/Error.php new file mode 100644 index 0000000..fddb7b5 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Handlers/Error.php @@ -0,0 +1,206 @@ +determineContentType($request); + switch ($contentType) { + case 'application/json': + $output = $this->renderJsonErrorMessage($exception); + break; + + case 'text/xml': + case 'application/xml': + $output = $this->renderXmlErrorMessage($exception); + break; + + case 'text/html': + $output = $this->renderHtmlErrorMessage($exception); + break; + + default: + throw new UnexpectedValueException('Cannot render unknown content type ' . $contentType); + } + + $this->writeToErrorLog($exception); + + $body = new Body(fopen('php://temp', 'r+')); + $body->write($output); + + return $response + ->withStatus(500) + ->withHeader('Content-type', $contentType) + ->withBody($body); + } + + /** + * Render HTML error page + * + * @param \Exception $exception + * + * @return string + */ + protected function renderHtmlErrorMessage(\Exception $exception) + { + $title = 'Slim Application Error'; + + if ($this->displayErrorDetails) { + $html = '

The application could not run because of the following error:

'; + $html .= '

Details

'; + $html .= $this->renderHtmlException($exception); + + while ($exception = $exception->getPrevious()) { + $html .= '

Previous exception

'; + $html .= $this->renderHtmlException($exception); + } + } else { + $html = '

A website error has occurred. Sorry for the temporary inconvenience.

'; + } + + $output = sprintf( + "" . + "%s

%s

%s", + $title, + $title, + $html + ); + + return $output; + } + + /** + * Render exception as HTML. + * + * @param \Exception $exception + * + * @return string + */ + protected function renderHtmlException(\Exception $exception) + { + $html = sprintf('
Type: %s
', get_class($exception)); + + if (($code = $exception->getCode())) { + $html .= sprintf('
Code: %s
', $code); + } + + if (($message = $exception->getMessage())) { + $html .= sprintf('
Message: %s
', htmlentities($message)); + } + + if (($file = $exception->getFile())) { + $html .= sprintf('
File: %s
', $file); + } + + if (($line = $exception->getLine())) { + $html .= sprintf('
Line: %s
', $line); + } + + if (($trace = $exception->getTraceAsString())) { + $html .= '

Trace

'; + $html .= sprintf('
%s
', htmlentities($trace)); + } + + return $html; + } + + /** + * Render JSON error + * + * @param \Exception $exception + * + * @return string + */ + protected function renderJsonErrorMessage(\Exception $exception) + { + $error = [ + 'message' => 'Slim Application Error', + ]; + + if ($this->displayErrorDetails) { + $error['exception'] = []; + + do { + $error['exception'][] = [ + 'type' => get_class($exception), + 'code' => $exception->getCode(), + 'message' => $exception->getMessage(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'trace' => explode("\n", $exception->getTraceAsString()), + ]; + } while ($exception = $exception->getPrevious()); + } + + return json_encode($error, JSON_PRETTY_PRINT); + } + + /** + * Render XML error + * + * @param \Exception $exception + * + * @return string + */ + protected function renderXmlErrorMessage(\Exception $exception) + { + $xml = "\n Slim Application Error\n"; + if ($this->displayErrorDetails) { + do { + $xml .= " \n"; + $xml .= " " . get_class($exception) . "\n"; + $xml .= " " . $exception->getCode() . "\n"; + $xml .= " " . $this->createCdataSection($exception->getMessage()) . "\n"; + $xml .= " " . $exception->getFile() . "\n"; + $xml .= " " . $exception->getLine() . "\n"; + $xml .= " " . $this->createCdataSection($exception->getTraceAsString()) . "\n"; + $xml .= " \n"; + } while ($exception = $exception->getPrevious()); + } + $xml .= ""; + + return $xml; + } + + /** + * Returns a CDATA section with the given content. + * + * @param string $content + * @return string + */ + private function createCdataSection($content) + { + return sprintf('', str_replace(']]>', ']]]]>', $content)); + } +} diff --git a/src/vendor/slim/slim/Slim/Handlers/NotAllowed.php b/src/vendor/slim/slim/Slim/Handlers/NotAllowed.php new file mode 100644 index 0000000..3442f20 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Handlers/NotAllowed.php @@ -0,0 +1,147 @@ +getMethod() === 'OPTIONS') { + $status = 200; + $contentType = 'text/plain'; + $output = $this->renderPlainNotAllowedMessage($methods); + } else { + $status = 405; + $contentType = $this->determineContentType($request); + switch ($contentType) { + case 'application/json': + $output = $this->renderJsonNotAllowedMessage($methods); + break; + + case 'text/xml': + case 'application/xml': + $output = $this->renderXmlNotAllowedMessage($methods); + break; + + case 'text/html': + $output = $this->renderHtmlNotAllowedMessage($methods); + break; + default: + throw new UnexpectedValueException('Cannot render unknown content type ' . $contentType); + } + } + + $body = new Body(fopen('php://temp', 'r+')); + $body->write($output); + $allow = implode(', ', $methods); + + return $response + ->withStatus($status) + ->withHeader('Content-type', $contentType) + ->withHeader('Allow', $allow) + ->withBody($body); + } + + /** + * Render PLAIN not allowed message + * + * @param array $methods + * @return string + */ + protected function renderPlainNotAllowedMessage($methods) + { + $allow = implode(', ', $methods); + + return 'Allowed methods: ' . $allow; + } + + /** + * Render JSON not allowed message + * + * @param array $methods + * @return string + */ + protected function renderJsonNotAllowedMessage($methods) + { + $allow = implode(', ', $methods); + + return '{"message":"Method not allowed. Must be one of: ' . $allow . '"}'; + } + + /** + * Render XML not allowed message + * + * @param array $methods + * @return string + */ + protected function renderXmlNotAllowedMessage($methods) + { + $allow = implode(', ', $methods); + + return "Method not allowed. Must be one of: $allow"; + } + + /** + * Render HTML not allowed message + * + * @param array $methods + * @return string + */ + protected function renderHtmlNotAllowedMessage($methods) + { + $allow = implode(', ', $methods); + $output = << + + Method not allowed + + + +

Method not allowed

+

Method not allowed. Must be one of: $allow

+ + +END; + + return $output; + } +} diff --git a/src/vendor/slim/slim/Slim/Handlers/NotFound.php b/src/vendor/slim/slim/Slim/Handlers/NotFound.php new file mode 100644 index 0000000..105d2e8 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Handlers/NotFound.php @@ -0,0 +1,126 @@ +determineContentType($request); + switch ($contentType) { + case 'application/json': + $output = $this->renderJsonNotFoundOutput(); + break; + + case 'text/xml': + case 'application/xml': + $output = $this->renderXmlNotFoundOutput(); + break; + + case 'text/html': + $output = $this->renderHtmlNotFoundOutput($request); + break; + + default: + throw new UnexpectedValueException('Cannot render unknown content type ' . $contentType); + } + + $body = new Body(fopen('php://temp', 'r+')); + $body->write($output); + + return $response->withStatus(404) + ->withHeader('Content-Type', $contentType) + ->withBody($body); + } + + /** + * Return a response for application/json content not found + * + * @return ResponseInterface + */ + protected function renderJsonNotFoundOutput() + { + return '{"message":"Not found"}'; + } + + /** + * Return a response for xml content not found + * + * @return ResponseInterface + */ + protected function renderXmlNotFoundOutput() + { + return 'Not found'; + } + + /** + * Return a response for text/html content not found + * + * @param ServerRequestInterface $request The most recent Request object + * + * @return ResponseInterface + */ + protected function renderHtmlNotFoundOutput(ServerRequestInterface $request) + { + $homeUrl = (string)($request->getUri()->withPath('')->withQuery('')->withFragment('')); + return << + + Page Not Found + + + +

Page Not Found

+

+ The page you are looking for could not be found. Check the address bar + to ensure your URL is spelled correctly. If all else fails, you can + visit our home page at the link below. +

+
Visit the Home Page + + +END; + } +} diff --git a/src/vendor/slim/slim/Slim/Handlers/PhpError.php b/src/vendor/slim/slim/Slim/Handlers/PhpError.php new file mode 100644 index 0000000..0122ddb --- /dev/null +++ b/src/vendor/slim/slim/Slim/Handlers/PhpError.php @@ -0,0 +1,205 @@ +determineContentType($request); + switch ($contentType) { + case 'application/json': + $output = $this->renderJsonErrorMessage($error); + break; + + case 'text/xml': + case 'application/xml': + $output = $this->renderXmlErrorMessage($error); + break; + + case 'text/html': + $output = $this->renderHtmlErrorMessage($error); + break; + default: + throw new UnexpectedValueException('Cannot render unknown content type ' . $contentType); + } + + $this->writeToErrorLog($error); + + $body = new Body(fopen('php://temp', 'r+')); + $body->write($output); + + return $response + ->withStatus(500) + ->withHeader('Content-type', $contentType) + ->withBody($body); + } + + /** + * Render HTML error page + * + * @param \Throwable $error + * + * @return string + */ + protected function renderHtmlErrorMessage(\Throwable $error) + { + $title = 'Slim Application Error'; + + if ($this->displayErrorDetails) { + $html = '

The application could not run because of the following error:

'; + $html .= '

Details

'; + $html .= $this->renderHtmlError($error); + + while ($error = $error->getPrevious()) { + $html .= '

Previous error

'; + $html .= $this->renderHtmlError($error); + } + } else { + $html = '

A website error has occurred. Sorry for the temporary inconvenience.

'; + } + + $output = sprintf( + "" . + "%s

%s

%s", + $title, + $title, + $html + ); + + return $output; + } + + /** + * Render error as HTML. + * + * @param \Throwable $error + * + * @return string + */ + protected function renderHtmlError(\Throwable $error) + { + $html = sprintf('
Type: %s
', get_class($error)); + + if (($code = $error->getCode())) { + $html .= sprintf('
Code: %s
', $code); + } + + if (($message = $error->getMessage())) { + $html .= sprintf('
Message: %s
', htmlentities($message)); + } + + if (($file = $error->getFile())) { + $html .= sprintf('
File: %s
', $file); + } + + if (($line = $error->getLine())) { + $html .= sprintf('
Line: %s
', $line); + } + + if (($trace = $error->getTraceAsString())) { + $html .= '

Trace

'; + $html .= sprintf('
%s
', htmlentities($trace)); + } + + return $html; + } + + /** + * Render JSON error + * + * @param \Throwable $error + * + * @return string + */ + protected function renderJsonErrorMessage(\Throwable $error) + { + $json = [ + 'message' => 'Slim Application Error', + ]; + + if ($this->displayErrorDetails) { + $json['error'] = []; + + do { + $json['error'][] = [ + 'type' => get_class($error), + 'code' => $error->getCode(), + 'message' => $error->getMessage(), + 'file' => $error->getFile(), + 'line' => $error->getLine(), + 'trace' => explode("\n", $error->getTraceAsString()), + ]; + } while ($error = $error->getPrevious()); + } + + return json_encode($json, JSON_PRETTY_PRINT); + } + + /** + * Render XML error + * + * @param \Throwable $error + * + * @return string + */ + protected function renderXmlErrorMessage(\Throwable $error) + { + $xml = "\n Slim Application Error\n"; + if ($this->displayErrorDetails) { + do { + $xml .= " \n"; + $xml .= " " . get_class($error) . "\n"; + $xml .= " " . $error->getCode() . "\n"; + $xml .= " " . $this->createCdataSection($error->getMessage()) . "\n"; + $xml .= " " . $error->getFile() . "\n"; + $xml .= " " . $error->getLine() . "\n"; + $xml .= " " . $this->createCdataSection($error->getTraceAsString()) . "\n"; + $xml .= " \n"; + } while ($error = $error->getPrevious()); + } + $xml .= ""; + + return $xml; + } + + /** + * Returns a CDATA section with the given content. + * + * @param string $content + * @return string + */ + private function createCdataSection($content) + { + return sprintf('', str_replace(']]>', ']]]]>', $content)); + } +} diff --git a/src/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php b/src/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php new file mode 100644 index 0000000..157bdeb --- /dev/null +++ b/src/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php @@ -0,0 +1,43 @@ + $v) { + $request = $request->withAttribute($k, $v); + } + + return call_user_func($callable, $request, $response, $routeArguments); + } +} diff --git a/src/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php b/src/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php new file mode 100644 index 0000000..11793d3 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php @@ -0,0 +1,42 @@ + '', + 'domain' => null, + 'hostonly' => null, + 'path' => null, + 'expires' => null, + 'secure' => false, + 'httponly' => false + ]; + + /** + * Create new cookies helper + * + * @param array $cookies + */ + public function __construct(array $cookies = []) + { + $this->requestCookies = $cookies; + } + + /** + * Set default cookie properties + * + * @param array $settings + */ + public function setDefaults(array $settings) + { + $this->defaults = array_replace($this->defaults, $settings); + } + + /** + * Get request cookie + * + * @param string $name Cookie name + * @param mixed $default Cookie default value + * + * @return mixed Cookie value if present, else default + */ + public function get($name, $default = null) + { + return isset($this->requestCookies[$name]) ? $this->requestCookies[$name] : $default; + } + + /** + * Set response cookie + * + * @param string $name Cookie name + * @param string|array $value Cookie value, or cookie properties + */ + public function set($name, $value) + { + if (!is_array($value)) { + $value = ['value' => (string)$value]; + } + $this->responseCookies[$name] = array_replace($this->defaults, $value); + } + + /** + * Convert to `Set-Cookie` headers + * + * @return string[] + */ + public function toHeaders() + { + $headers = []; + foreach ($this->responseCookies as $name => $properties) { + $headers[] = $this->toHeader($name, $properties); + } + + return $headers; + } + + /** + * Convert to `Set-Cookie` header + * + * @param string $name Cookie name + * @param array $properties Cookie properties + * + * @return string + */ + protected function toHeader($name, array $properties) + { + $result = urlencode($name) . '=' . urlencode($properties['value']); + + if (isset($properties['domain'])) { + $result .= '; domain=' . $properties['domain']; + } + + if (isset($properties['path'])) { + $result .= '; path=' . $properties['path']; + } + + if (isset($properties['expires'])) { + if (is_string($properties['expires'])) { + $timestamp = strtotime($properties['expires']); + } else { + $timestamp = (int)$properties['expires']; + } + if ($timestamp !== 0) { + $result .= '; expires=' . gmdate('D, d-M-Y H:i:s e', $timestamp); + } + } + + if (isset($properties['secure']) && $properties['secure']) { + $result .= '; secure'; + } + + if (isset($properties['hostonly']) && $properties['hostonly']) { + $result .= '; HostOnly'; + } + + if (isset($properties['httponly']) && $properties['httponly']) { + $result .= '; HttpOnly'; + } + + return $result; + } + + /** + * Parse HTTP request `Cookie:` header and extract + * into a PHP associative array. + * + * @param string $header The raw HTTP request `Cookie:` header + * + * @return array Associative array of cookie names and values + * + * @throws InvalidArgumentException if the cookie data cannot be parsed + */ + public static function parseHeader($header) + { + if (is_array($header) === true) { + $header = isset($header[0]) ? $header[0] : ''; + } + + if (is_string($header) === false) { + throw new InvalidArgumentException('Cannot parse Cookie data. Header value must be a string.'); + } + + $header = rtrim($header, "\r\n"); + $pieces = preg_split('@\s*[;,]\s*@', $header); + $cookies = []; + + foreach ($pieces as $cookie) { + $cookie = explode('=', $cookie, 2); + + if (count($cookie) === 2) { + $key = urldecode($cookie[0]); + $value = urldecode($cookie[1]); + + if (!isset($cookies[$key])) { + $cookies[$key] = $value; + } + } + } + + return $cookies; + } +} diff --git a/src/vendor/slim/slim/Slim/Http/Environment.php b/src/vendor/slim/slim/Slim/Http/Environment.php new file mode 100644 index 0000000..a106fa8 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Http/Environment.php @@ -0,0 +1,52 @@ + 'HTTP/1.1', + 'REQUEST_METHOD' => 'GET', + 'SCRIPT_NAME' => '', + 'REQUEST_URI' => '', + 'QUERY_STRING' => '', + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => 80, + 'HTTP_HOST' => 'localhost', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.8', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', + 'HTTP_USER_AGENT' => 'Slim Framework', + 'REMOTE_ADDR' => '127.0.0.1', + 'REQUEST_TIME' => time(), + 'REQUEST_TIME_FLOAT' => microtime(true), + ], $userData); + + return new static($data); + } +} diff --git a/src/vendor/slim/slim/Slim/Http/Headers.php b/src/vendor/slim/slim/Slim/Http/Headers.php new file mode 100644 index 0000000..4aa7a5e --- /dev/null +++ b/src/vendor/slim/slim/Slim/Http/Headers.php @@ -0,0 +1,222 @@ + 1, + 'CONTENT_LENGTH' => 1, + 'PHP_AUTH_USER' => 1, + 'PHP_AUTH_PW' => 1, + 'PHP_AUTH_DIGEST' => 1, + 'AUTH_TYPE' => 1, + ]; + + /** + * Create new headers collection with data extracted from + * the application Environment object + * + * @param Environment $environment The Slim application Environment + * + * @return self + */ + public static function createFromEnvironment(Environment $environment) + { + $data = []; + $environment = self::determineAuthorization($environment); + foreach ($environment as $key => $value) { + $key = strtoupper($key); + if (isset(static::$special[$key]) || strpos($key, 'HTTP_') === 0) { + if ($key !== 'HTTP_CONTENT_LENGTH') { + $data[$key] = $value; + } + } + } + + return new static($data); + } + + /** + * If HTTP_AUTHORIZATION does not exist tries to get it from + * getallheaders() when available. + * + * @param Environment $environment The Slim application Environment + * + * @return Environment + */ + + public static function determineAuthorization(Environment $environment) + { + $authorization = $environment->get('HTTP_AUTHORIZATION'); + + if (null === $authorization && is_callable('getallheaders')) { + $headers = getallheaders(); + $headers = array_change_key_case($headers, CASE_LOWER); + if (isset($headers['authorization'])) { + $environment->set('HTTP_AUTHORIZATION', $headers['authorization']); + } + } + + return $environment; + } + + /** + * Return array of HTTP header names and values. + * This method returns the _original_ header name + * as specified by the end user. + * + * @return array + */ + public function all() + { + $all = parent::all(); + $out = []; + foreach ($all as $key => $props) { + $out[$props['originalKey']] = $props['value']; + } + + return $out; + } + + /** + * Set HTTP header value + * + * This method sets a header value. It replaces + * any values that may already exist for the header name. + * + * @param string $key The case-insensitive header name + * @param string $value The header value + */ + public function set($key, $value) + { + if (!is_array($value)) { + $value = [$value]; + } + parent::set($this->normalizeKey($key), [ + 'value' => $value, + 'originalKey' => $key + ]); + } + + /** + * Get HTTP header value + * + * @param string $key The case-insensitive header name + * @param mixed $default The default value if key does not exist + * + * @return string[] + */ + public function get($key, $default = null) + { + if ($this->has($key)) { + return parent::get($this->normalizeKey($key))['value']; + } + + return $default; + } + + /** + * Get HTTP header key as originally specified + * + * @param string $key The case-insensitive header name + * @param mixed $default The default value if key does not exist + * + * @return string + */ + public function getOriginalKey($key, $default = null) + { + if ($this->has($key)) { + return parent::get($this->normalizeKey($key))['originalKey']; + } + + return $default; + } + + /** + * Add HTTP header value + * + * This method appends a header value. Unlike the set() method, + * this method _appends_ this new value to any values + * that already exist for this header name. + * + * @param string $key The case-insensitive header name + * @param array|string $value The new header value(s) + */ + public function add($key, $value) + { + $oldValues = $this->get($key, []); + $newValues = is_array($value) ? $value : [$value]; + $this->set($key, array_merge($oldValues, array_values($newValues))); + } + + /** + * Does this collection have a given header? + * + * @param string $key The case-insensitive header name + * + * @return bool + */ + public function has($key) + { + return parent::has($this->normalizeKey($key)); + } + + /** + * Remove header from collection + * + * @param string $key The case-insensitive header name + */ + public function remove($key) + { + parent::remove($this->normalizeKey($key)); + } + + /** + * Normalize header name + * + * This method transforms header names into a + * normalized form. This is how we enable case-insensitive + * header names in the other methods in this class. + * + * @param string $key The case-insensitive header name + * + * @return string Normalized header name + */ + public function normalizeKey($key) + { + $key = strtr(strtolower($key), '_', '-'); + if (strpos($key, 'http-') === 0) { + $key = substr($key, 5); + } + + return $key; + } +} diff --git a/src/vendor/slim/slim/Slim/Http/Message.php b/src/vendor/slim/slim/Slim/Http/Message.php new file mode 100644 index 0000000..1596c8d --- /dev/null +++ b/src/vendor/slim/slim/Slim/Http/Message.php @@ -0,0 +1,304 @@ + true, + '1.1' => true, + '2.0' => true, + ]; + + /** + * Headers + * + * @var \Slim\Interfaces\Http\HeadersInterface + */ + protected $headers; + + /** + * Body object + * + * @var \Psr\Http\Message\StreamInterface + */ + protected $body; + + + /** + * Disable magic setter to ensure immutability + */ + public function __set($name, $value) + { + // Do nothing + } + + /******************************************************************************* + * Protocol + ******************************************************************************/ + + /** + * Retrieves the HTTP protocol version as a string. + * + * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0"). + * + * @return string HTTP protocol version. + */ + public function getProtocolVersion() + { + return $this->protocolVersion; + } + + /** + * Return an instance with the specified HTTP protocol version. + * + * The version string MUST contain only the HTTP version number (e.g., + * "1.1", "1.0"). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new protocol version. + * + * @param string $version HTTP protocol version + * @return static + * @throws InvalidArgumentException if the http version is an invalid number + */ + public function withProtocolVersion($version) + { + if (!isset(self::$validProtocolVersions[$version])) { + throw new InvalidArgumentException( + 'Invalid HTTP version. Must be one of: ' + . implode(', ', array_keys(self::$validProtocolVersions)) + ); + } + $clone = clone $this; + $clone->protocolVersion = $version; + + return $clone; + } + + /******************************************************************************* + * Headers + ******************************************************************************/ + + /** + * Retrieves all message header values. + * + * The keys represent the header name as it will be sent over the wire, and + * each value is an array of strings associated with the header. + * + * // Represent the headers as a string + * foreach ($message->getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return array Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders() + { + return $this->headers->all(); + } + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name) + { + return $this->headers->has($name); + } + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name) + { + return $this->headers->get($name, []); + } + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name) + { + return implode(',', $this->headers->get($name, [])); + } + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value) + { + $clone = clone $this; + $clone->headers->set($name, $value); + + return $clone; + } + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value) + { + $clone = clone $this; + $clone->headers->add($name, $value); + + return $clone; + } + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader($name) + { + $clone = clone $this; + $clone->headers->remove($name); + + return $clone; + } + + /******************************************************************************* + * Body + ******************************************************************************/ + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody() + { + return $this->body; + } + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body) + { + // TODO: Test for invalid body? + $clone = clone $this; + $clone->body = $body; + + return $clone; + } +} diff --git a/src/vendor/slim/slim/Slim/Http/Request.php b/src/vendor/slim/slim/Slim/Http/Request.php new file mode 100644 index 0000000..fa9ffd9 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Http/Request.php @@ -0,0 +1,1190 @@ + 1, + 'DELETE' => 1, + 'GET' => 1, + 'HEAD' => 1, + 'OPTIONS' => 1, + 'PATCH' => 1, + 'POST' => 1, + 'PUT' => 1, + 'TRACE' => 1, + ]; + + /** + * Create new HTTP request with data extracted from the application + * Environment object + * + * @param Environment $environment The Slim application Environment + * + * @return self + */ + public static function createFromEnvironment(Environment $environment) + { + $method = $environment['REQUEST_METHOD']; + $uri = Uri::createFromEnvironment($environment); + $headers = Headers::createFromEnvironment($environment); + $cookies = Cookies::parseHeader($headers->get('Cookie', [])); + $serverParams = $environment->all(); + $body = new RequestBody(); + $uploadedFiles = UploadedFile::createFromEnvironment($environment); + + $request = new static($method, $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles); + + if ($method === 'POST' && + in_array($request->getMediaType(), ['application/x-www-form-urlencoded', 'multipart/form-data']) + ) { + // parsed body must be $_POST + $request = $request->withParsedBody($_POST); + } + return $request; + } + + /** + * Create new HTTP request. + * + * Adds a host header when none was provided and a host is defined in uri. + * + * @param string $method The request method + * @param UriInterface $uri The request URI object + * @param HeadersInterface $headers The request headers collection + * @param array $cookies The request cookies collection + * @param array $serverParams The server environment variables + * @param StreamInterface $body The request body object + * @param array $uploadedFiles The request uploadedFiles collection + */ + public function __construct( + $method, + UriInterface $uri, + HeadersInterface $headers, + array $cookies, + array $serverParams, + StreamInterface $body, + array $uploadedFiles = [] + ) { + $this->originalMethod = $this->filterMethod($method); + $this->uri = $uri; + $this->headers = $headers; + $this->cookies = $cookies; + $this->serverParams = $serverParams; + $this->attributes = new Collection(); + $this->body = $body; + $this->uploadedFiles = $uploadedFiles; + + if (isset($serverParams['SERVER_PROTOCOL'])) { + $this->protocolVersion = str_replace('HTTP/', '', $serverParams['SERVER_PROTOCOL']); + } + + if (!$this->headers->has('Host') || $this->uri->getHost() !== '') { + $this->headers->set('Host', $this->uri->getHost()); + } + + $this->registerMediaTypeParser('application/json', function ($input) { + return json_decode($input, true); + }); + + $this->registerMediaTypeParser('application/xml', function ($input) { + $backup = libxml_disable_entity_loader(true); + $result = simplexml_load_string($input); + libxml_disable_entity_loader($backup); + return $result; + }); + + $this->registerMediaTypeParser('text/xml', function ($input) { + $backup = libxml_disable_entity_loader(true); + $result = simplexml_load_string($input); + libxml_disable_entity_loader($backup); + return $result; + }); + + $this->registerMediaTypeParser('application/x-www-form-urlencoded', function ($input) { + parse_str($input, $data); + return $data; + }); + } + + /** + * This method is applied to the cloned object + * after PHP performs an initial shallow-copy. This + * method completes a deep-copy by creating new objects + * for the cloned object's internal reference pointers. + */ + public function __clone() + { + $this->headers = clone $this->headers; + $this->attributes = clone $this->attributes; + $this->body = clone $this->body; + } + + /******************************************************************************* + * Method + ******************************************************************************/ + + /** + * Retrieves the HTTP method of the request. + * + * @return string Returns the request method. + */ + public function getMethod() + { + if ($this->method === null) { + $this->method = $this->originalMethod; + $customMethod = $this->getHeaderLine('X-Http-Method-Override'); + + if ($customMethod) { + $this->method = $this->filterMethod($customMethod); + } elseif ($this->originalMethod === 'POST') { + $overrideMethod = $this->filterMethod($this->getParsedBodyParam('_METHOD')); + if ($overrideMethod !== null) { + $this->method = $overrideMethod; + } + + if ($this->getBody()->eof()) { + $this->getBody()->rewind(); + } + } + } + + return $this->method; + } + + /** + * Get the original HTTP method (ignore override). + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string + */ + public function getOriginalMethod() + { + return $this->originalMethod; + } + + /** + * Return an instance with the provided HTTP method. + * + * While HTTP method names are typically all uppercase characters, HTTP + * method names are case-sensitive and thus implementations SHOULD NOT + * modify the given string. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * changed request method. + * + * @param string $method Case-sensitive method. + * @return self + * @throws \InvalidArgumentException for invalid HTTP methods. + */ + public function withMethod($method) + { + $method = $this->filterMethod($method); + $clone = clone $this; + $clone->originalMethod = $method; + $clone->method = $method; + + return $clone; + } + + /** + * Validate the HTTP method + * + * @param null|string $method + * @return null|string + * @throws \InvalidArgumentException on invalid HTTP method. + */ + protected function filterMethod($method) + { + if ($method === null) { + return $method; + } + + if (!is_string($method)) { + throw new InvalidArgumentException(sprintf( + 'Unsupported HTTP method; must be a string, received %s', + (is_object($method) ? get_class($method) : gettype($method)) + )); + } + + $method = strtoupper($method); + if (!isset($this->validMethods[$method])) { + throw new InvalidArgumentException(sprintf( + 'Unsupported HTTP method "%s" provided', + $method + )); + } + + return $method; + } + + /** + * Does this request use a given method? + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $method HTTP method + * @return bool + */ + public function isMethod($method) + { + return $this->getMethod() === $method; + } + + /** + * Is this a GET request? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isGet() + { + return $this->isMethod('GET'); + } + + /** + * Is this a POST request? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isPost() + { + return $this->isMethod('POST'); + } + + /** + * Is this a PUT request? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isPut() + { + return $this->isMethod('PUT'); + } + + /** + * Is this a PATCH request? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isPatch() + { + return $this->isMethod('PATCH'); + } + + /** + * Is this a DELETE request? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isDelete() + { + return $this->isMethod('DELETE'); + } + + /** + * Is this a HEAD request? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isHead() + { + return $this->isMethod('HEAD'); + } + + /** + * Is this a OPTIONS request? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isOptions() + { + return $this->isMethod('OPTIONS'); + } + + /** + * Is this an XHR request? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isXhr() + { + return $this->getHeaderLine('X-Requested-With') === 'XMLHttpRequest'; + } + + /******************************************************************************* + * URI + ******************************************************************************/ + + /** + * Retrieves the message's request target. + * + * Retrieves the message's request-target either as it will appear (for + * clients), as it appeared at request (for servers), or as it was + * specified for the instance (see withRequestTarget()). + * + * In most cases, this will be the origin-form of the composed URI, + * unless a value was provided to the concrete implementation (see + * withRequestTarget() below). + * + * If no URI is available, and no request-target has been specifically + * provided, this method MUST return the string "/". + * + * @return string + */ + public function getRequestTarget() + { + if ($this->requestTarget) { + return $this->requestTarget; + } + + if ($this->uri === null) { + return '/'; + } + + $basePath = $this->uri->getBasePath(); + $path = $this->uri->getPath(); + $path = $basePath . '/' . ltrim($path, '/'); + + $query = $this->uri->getQuery(); + if ($query) { + $path .= '?' . $query; + } + $this->requestTarget = $path; + + return $this->requestTarget; + } + + /** + * Return an instance with the specific request-target. + * + * If the request needs a non-origin-form request-target — e.g., for + * specifying an absolute-form, authority-form, or asterisk-form — + * this method may be used to create an instance with the specified + * request-target, verbatim. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * changed request target. + * + * @link http://tools.ietf.org/html/rfc7230#section-2.7 (for the various + * request-target forms allowed in request messages) + * @param mixed $requestTarget + * @return self + * @throws InvalidArgumentException if the request target is invalid + */ + public function withRequestTarget($requestTarget) + { + if (preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; must be a string and cannot contain whitespace' + ); + } + $clone = clone $this; + $clone->requestTarget = $requestTarget; + + return $clone; + } + + /** + * Retrieves the URI instance. + * + * This method MUST return a UriInterface instance. + * + * @link http://tools.ietf.org/html/rfc3986#section-4.3 + * @return UriInterface Returns a UriInterface instance + * representing the URI of the request. + */ + public function getUri() + { + return $this->uri; + } + + /** + * Returns an instance with the provided URI. + * + * This method MUST update the Host header of the returned request by + * default if the URI contains a host component. If the URI does not + * contain a host component, any pre-existing Host header MUST be carried + * over to the returned request. + * + * You can opt-in to preserving the original state of the Host header by + * setting `$preserveHost` to `true`. When `$preserveHost` is set to + * `true`, this method interacts with the Host header in the following ways: + * + * - If the the Host header is missing or empty, and the new URI contains + * a host component, this method MUST update the Host header in the returned + * request. + * - If the Host header is missing or empty, and the new URI does not contain a + * host component, this method MUST NOT update the Host header in the returned + * request. + * - If a Host header is present and non-empty, this method MUST NOT update + * the Host header in the returned request. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new UriInterface instance. + * + * @link http://tools.ietf.org/html/rfc3986#section-4.3 + * @param UriInterface $uri New request URI to use. + * @param bool $preserveHost Preserve the original state of the Host header. + * @return self + */ + public function withUri(UriInterface $uri, $preserveHost = false) + { + $clone = clone $this; + $clone->uri = $uri; + + if (!$preserveHost) { + if ($uri->getHost() !== '') { + $clone->headers->set('Host', $uri->getHost()); + } + } else { + if ($this->uri->getHost() !== '' && (!$this->hasHeader('Host') || $this->getHeader('Host') === null)) { + $clone->headers->set('Host', $uri->getHost()); + } + } + + return $clone; + } + + /** + * Get request content type. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string|null The request content type, if known + */ + public function getContentType() + { + $result = $this->getHeader('Content-Type'); + + return $result ? $result[0] : null; + } + + /** + * Get request media type, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string|null The request media type, minus content-type params + */ + public function getMediaType() + { + $contentType = $this->getContentType(); + if ($contentType) { + $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); + + return strtolower($contentTypeParts[0]); + } + + return null; + } + + /** + * Get request media type params, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return array + */ + public function getMediaTypeParams() + { + $contentType = $this->getContentType(); + $contentTypeParams = []; + if ($contentType) { + $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); + $contentTypePartsLength = count($contentTypeParts); + for ($i = 1; $i < $contentTypePartsLength; $i++) { + $paramParts = explode('=', $contentTypeParts[$i]); + $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1]; + } + } + + return $contentTypeParams; + } + + /** + * Get request content character set, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string|null + */ + public function getContentCharset() + { + $mediaTypeParams = $this->getMediaTypeParams(); + if (isset($mediaTypeParams['charset'])) { + return $mediaTypeParams['charset']; + } + + return null; + } + + /** + * Get request content length, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return int|null + */ + public function getContentLength() + { + $result = $this->headers->get('Content-Length'); + + return $result ? (int)$result[0] : null; + } + + /******************************************************************************* + * Cookies + ******************************************************************************/ + + /** + * Retrieve cookies. + * + * Retrieves cookies sent by the client to the server. + * + * The data MUST be compatible with the structure of the $_COOKIE + * superglobal. + * + * @return array + */ + public function getCookieParams() + { + return $this->cookies; + } + + /** + * Fetch cookie value from cookies sent by the client to the server. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * + * @return mixed + */ + public function getCookieParam($key, $default = null) + { + $cookies = $this->getCookieParams(); + $result = $default; + if (isset($cookies[$key])) { + $result = $cookies[$key]; + } + + return $result; + } + + /** + * Return an instance with the specified cookies. + * + * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST + * be compatible with the structure of $_COOKIE. Typically, this data will + * be injected at instantiation. + * + * This method MUST NOT update the related Cookie header of the request + * instance, nor related values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated cookie values. + * + * @param array $cookies Array of key/value pairs representing cookies. + * @return self + */ + public function withCookieParams(array $cookies) + { + $clone = clone $this; + $clone->cookies = $cookies; + + return $clone; + } + + /******************************************************************************* + * Query Params + ******************************************************************************/ + + /** + * Retrieve query string arguments. + * + * Retrieves the deserialized query string arguments, if any. + * + * Note: the query params might not be in sync with the URI or server + * params. If you need to ensure you are only getting the original + * values, you may need to parse the query string from `getUri()->getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams() + { + if (is_array($this->queryParams)) { + return $this->queryParams; + } + + if ($this->uri === null) { + return []; + } + + parse_str($this->uri->getQuery(), $this->queryParams); // <-- URL decodes data + + return $this->queryParams; + } + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return self + */ + public function withQueryParams(array $query) + { + $clone = clone $this; + $clone->queryParams = $query; + + return $clone; + } + + /******************************************************************************* + * File Params + ******************************************************************************/ + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles() + { + return $this->uploadedFiles; + } + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return self + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles) + { + $clone = clone $this; + $clone->uploadedFiles = $uploadedFiles; + + return $clone; + } + + /******************************************************************************* + * Server Params + ******************************************************************************/ + + /** + * Retrieve server parameters. + * + * Retrieves data related to the incoming request environment, + * typically derived from PHP's $_SERVER superglobal. The data IS NOT + * REQUIRED to originate from $_SERVER. + * + * @return array + */ + public function getServerParams() + { + return $this->serverParams; + } + + /** + * Retrieve a server parameter. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function getServerParam($key, $default = null) + { + $serverParams = $this->getServerParams(); + + return isset($serverParams[$key]) ? $serverParams[$key] : $default; + } + + /******************************************************************************* + * Attributes + ******************************************************************************/ + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes() + { + return $this->attributes->all(); + } + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null) + { + return $this->attributes->get($name, $default); + } + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return self + */ + public function withAttribute($name, $value) + { + $clone = clone $this; + $clone->attributes->set($name, $value); + + return $clone; + } + + /** + * Create a new instance with the specified derived request attributes. + * + * Note: This method is not part of the PSR-7 standard. + * + * This method allows setting all new derived request attributes as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * updated attributes. + * + * @param array $attributes New attributes + * @return self + */ + public function withAttributes(array $attributes) + { + $clone = clone $this; + $clone->attributes = new Collection($attributes); + + return $clone; + } + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return self + */ + public function withoutAttribute($name) + { + $clone = clone $this; + $clone->attributes->remove($name); + + return $clone; + } + + /******************************************************************************* + * Body + ******************************************************************************/ + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + * @throws RuntimeException if the request body media type parser returns an invalid value + */ + public function getParsedBody() + { + if ($this->bodyParsed !== false) { + return $this->bodyParsed; + } + + if (!$this->body) { + return null; + } + + $mediaType = $this->getMediaType(); + + // look for a media type with a structured syntax suffix (RFC 6839) + $parts = explode('+', $mediaType); + if (count($parts) >= 2) { + $mediaType = 'application/' . $parts[count($parts)-1]; + } + + if (isset($this->bodyParsers[$mediaType]) === true) { + $body = (string)$this->getBody(); + $parsed = $this->bodyParsers[$mediaType]($body); + + if (!is_null($parsed) && !is_object($parsed) && !is_array($parsed)) { + throw new RuntimeException( + 'Request body media type parser return value must be an array, an object, or null' + ); + } + $this->bodyParsed = $parsed; + return $this->bodyParsed; + } + + return null; + } + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return self + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data) + { + if (!is_null($data) && !is_object($data) && !is_array($data)) { + throw new InvalidArgumentException('Parsed body value must be an array, an object, or null'); + } + + $clone = clone $this; + $clone->bodyParsed = $data; + + return $clone; + } + + /** + * Force Body to be parsed again. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return self + */ + public function reparseBody() + { + $this->bodyParsed = false; + + return $this; + } + + /** + * Register media type parser. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $mediaType A HTTP media type (excluding content-type + * params). + * @param callable $callable A callable that returns parsed contents for + * media type. + */ + public function registerMediaTypeParser($mediaType, callable $callable) + { + if ($callable instanceof Closure) { + $callable = $callable->bindTo($this); + } + $this->bodyParsers[(string)$mediaType] = $callable; + } + + /******************************************************************************* + * Parameters (e.g., POST and GET data) + ******************************************************************************/ + + /** + * Fetch request parameter value from body or query string (in that order). + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key The parameter key. + * @param string $default The default value. + * + * @return mixed The parameter value. + */ + public function getParam($key, $default = null) + { + $postParams = $this->getParsedBody(); + $getParams = $this->getQueryParams(); + $result = $default; + if (is_array($postParams) && isset($postParams[$key])) { + $result = $postParams[$key]; + } elseif (is_object($postParams) && property_exists($postParams, $key)) { + $result = $postParams->$key; + } elseif (isset($getParams[$key])) { + $result = $getParams[$key]; + } + + return $result; + } + + /** + * Fetch parameter value from request body. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param $key + * @param null $default + * + * @return null + */ + public function getParsedBodyParam($key, $default = null) + { + $postParams = $this->getParsedBody(); + $result = $default; + if (is_array($postParams) && isset($postParams[$key])) { + $result = $postParams[$key]; + } elseif (is_object($postParams) && property_exists($postParams, $key)) { + $result = $postParams->$key; + } + + return $result; + } + + /** + * Fetch parameter value from query string. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param $key + * @param null $default + * + * @return null + */ + public function getQueryParam($key, $default = null) + { + $getParams = $this->getQueryParams(); + $result = $default; + if (isset($getParams[$key])) { + $result = $getParams[$key]; + } + + return $result; + } + + /** + * Fetch assocative array of body and query string parameters. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return array + */ + public function getParams() + { + $params = $this->getQueryParams(); + $postParams = $this->getParsedBody(); + if ($postParams) { + $params = array_merge($params, (array)$postParams); + } + + return $params; + } +} diff --git a/src/vendor/slim/slim/Slim/Http/RequestBody.php b/src/vendor/slim/slim/Slim/Http/RequestBody.php new file mode 100644 index 0000000..2345fe4 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Http/RequestBody.php @@ -0,0 +1,27 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + //Successful 2xx + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + 226 => 'IM Used', + //Redirection 3xx + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => '(Unused)', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + //Client Error 4xx + 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 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 421 => 'Misdirected Request', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 444 => 'Connection Closed Without Response', + 451 => 'Unavailable For Legal Reasons', + 499 => 'Client Closed Request', + //Server Error 5xx + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + 599 => 'Network Connect Timeout Error', + ]; + + /** + * Create new HTTP response. + * + * @param int $status The response status code. + * @param HeadersInterface|null $headers The response headers. + * @param StreamInterface|null $body The response body. + */ + public function __construct($status = 200, HeadersInterface $headers = null, StreamInterface $body = null) + { + $this->status = $this->filterStatus($status); + $this->headers = $headers ? $headers : new Headers(); + $this->body = $body ? $body : new Body(fopen('php://temp', 'r+')); + } + + /** + * This method is applied to the cloned object + * after PHP performs an initial shallow-copy. This + * method completes a deep-copy by creating new objects + * for the cloned object's internal reference pointers. + */ + public function __clone() + { + $this->headers = clone $this->headers; + } + + /******************************************************************************* + * Status + ******************************************************************************/ + + /** + * Gets the response status code. + * + * The status code is a 3-digit integer result code of the server's attempt + * to understand and satisfy the request. + * + * @return int Status code. + */ + public function getStatusCode() + { + return $this->status; + } + + /** + * Return an instance with the specified status code and, optionally, reason phrase. + * + * If no reason phrase is specified, implementations MAY choose to default + * to the RFC 7231 or IANA recommended reason phrase for the response's + * status code. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated status and reason phrase. + * + * @link http://tools.ietf.org/html/rfc7231#section-6 + * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + * @param int $code The 3-digit integer result code to set. + * @param string $reasonPhrase The reason phrase to use with the + * provided status code; if none is provided, implementations MAY + * use the defaults as suggested in the HTTP specification. + * @return self + * @throws \InvalidArgumentException For invalid status code arguments. + */ + public function withStatus($code, $reasonPhrase = '') + { + $code = $this->filterStatus($code); + + if (!is_string($reasonPhrase) && !method_exists($reasonPhrase, '__toString')) { + throw new InvalidArgumentException('ReasonPhrase must be a string'); + } + + $clone = clone $this; + $clone->status = $code; + if ($reasonPhrase === '' && isset(static::$messages[$code])) { + $reasonPhrase = static::$messages[$code]; + } + + if ($reasonPhrase === '') { + throw new InvalidArgumentException('ReasonPhrase must be supplied for this code'); + } + + $clone->reasonPhrase = $reasonPhrase; + + return $clone; + } + + /** + * Filter HTTP status code. + * + * @param int $status HTTP status code. + * @return int + * @throws \InvalidArgumentException If an invalid HTTP status code is provided. + */ + protected function filterStatus($status) + { + if (!is_integer($status) || $status<100 || $status>599) { + throw new InvalidArgumentException('Invalid HTTP status code'); + } + + return $status; + } + + /** + * Gets the response reason phrase associated with the status code. + * + * Because a reason phrase is not a required element in a response + * status line, the reason phrase value MAY be null. Implementations MAY + * choose to return the default RFC 7231 recommended reason phrase (or those + * listed in the IANA HTTP Status Code Registry) for the response's + * status code. + * + * @link http://tools.ietf.org/html/rfc7231#section-6 + * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + * @return string Reason phrase; must return an empty string if none present. + */ + public function getReasonPhrase() + { + if ($this->reasonPhrase) { + return $this->reasonPhrase; + } + if (isset(static::$messages[$this->status])) { + return static::$messages[$this->status]; + } + return ''; + } + + /******************************************************************************* + * Body + ******************************************************************************/ + + /** + * Write data to the response body. + * + * Note: This method is not part of the PSR-7 standard. + * + * Proxies to the underlying stream and writes the provided data to it. + * + * @param string $data + * @return self + */ + public function write($data) + { + $this->getBody()->write($data); + + return $this; + } + + /******************************************************************************* + * Response Helpers + ******************************************************************************/ + + /** + * Redirect. + * + * Note: This method is not part of the PSR-7 standard. + * + * This method prepares the response object to return an HTTP Redirect + * response to the client. + * + * @param string|UriInterface $url The redirect destination. + * @param int|null $status The redirect HTTP status code. + * @return self + */ + public function withRedirect($url, $status = null) + { + $responseWithRedirect = $this->withHeader('Location', (string)$url); + + if (is_null($status) && $this->getStatusCode() === 200) { + $status = 302; + } + + if (!is_null($status)) { + return $responseWithRedirect->withStatus($status); + } + + return $responseWithRedirect; + } + + /** + * Json. + * + * Note: This method is not part of the PSR-7 standard. + * + * This method prepares the response object to return an HTTP Json + * response to the client. + * + * @param mixed $data The data + * @param int $status The HTTP status code. + * @param int $encodingOptions Json encoding options + * @throws \RuntimeException + * @return self + */ + public function withJson($data, $status = null, $encodingOptions = 0) + { + $response = $this->withBody(new Body(fopen('php://temp', 'r+'))); + $response->body->write($json = json_encode($data, $encodingOptions)); + + // Ensure that the json encoding passed successfully + if ($json === false) { + throw new \RuntimeException(json_last_error_msg(), json_last_error()); + } + + $responseWithJson = $response->withHeader('Content-Type', 'application/json;charset=utf-8'); + if (isset($status)) { + return $responseWithJson->withStatus($status); + } + return $responseWithJson; + } + + /** + * Is this response empty? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isEmpty() + { + return in_array($this->getStatusCode(), [204, 205, 304]); + } + + /** + * Is this response informational? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isInformational() + { + return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200; + } + + /** + * Is this response OK? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isOk() + { + return $this->getStatusCode() === 200; + } + + /** + * Is this response successful? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isSuccessful() + { + return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300; + } + + /** + * Is this response a redirect? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isRedirect() + { + return in_array($this->getStatusCode(), [301, 302, 303, 307]); + } + + /** + * Is this response a redirection? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isRedirection() + { + return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400; + } + + /** + * Is this response forbidden? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + * @api + */ + public function isForbidden() + { + return $this->getStatusCode() === 403; + } + + /** + * Is this response not Found? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isNotFound() + { + return $this->getStatusCode() === 404; + } + + /** + * Is this response a client error? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isClientError() + { + return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500; + } + + /** + * Is this response a server error? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isServerError() + { + return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600; + } + + /** + * Convert response to string. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string + */ + public function __toString() + { + $output = sprintf( + 'HTTP/%s %s %s', + $this->getProtocolVersion(), + $this->getStatusCode(), + $this->getReasonPhrase() + ); + $output .= PHP_EOL; + foreach ($this->getHeaders() as $name => $values) { + $output .= sprintf('%s: %s', $name, $this->getHeaderLine($name)) . PHP_EOL; + } + $output .= PHP_EOL; + $output .= (string)$this->getBody(); + + return $output; + } +} diff --git a/src/vendor/slim/slim/Slim/Http/Stream.php b/src/vendor/slim/slim/Slim/Http/Stream.php new file mode 100644 index 0000000..62c9562 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Http/Stream.php @@ -0,0 +1,450 @@ + ['r', 'r+', 'w+', 'a+', 'x+', 'c+'], + 'writable' => ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+'], + ]; + + /** + * The underlying stream resource + * + * @var resource + */ + protected $stream; + + /** + * Stream metadata + * + * @var array + */ + protected $meta; + + /** + * Is this stream readable? + * + * @var bool + */ + protected $readable; + + /** + * Is this stream writable? + * + * @var bool + */ + protected $writable; + + /** + * Is this stream seekable? + * + * @var bool + */ + protected $seekable; + + /** + * The size of the stream if known + * + * @var null|int + */ + protected $size; + + /** + * Is this stream a pipe? + * + * @var bool + */ + protected $isPipe; + + /** + * Create a new Stream. + * + * @param resource $stream A PHP resource handle. + * + * @throws InvalidArgumentException If argument is not a resource. + */ + public function __construct($stream) + { + $this->attach($stream); + } + + /** + * Get stream metadata as an associative array or retrieve a specific key. + * + * The keys returned are identical to the keys returned from PHP's + * stream_get_meta_data() function. + * + * @link http://php.net/manual/en/function.stream-get-meta-data.php + * + * @param string $key Specific metadata to retrieve. + * + * @return array|mixed|null Returns an associative array if no key is + * provided. Returns a specific key value if a key is provided and the + * value is found, or null if the key is not found. + */ + public function getMetadata($key = null) + { + $this->meta = stream_get_meta_data($this->stream); + if (is_null($key) === true) { + return $this->meta; + } + + return isset($this->meta[$key]) ? $this->meta[$key] : null; + } + + /** + * Is a resource attached to this stream? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + protected function isAttached() + { + return is_resource($this->stream); + } + + /** + * Attach new resource to this object. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param resource $newStream A PHP resource handle. + * + * @throws InvalidArgumentException If argument is not a valid PHP resource. + */ + protected function attach($newStream) + { + if (is_resource($newStream) === false) { + throw new InvalidArgumentException(__METHOD__ . ' argument must be a valid PHP resource'); + } + + if ($this->isAttached() === true) { + $this->detach(); + } + + $this->stream = $newStream; + } + + /** + * Separates any underlying resources from the stream. + * + * After the stream has been detached, the stream is in an unusable state. + * + * @return resource|null Underlying PHP stream, if any + */ + public function detach() + { + $oldResource = $this->stream; + $this->stream = null; + $this->meta = null; + $this->readable = null; + $this->writable = null; + $this->seekable = null; + $this->size = null; + $this->isPipe = null; + + return $oldResource; + } + + /** + * Reads all data from the stream into a string, from the beginning to end. + * + * This method MUST attempt to seek to the beginning of the stream before + * reading data and read the stream until the end is reached. + * + * Warning: This could attempt to load a large amount of data into memory. + * + * This method MUST NOT raise an exception in order to conform with PHP's + * string casting operations. + * + * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring + * @return string + */ + public function __toString() + { + if (!$this->isAttached()) { + return ''; + } + + try { + $this->rewind(); + return $this->getContents(); + } catch (RuntimeException $e) { + return ''; + } + } + + /** + * Closes the stream and any underlying resources. + */ + public function close() + { + if ($this->isAttached() === true) { + if ($this->isPipe()) { + pclose($this->stream); + } else { + fclose($this->stream); + } + } + + $this->detach(); + } + + /** + * Get the size of the stream if known. + * + * @return int|null Returns the size in bytes if known, or null if unknown. + */ + public function getSize() + { + if (!$this->size && $this->isAttached() === true) { + $stats = fstat($this->stream); + $this->size = isset($stats['size']) && !$this->isPipe() ? $stats['size'] : null; + } + + return $this->size; + } + + /** + * Returns the current position of the file read/write pointer + * + * @return int Position of the file pointer + * + * @throws RuntimeException on error. + */ + public function tell() + { + if (!$this->isAttached() || ($position = ftell($this->stream)) === false || $this->isPipe()) { + throw new RuntimeException('Could not get the position of the pointer in stream'); + } + + return $position; + } + + /** + * Returns true if the stream is at the end of the stream. + * + * @return bool + */ + public function eof() + { + return $this->isAttached() ? feof($this->stream) : true; + } + + /** + * Returns whether or not the stream is readable. + * + * @return bool + */ + public function isReadable() + { + if ($this->readable === null) { + if ($this->isPipe()) { + $this->readable = true; + } else { + $this->readable = false; + if ($this->isAttached()) { + $meta = $this->getMetadata(); + foreach (self::$modes['readable'] as $mode) { + if (strpos($meta['mode'], $mode) === 0) { + $this->readable = true; + break; + } + } + } + } + } + + return $this->readable; + } + + /** + * Returns whether or not the stream is writable. + * + * @return bool + */ + public function isWritable() + { + if ($this->writable === null) { + $this->writable = false; + if ($this->isAttached()) { + $meta = $this->getMetadata(); + foreach (self::$modes['writable'] as $mode) { + if (strpos($meta['mode'], $mode) === 0) { + $this->writable = true; + break; + } + } + } + } + + return $this->writable; + } + + /** + * Returns whether or not the stream is seekable. + * + * @return bool + */ + public function isSeekable() + { + if ($this->seekable === null) { + $this->seekable = false; + if ($this->isAttached()) { + $meta = $this->getMetadata(); + $this->seekable = !$this->isPipe() && $meta['seekable']; + } + } + + return $this->seekable; + } + + /** + * Seek to a position in the stream. + * + * @link http://www.php.net/manual/en/function.fseek.php + * + * @param int $offset Stream offset + * @param int $whence Specifies how the cursor position will be calculated + * based on the seek offset. Valid values are identical to the built-in + * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to + * offset bytes SEEK_CUR: Set position to current location plus offset + * SEEK_END: Set position to end-of-stream plus offset. + * + * @throws RuntimeException on failure. + */ + public function seek($offset, $whence = SEEK_SET) + { + // Note that fseek returns 0 on success! + if (!$this->isSeekable() || fseek($this->stream, $offset, $whence) === -1) { + throw new RuntimeException('Could not seek in stream'); + } + } + + /** + * Seek to the beginning of the stream. + * + * If the stream is not seekable, this method will raise an exception; + * otherwise, it will perform a seek(0). + * + * @see seek() + * + * @link http://www.php.net/manual/en/function.fseek.php + * + * @throws RuntimeException on failure. + */ + public function rewind() + { + if (!$this->isSeekable() || rewind($this->stream) === false) { + throw new RuntimeException('Could not rewind stream'); + } + } + + /** + * Read data from the stream. + * + * @param int $length Read up to $length bytes from the object and return + * them. Fewer than $length bytes may be returned if underlying stream + * call returns fewer bytes. + * + * @return string Returns the data read from the stream, or an empty string + * if no bytes are available. + * + * @throws RuntimeException if an error occurs. + */ + public function read($length) + { + if (!$this->isReadable() || ($data = fread($this->stream, $length)) === false) { + throw new RuntimeException('Could not read from stream'); + } + + return $data; + } + + /** + * Write data to the stream. + * + * @param string $string The string that is to be written. + * + * @return int Returns the number of bytes written to the stream. + * + * @throws RuntimeException on failure. + */ + public function write($string) + { + if (!$this->isWritable() || ($written = fwrite($this->stream, $string)) === false) { + throw new RuntimeException('Could not write to stream'); + } + + // reset size so that it will be recalculated on next call to getSize() + $this->size = null; + + return $written; + } + + /** + * Returns the remaining contents in a string + * + * @return string + * + * @throws RuntimeException if unable to read or an error occurs while + * reading. + */ + public function getContents() + { + if (!$this->isReadable() || ($contents = stream_get_contents($this->stream)) === false) { + throw new RuntimeException('Could not get contents of stream'); + } + + return $contents; + } + + /** + * Returns whether or not the stream is a pipe. + * + * @return bool + */ + public function isPipe() + { + if ($this->isPipe === null) { + $this->isPipe = false; + if ($this->isAttached()) { + $mode = fstat($this->stream)['mode']; + $this->isPipe = ($mode & self::FSTAT_MODE_S_IFIFO) !== 0; + } + } + + return $this->isPipe; + } +} diff --git a/src/vendor/slim/slim/Slim/Http/UploadedFile.php b/src/vendor/slim/slim/Slim/Http/UploadedFile.php new file mode 100644 index 0000000..48ed3ff --- /dev/null +++ b/src/vendor/slim/slim/Slim/Http/UploadedFile.php @@ -0,0 +1,327 @@ +has('slim.files')) { + return $env['slim.files']; + } elseif (isset($_FILES)) { + return static::parseUploadedFiles($_FILES); + } + + return []; + } + + /** + * Parse a non-normalized, i.e. $_FILES superglobal, tree of uploaded file data. + * + * @param array $uploadedFiles The non-normalized tree of uploaded file data. + * + * @return array A normalized tree of UploadedFile instances. + */ + private static function parseUploadedFiles(array $uploadedFiles) + { + $parsed = []; + foreach ($uploadedFiles as $field => $uploadedFile) { + if (!isset($uploadedFile['error'])) { + if (is_array($uploadedFile)) { + $parsed[$field] = static::parseUploadedFiles($uploadedFile); + } + continue; + } + + $parsed[$field] = []; + if (!is_array($uploadedFile['error'])) { + $parsed[$field] = new static( + $uploadedFile['tmp_name'], + isset($uploadedFile['name']) ? $uploadedFile['name'] : null, + isset($uploadedFile['type']) ? $uploadedFile['type'] : null, + isset($uploadedFile['size']) ? $uploadedFile['size'] : null, + $uploadedFile['error'], + true + ); + } else { + $subArray = []; + foreach ($uploadedFile['error'] as $fileIdx => $error) { + // normalise subarray and re-parse to move the input's keyname up a level + $subArray[$fileIdx]['name'] = $uploadedFile['name'][$fileIdx]; + $subArray[$fileIdx]['type'] = $uploadedFile['type'][$fileIdx]; + $subArray[$fileIdx]['tmp_name'] = $uploadedFile['tmp_name'][$fileIdx]; + $subArray[$fileIdx]['error'] = $uploadedFile['error'][$fileIdx]; + $subArray[$fileIdx]['size'] = $uploadedFile['size'][$fileIdx]; + + $parsed[$field] = static::parseUploadedFiles($subArray); + } + } + } + + return $parsed; + } + + /** + * Construct a new UploadedFile instance. + * + * @param string $file The full path to the uploaded file provided by the client. + * @param string|null $name The file name. + * @param string|null $type The file media type. + * @param int|null $size The file size in bytes. + * @param int $error The UPLOAD_ERR_XXX code representing the status of the upload. + * @param bool $sapi Indicates if the upload is in a SAPI environment. + */ + public function __construct($file, $name = null, $type = null, $size = null, $error = UPLOAD_ERR_OK, $sapi = false) + { + $this->file = $file; + $this->name = $name; + $this->type = $type; + $this->size = $size; + $this->error = $error; + $this->sapi = $sapi; + } + + /** + * Retrieve a stream representing the uploaded file. + * + * This method MUST return a StreamInterface instance, representing the + * uploaded file. The purpose of this method is to allow utilizing native PHP + * stream functionality to manipulate the file upload, such as + * stream_copy_to_stream() (though the result will need to be decorated in a + * native PHP stream wrapper to work with such functions). + * + * If the moveTo() method has been called previously, this method MUST raise + * an exception. + * + * @return StreamInterface Stream representation of the uploaded file. + * @throws \RuntimeException in cases when no stream is available or can be + * created. + */ + public function getStream() + { + if ($this->moved) { + throw new \RuntimeException(sprintf('Uploaded file %1s has already been moved', $this->name)); + } + if ($this->stream === null) { + $this->stream = new Stream(fopen($this->file, 'r')); + } + + return $this->stream; + } + + /** + * Move the uploaded file to a new location. + * + * Use this method as an alternative to move_uploaded_file(). This method is + * guaranteed to work in both SAPI and non-SAPI environments. + * Implementations must determine which environment they are in, and use the + * appropriate method (move_uploaded_file(), rename(), or a stream + * operation) to perform the operation. + * + * $targetPath may be an absolute path, or a relative path. If it is a + * relative path, resolution should be the same as used by PHP's rename() + * function. + * + * The original file or stream MUST be removed on completion. + * + * If this method is called more than once, any subsequent calls MUST raise + * an exception. + * + * When used in an SAPI environment where $_FILES is populated, when writing + * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be + * used to ensure permissions and upload status are verified correctly. + * + * If you wish to move to a stream, use getStream(), as SAPI operations + * cannot guarantee writing to stream destinations. + * + * @see http://php.net/is_uploaded_file + * @see http://php.net/move_uploaded_file + * + * @param string $targetPath Path to which to move the uploaded file. + * + * @throws InvalidArgumentException if the $path specified is invalid. + * @throws RuntimeException on any error during the move operation, or on + * the second or subsequent call to the method. + */ + public function moveTo($targetPath) + { + if ($this->moved) { + throw new RuntimeException('Uploaded file already moved'); + } + + $targetIsStream = strpos($targetPath, '://') > 0; + if (!$targetIsStream && !is_writable(dirname($targetPath))) { + throw new InvalidArgumentException('Upload target path is not writable'); + } + + if ($targetIsStream) { + if (!copy($this->file, $targetPath)) { + throw new RuntimeException(sprintf('Error moving uploaded file %1s to %2s', $this->name, $targetPath)); + } + if (!unlink($this->file)) { + throw new RuntimeException(sprintf('Error removing uploaded file %1s', $this->name)); + } + } elseif ($this->sapi) { + if (!is_uploaded_file($this->file)) { + throw new RuntimeException(sprintf('%1s is not a valid uploaded file', $this->file)); + } + + if (!move_uploaded_file($this->file, $targetPath)) { + throw new RuntimeException(sprintf('Error moving uploaded file %1s to %2s', $this->name, $targetPath)); + } + } else { + if (!rename($this->file, $targetPath)) { + throw new RuntimeException(sprintf('Error moving uploaded file %1s to %2s', $this->name, $targetPath)); + } + } + + $this->moved = true; + } + + /** + * Retrieve the error associated with the uploaded file. + * + * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants. + * + * If the file was uploaded successfully, this method MUST return + * UPLOAD_ERR_OK. + * + * Implementations SHOULD return the value stored in the "error" key of + * the file in the $_FILES array. + * + * @see http://php.net/manual/en/features.file-upload.errors.php + * + * @return int One of PHP's UPLOAD_ERR_XXX constants. + */ + public function getError() + { + return $this->error; + } + + /** + * Retrieve the filename sent by the client. + * + * Do not trust the value returned by this method. A client could send + * a malicious filename with the intention to corrupt or hack your + * application. + * + * Implementations SHOULD return the value stored in the "name" key of + * the file in the $_FILES array. + * + * @return string|null The filename sent by the client or null if none + * was provided. + */ + public function getClientFilename() + { + return $this->name; + } + + /** + * Retrieve the media type sent by the client. + * + * Do not trust the value returned by this method. A client could send + * a malicious media type with the intention to corrupt or hack your + * application. + * + * Implementations SHOULD return the value stored in the "type" key of + * the file in the $_FILES array. + * + * @return string|null The media type sent by the client or null if none + * was provided. + */ + public function getClientMediaType() + { + return $this->type; + } + + /** + * Retrieve the file size. + * + * Implementations SHOULD return the value stored in the "size" key of + * the file in the $_FILES array if available, as PHP calculates this based + * on the actual size transmitted. + * + * @return int|null The file size in bytes or null if unknown. + */ + public function getSize() + { + return $this->size; + } +} diff --git a/src/vendor/slim/slim/Slim/Http/Uri.php b/src/vendor/slim/slim/Slim/Http/Uri.php new file mode 100644 index 0000000..6b87747 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Http/Uri.php @@ -0,0 +1,824 @@ +scheme = $this->filterScheme($scheme); + $this->host = $host; + $this->port = $this->filterPort($port); + $this->path = empty($path) ? '/' : $this->filterPath($path); + $this->query = $this->filterQuery($query); + $this->fragment = $this->filterQuery($fragment); + $this->user = $user; + $this->password = $password; + } + + /** + * Create new Uri from string. + * + * @param string $uri Complete Uri string + * (i.e., https://user:pass@host:443/path?query). + * + * @return self + */ + public static function createFromString($uri) + { + if (!is_string($uri) && !method_exists($uri, '__toString')) { + throw new InvalidArgumentException('Uri must be a string'); + } + + $parts = parse_url($uri); + $scheme = isset($parts['scheme']) ? $parts['scheme'] : ''; + $user = isset($parts['user']) ? $parts['user'] : ''; + $pass = isset($parts['pass']) ? $parts['pass'] : ''; + $host = isset($parts['host']) ? $parts['host'] : ''; + $port = isset($parts['port']) ? $parts['port'] : null; + $path = isset($parts['path']) ? $parts['path'] : ''; + $query = isset($parts['query']) ? $parts['query'] : ''; + $fragment = isset($parts['fragment']) ? $parts['fragment'] : ''; + + return new static($scheme, $host, $port, $path, $query, $fragment, $user, $pass); + } + + /** + * Create new Uri from environment. + * + * @param Environment $env + * + * @return self + */ + public static function createFromEnvironment(Environment $env) + { + // Scheme + $isSecure = $env->get('HTTPS'); + $scheme = (empty($isSecure) || $isSecure === 'off') ? 'http' : 'https'; + + // Authority: Username and password + $username = $env->get('PHP_AUTH_USER', ''); + $password = $env->get('PHP_AUTH_PW', ''); + + // Authority: Host + if ($env->has('HTTP_HOST')) { + $host = $env->get('HTTP_HOST'); + } else { + $host = $env->get('SERVER_NAME'); + } + + // Authority: Port + $port = (int)$env->get('SERVER_PORT', 80); + if (preg_match('/^(\[[a-fA-F0-9:.]+\])(:\d+)?\z/', $host, $matches)) { + $host = $matches[1]; + + if ($matches[2]) { + $port = (int) substr($matches[2], 1); + } + } else { + $pos = strpos($host, ':'); + if ($pos !== false) { + $port = (int) substr($host, $pos + 1); + $host = strstr($host, ':', true); + } + } + + // Path + $requestScriptName = parse_url($env->get('SCRIPT_NAME'), PHP_URL_PATH); + $requestScriptDir = dirname($requestScriptName); + + // parse_url() requires a full URL. As we don't extract the domain name or scheme, + // we use a stand-in. + $requestUri = parse_url('http://example.com' . $env->get('REQUEST_URI'), PHP_URL_PATH); + + $basePath = ''; + $virtualPath = $requestUri; + if (stripos($requestUri, $requestScriptName) === 0) { + $basePath = $requestScriptName; + } elseif ($requestScriptDir !== '/' && stripos($requestUri, $requestScriptDir) === 0) { + $basePath = $requestScriptDir; + } + + if ($basePath) { + $virtualPath = ltrim(substr($requestUri, strlen($basePath)), '/'); + } + + // Query string + $queryString = $env->get('QUERY_STRING', ''); + if ($queryString === '') { + $queryString = parse_url('http://example.com' . $env->get('REQUEST_URI'), PHP_URL_QUERY); + } + + // Fragment + $fragment = ''; + + // Build Uri + $uri = new static($scheme, $host, $port, $virtualPath, $queryString, $fragment, $username, $password); + if ($basePath) { + $uri = $uri->withBasePath($basePath); + } + + return $uri; + } + + /******************************************************************************** + * Scheme + *******************************************************************************/ + + /** + * Retrieve the scheme component of the URI. + * + * If no scheme is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.1. + * + * The trailing ":" character is not part of the scheme and MUST NOT be + * added. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.1 + * @return string The URI scheme. + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return self A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme) + { + $scheme = $this->filterScheme($scheme); + $clone = clone $this; + $clone->scheme = $scheme; + + return $clone; + } + + /** + * Filter Uri scheme. + * + * @param string $scheme Raw Uri scheme. + * @return string + * + * @throws InvalidArgumentException If the Uri scheme is not a string. + * @throws InvalidArgumentException If Uri scheme is not "", "https", or "http". + */ + protected function filterScheme($scheme) + { + static $valid = [ + '' => true, + 'https' => true, + 'http' => true, + ]; + + if (!is_string($scheme) && !method_exists($scheme, '__toString')) { + throw new InvalidArgumentException('Uri scheme must be a string'); + } + + $scheme = str_replace('://', '', strtolower((string)$scheme)); + if (!isset($valid[$scheme])) { + throw new InvalidArgumentException('Uri scheme must be one of: "", "https", "http"'); + } + + return $scheme; + } + + /******************************************************************************** + * Authority + *******************************************************************************/ + + /** + * Retrieve the authority component of the URI. + * + * If no authority information is present, this method MUST return an empty + * string. + * + * The authority syntax of the URI is: + * + *
+     * [user-info@]host[:port]
+     * 
+ * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority() + { + $userInfo = $this->getUserInfo(); + $host = $this->getHost(); + $port = $this->getPort(); + + return ($userInfo ? $userInfo . '@' : '') . $host . ($port !== null ? ':' . $port : ''); + } + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo() + { + return $this->user . ($this->password ? ':' . $this->password : ''); + } + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return self A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null) + { + $clone = clone $this; + $clone->user = $user; + $clone->password = $password ? $password : ''; + + return $clone; + } + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost() + { + return $this->host; + } + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return self A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host) + { + $clone = clone $this; + $clone->host = $host; + + return $clone; + } + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort() + { + return $this->port && !$this->hasStandardPort() ? $this->port : null; + } + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return self A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port) + { + $port = $this->filterPort($port); + $clone = clone $this; + $clone->port = $port; + + return $clone; + } + + /** + * Does this Uri use a standard port? + * + * @return bool + */ + protected function hasStandardPort() + { + return ($this->scheme === 'http' && $this->port === 80) || ($this->scheme === 'https' && $this->port === 443); + } + + /** + * Filter Uri port. + * + * @param null|int $port The Uri port number. + * @return null|int + * + * @throws InvalidArgumentException If the port is invalid. + */ + protected function filterPort($port) + { + if (is_null($port) || (is_integer($port) && ($port >= 1 && $port <= 65535))) { + return $port; + } + + throw new InvalidArgumentException('Uri port must be null or an integer between 1 and 65535 (inclusive)'); + } + + /******************************************************************************** + * Path + *******************************************************************************/ + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath() + { + return $this->path; + } + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return self A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path) + { + if (!is_string($path)) { + throw new InvalidArgumentException('Uri path must be a string'); + } + + $clone = clone $this; + $clone->path = $this->filterPath($path); + + // if the path is absolute, then clear basePath + if (substr($path, 0, 1) == '/') { + $clone->basePath = ''; + } + + return $clone; + } + + /** + * Retrieve the base path segment of the URI. + * + * Note: This method is not part of the PSR-7 standard. + * + * This method MUST return a string; if no path is present it MUST return + * an empty string. + * + * @return string The base path segment of the URI. + */ + public function getBasePath() + { + return $this->basePath; + } + + /** + * Set base path. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $basePath + * @return self + */ + public function withBasePath($basePath) + { + if (!is_string($basePath)) { + throw new InvalidArgumentException('Uri path must be a string'); + } + if (!empty($basePath)) { + $basePath = '/' . trim($basePath, '/'); // <-- Trim on both sides + } + $clone = clone $this; + + if ($basePath !== '/') { + $clone->basePath = $this->filterPath($basePath); + } + + return $clone; + } + + /** + * Filter Uri path. + * + * This method percent-encodes all reserved + * characters in the provided path string. This method + * will NOT double-encode characters that are already + * percent-encoded. + * + * @param string $path The raw uri path. + * @return string The RFC 3986 percent-encoded uri path. + * @link http://www.faqs.org/rfcs/rfc3986.html + */ + protected function filterPath($path) + { + return preg_replace_callback( + '/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', + function ($match) { + return rawurlencode($match[0]); + }, + $path + ); + } + + /******************************************************************************** + * Query + *******************************************************************************/ + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery() + { + return $this->query; + } + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return self A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query) + { + if (!is_string($query) && !method_exists($query, '__toString')) { + throw new InvalidArgumentException('Uri query must be a string'); + } + $query = ltrim((string)$query, '?'); + $clone = clone $this; + $clone->query = $this->filterQuery($query); + + return $clone; + } + + /** + * Filters the query string or fragment of a URI. + * + * @param string $query The raw uri query string. + * @return string The percent-encoded query string. + */ + protected function filterQuery($query) + { + return preg_replace_callback( + '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', + function ($match) { + return rawurlencode($match[0]); + }, + $query + ); + } + + /******************************************************************************** + * Fragment + *******************************************************************************/ + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment() + { + return $this->fragment; + } + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return self A new instance with the specified fragment. + */ + public function withFragment($fragment) + { + if (!is_string($fragment) && !method_exists($fragment, '__toString')) { + throw new InvalidArgumentException('Uri fragment must be a string'); + } + $fragment = ltrim((string)$fragment, '#'); + $clone = clone $this; + $clone->fragment = $this->filterQuery($fragment); + + return $clone; + } + + /******************************************************************************** + * Helpers + *******************************************************************************/ + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString() + { + $scheme = $this->getScheme(); + $authority = $this->getAuthority(); + $basePath = $this->getBasePath(); + $path = $this->getPath(); + $query = $this->getQuery(); + $fragment = $this->getFragment(); + + $path = $basePath . '/' . ltrim($path, '/'); + + return ($scheme ? $scheme . ':' : '') + . ($authority ? '//' . $authority : '') + . $path + . ($query ? '?' . $query : '') + . ($fragment ? '#' . $fragment : ''); + } + + /** + * Return the fully qualified base URL. + * + * Note that this method never includes a trailing / + * + * This method is not part of PSR-7. + * + * @return string + */ + public function getBaseUrl() + { + $scheme = $this->getScheme(); + $authority = $this->getAuthority(); + $basePath = $this->getBasePath(); + + if ($authority && substr($basePath, 0, 1) !== '/') { + $basePath = $basePath . '/' . $basePath; + } + + return ($scheme ? $scheme . ':' : '') + . ($authority ? '//' . $authority : '') + . rtrim($basePath, '/'); + } +} diff --git a/src/vendor/slim/slim/Slim/Interfaces/CallableResolverInterface.php b/src/vendor/slim/slim/Slim/Interfaces/CallableResolverInterface.php new file mode 100644 index 0000000..9bde59a --- /dev/null +++ b/src/vendor/slim/slim/Slim/Interfaces/CallableResolverInterface.php @@ -0,0 +1,27 @@ +middlewareLock) { + throw new RuntimeException('Middleware can’t be added once the stack is dequeuing'); + } + + if (is_null($this->stack)) { + $this->seedMiddlewareStack(); + } + $next = $this->stack->top(); + $this->stack[] = function (ServerRequestInterface $req, ResponseInterface $res) use ($callable, $next) { + $result = call_user_func($callable, $req, $res, $next); + if ($result instanceof ResponseInterface === false) { + throw new UnexpectedValueException( + 'Middleware must return instance of \Psr\Http\Message\ResponseInterface' + ); + } + + return $result; + }; + + return $this; + } + + /** + * Seed middleware stack with first callable + * + * @param callable $kernel The last item to run as middleware + * + * @throws RuntimeException if the stack is seeded more than once + */ + protected function seedMiddlewareStack(callable $kernel = null) + { + if (!is_null($this->stack)) { + throw new RuntimeException('MiddlewareStack can only be seeded once.'); + } + if ($kernel === null) { + $kernel = $this; + } + $this->stack = new SplStack; + $this->stack->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO | SplDoublyLinkedList::IT_MODE_KEEP); + $this->stack[] = $kernel; + } + + /** + * Call middleware stack + * + * @param ServerRequestInterface $req A request object + * @param ResponseInterface $res A response object + * + * @return ResponseInterface + */ + public function callMiddlewareStack(ServerRequestInterface $req, ResponseInterface $res) + { + if (is_null($this->stack)) { + $this->seedMiddlewareStack(); + } + /** @var callable $start */ + $start = $this->stack->top(); + $this->middlewareLock = true; + $resp = $start($req, $res); + $this->middlewareLock = false; + return $resp; + } +} diff --git a/src/vendor/slim/slim/Slim/Routable.php b/src/vendor/slim/slim/Slim/Routable.php new file mode 100644 index 0000000..dff5d77 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Routable.php @@ -0,0 +1,106 @@ +middleware; + } + + /** + * Get the route pattern + * + * @return string + */ + public function getPattern() + { + return $this->pattern; + } + + /** + * Set container for use with resolveCallable + * + * @param ContainerInterface $container + * + * @return self + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + return $this; + } + + /** + * Prepend middleware to the middleware collection + * + * @param callable|string $callable The callback routine + * + * @return static + */ + public function add($callable) + { + $this->middleware[] = new DeferredCallable($callable, $this->container); + return $this; + } + + /** + * Set the route pattern + * + * @set string + */ + public function setPattern($newPattern) + { + $this->pattern = $newPattern; + } +} diff --git a/src/vendor/slim/slim/Slim/Route.php b/src/vendor/slim/slim/Slim/Route.php new file mode 100644 index 0000000..3920820 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Route.php @@ -0,0 +1,381 @@ +methods = is_string($methods) ? [$methods] : $methods; + $this->pattern = $pattern; + $this->callable = $callable; + $this->groups = $groups; + $this->identifier = 'route' . $identifier; + } + + /** + * Finalize the route in preparation for dispatching + */ + public function finalize() + { + if ($this->finalized) { + return; + } + + $groupMiddleware = []; + foreach ($this->getGroups() as $group) { + $groupMiddleware = array_merge($group->getMiddleware(), $groupMiddleware); + } + + $this->middleware = array_merge($this->middleware, $groupMiddleware); + + foreach ($this->getMiddleware() as $middleware) { + $this->addMiddleware($middleware); + } + + $this->finalized = true; + } + + /** + * Get route callable + * + * @return callable + */ + public function getCallable() + { + return $this->callable; + } + + /** + * This method enables you to override the Route's callable + * + * @param string|\Closure $callable + */ + public function setCallable($callable) + { + $this->callable = $callable; + } + + /** + * Get route methods + * + * @return string[] + */ + public function getMethods() + { + return $this->methods; + } + + /** + * Get parent route groups + * + * @return RouteGroup[] + */ + public function getGroups() + { + return $this->groups; + } + + /** + * Get route name + * + * @return null|string + */ + public function getName() + { + return $this->name; + } + + /** + * Get route identifier + * + * @return string + */ + public function getIdentifier() + { + return $this->identifier; + } + + /** + * Get output buffering mode + * + * @return boolean|string + */ + public function getOutputBuffering() + { + return $this->outputBuffering; + } + + /** + * Set output buffering mode + * + * One of: false, 'prepend' or 'append' + * + * @param boolean|string $mode + * + * @throws InvalidArgumentException If an unknown buffering mode is specified + */ + public function setOutputBuffering($mode) + { + if (!in_array($mode, [false, 'prepend', 'append'], true)) { + throw new InvalidArgumentException('Unknown output buffering mode'); + } + $this->outputBuffering = $mode; + } + + /** + * Set route name + * + * @param string $name + * + * @return self + * + * @throws InvalidArgumentException if the route name is not a string + */ + public function setName($name) + { + if (!is_string($name)) { + throw new InvalidArgumentException('Route name must be a string'); + } + $this->name = $name; + return $this; + } + + /** + * Set a route argument + * + * @param string $name + * @param string $value + * + * @return self + */ + public function setArgument($name, $value) + { + $this->arguments[$name] = $value; + return $this; + } + + /** + * Replace route arguments + * + * @param array $arguments + * + * @return self + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + return $this; + } + + /** + * Retrieve route arguments + * + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Retrieve a specific route argument + * + * @param string $name + * @param mixed $default + * + * @return mixed + */ + public function getArgument($name, $default = null) + { + if (array_key_exists($name, $this->arguments)) { + return $this->arguments[$name]; + } + return $default; + } + + /******************************************************************************** + * Route Runner + *******************************************************************************/ + + /** + * Prepare the route for use + * + * @param ServerRequestInterface $request + * @param array $arguments + */ + public function prepare(ServerRequestInterface $request, array $arguments) + { + // Add the arguments + foreach ($arguments as $k => $v) { + $this->setArgument($k, $v); + } + } + + /** + * Run route + * + * This method traverses the middleware stack, including the route's callable + * and captures the resultant HTTP response object. It then sends the response + * back to the Application. + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * + * @return ResponseInterface + */ + public function run(ServerRequestInterface $request, ResponseInterface $response) + { + // Finalise route now that we are about to run it + $this->finalize(); + + // Traverse middleware stack and fetch updated response + return $this->callMiddlewareStack($request, $response); + } + + /** + * Dispatch route callable against current Request and Response objects + * + * This method invokes the route object's callable. If middleware is + * registered for the route, each callable middleware is invoked in + * the order specified. + * + * @param ServerRequestInterface $request The current Request object + * @param ResponseInterface $response The current Response object + * @return \Psr\Http\Message\ResponseInterface + * @throws \Exception if the route callable throws an exception + */ + public function __invoke(ServerRequestInterface $request, ResponseInterface $response) + { + $this->callable = $this->resolveCallable($this->callable); + + /** @var InvocationStrategyInterface $handler */ + $handler = isset($this->container) ? $this->container->get('foundHandler') : new RequestResponse(); + + // invoke route callable + if ($this->outputBuffering === false) { + $newResponse = $handler($this->callable, $request, $response, $this->arguments); + } else { + try { + ob_start(); + $newResponse = $handler($this->callable, $request, $response, $this->arguments); + $output = ob_get_clean(); + // @codeCoverageIgnoreStart + } catch (Throwable $e) { + ob_end_clean(); + throw $e; + // @codeCoverageIgnoreEnd + } catch (Exception $e) { + ob_end_clean(); + throw $e; + } + } + + if ($newResponse instanceof ResponseInterface) { + // if route callback returns a ResponseInterface, then use it + $response = $newResponse; + } elseif (is_string($newResponse)) { + // if route callback returns a string, then append it to the response + if ($response->getBody()->isWritable()) { + $response->getBody()->write($newResponse); + } + } + + if (!empty($output) && $response->getBody()->isWritable()) { + if ($this->outputBuffering === 'prepend') { + // prepend output buffer content + $body = new Http\Body(fopen('php://temp', 'r+')); + $body->write($output . $response->getBody()); + $response = $response->withBody($body); + } elseif ($this->outputBuffering === 'append') { + // append output buffer content + $response->getBody()->write($output); + } + } + + return $response; + } +} diff --git a/src/vendor/slim/slim/Slim/RouteGroup.php b/src/vendor/slim/slim/Slim/RouteGroup.php new file mode 100644 index 0000000..a0cdf4d --- /dev/null +++ b/src/vendor/slim/slim/Slim/RouteGroup.php @@ -0,0 +1,47 @@ +pattern = $pattern; + $this->callable = $callable; + } + + /** + * Invoke the group to register any Routable objects within it. + * + * @param App $app The App to bind the callable to. + */ + public function __invoke(App $app = null) + { + $callable = $this->resolveCallable($this->callable); + if ($callable instanceof Closure && $app !== null) { + $callable = $callable->bindTo($app); + } + + $callable(); + } +} diff --git a/src/vendor/slim/slim/Slim/Router.php b/src/vendor/slim/slim/Slim/Router.php new file mode 100644 index 0000000..41fec40 --- /dev/null +++ b/src/vendor/slim/slim/Slim/Router.php @@ -0,0 +1,455 @@ +routeParser = $parser ?: new StdParser; + } + + /** + * Set the base path used in pathFor() + * + * @param string $basePath + * + * @return self + */ + public function setBasePath($basePath) + { + if (!is_string($basePath)) { + throw new InvalidArgumentException('Router basePath must be a string'); + } + + $this->basePath = $basePath; + + return $this; + } + + /** + * Set path to fast route cache file. If this is false then route caching is disabled. + * + * @param string|false $cacheFile + * + * @return self + */ + public function setCacheFile($cacheFile) + { + if (!is_string($cacheFile) && $cacheFile !== false) { + throw new InvalidArgumentException('Router cacheFile must be a string or false'); + } + + $this->cacheFile = $cacheFile; + + if ($cacheFile !== false && !is_writable(dirname($cacheFile))) { + throw new RuntimeException('Router cacheFile directory must be writable'); + } + + + return $this; + } + + /** + * @param ContainerInterface $container + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Add route + * + * @param string[] $methods Array of HTTP methods + * @param string $pattern The route pattern + * @param callable $handler The route callable + * + * @return RouteInterface + * + * @throws InvalidArgumentException if the route pattern isn't a string + */ + public function map($methods, $pattern, $handler) + { + if (!is_string($pattern)) { + throw new InvalidArgumentException('Route pattern must be a string'); + } + + // Prepend parent group pattern(s) + if ($this->routeGroups) { + $pattern = $this->processGroups() . $pattern; + } + + // According to RFC methods are defined in uppercase (See RFC 7231) + $methods = array_map("strtoupper", $methods); + + // Add route + $route = $this->createRoute($methods, $pattern, $handler); + $this->routes[$route->getIdentifier()] = $route; + $this->routeCounter++; + + return $route; + } + + /** + * Dispatch router for HTTP request + * + * @param ServerRequestInterface $request The current HTTP request object + * + * @return array + * + * @link https://github.com/nikic/FastRoute/blob/master/src/Dispatcher.php + */ + public function dispatch(ServerRequestInterface $request) + { + $uri = '/' . ltrim($request->getUri()->getPath(), '/'); + + return $this->createDispatcher()->dispatch( + $request->getMethod(), + $uri + ); + } + + /** + * Create a new Route object + * + * @param string[] $methods Array of HTTP methods + * @param string $pattern The route pattern + * @param callable $handler The route callable + * + * @return \Slim\Interfaces\RouteInterface + */ + protected function createRoute($methods, $pattern, $callable) + { + $route = new Route($methods, $pattern, $callable, $this->routeGroups, $this->routeCounter); + if (!empty($this->container)) { + $route->setContainer($this->container); + } + + return $route; + } + + /** + * @return \FastRoute\Dispatcher + */ + protected function createDispatcher() + { + if ($this->dispatcher) { + return $this->dispatcher; + } + + $routeDefinitionCallback = function (RouteCollector $r) { + foreach ($this->getRoutes() as $route) { + $r->addRoute($route->getMethods(), $route->getPattern(), $route->getIdentifier()); + } + }; + + if ($this->cacheFile) { + $this->dispatcher = \FastRoute\cachedDispatcher($routeDefinitionCallback, [ + 'routeParser' => $this->routeParser, + 'cacheFile' => $this->cacheFile, + ]); + } else { + $this->dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback, [ + 'routeParser' => $this->routeParser, + ]); + } + + return $this->dispatcher; + } + + /** + * @param \FastRoute\Dispatcher $dispatcher + */ + public function setDispatcher(Dispatcher $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * Get route objects + * + * @return Route[] + */ + public function getRoutes() + { + return $this->routes; + } + + /** + * Get named route object + * + * @param string $name Route name + * + * @return Route + * + * @throws RuntimeException If named route does not exist + */ + public function getNamedRoute($name) + { + foreach ($this->routes as $route) { + if ($name == $route->getName()) { + return $route; + } + } + throw new RuntimeException('Named route does not exist for name: ' . $name); + } + + /** + * Remove named route + * + * @param string $name Route name + * + * @throws RuntimeException If named route does not exist + */ + public function removeNamedRoute($name) + { + $route = $this->getNamedRoute($name); + + // no exception, route exists, now remove by id + unset($this->routes[$route->getIdentifier()]); + } + + /** + * Process route groups + * + * @return string A group pattern to prefix routes with + */ + protected function processGroups() + { + $pattern = ""; + foreach ($this->routeGroups as $group) { + $pattern .= $group->getPattern(); + } + return $pattern; + } + + /** + * Add a route group to the array + * + * @param string $pattern + * @param callable $callable + * + * @return RouteGroupInterface + */ + public function pushGroup($pattern, $callable) + { + $group = new RouteGroup($pattern, $callable); + array_push($this->routeGroups, $group); + return $group; + } + + /** + * Removes the last route group from the array + * + * @return RouteGroup|bool The RouteGroup if successful, else False + */ + public function popGroup() + { + $group = array_pop($this->routeGroups); + return $group instanceof RouteGroup ? $group : false; + } + + /** + * @param $identifier + * @return \Slim\Interfaces\RouteInterface + */ + public function lookupRoute($identifier) + { + if (!isset($this->routes[$identifier])) { + throw new RuntimeException('Route not found, looks like your route cache is stale.'); + } + return $this->routes[$identifier]; + } + + /** + * Build the path for a named route excluding the base path + * + * @param string $name Route name + * @param array $data Named argument replacement data + * @param array $queryParams Optional query string parameters + * + * @return string + * + * @throws RuntimeException If named route does not exist + * @throws InvalidArgumentException If required data not provided + */ + public function relativePathFor($name, array $data = [], array $queryParams = []) + { + $route = $this->getNamedRoute($name); + $pattern = $route->getPattern(); + + $routeDatas = $this->routeParser->parse($pattern); + // $routeDatas is an array of all possible routes that can be made. There is + // one routedata for each optional parameter plus one for no optional parameters. + // + // The most specific is last, so we look for that first. + $routeDatas = array_reverse($routeDatas); + + $segments = []; + foreach ($routeDatas as $routeData) { + foreach ($routeData as $item) { + if (is_string($item)) { + // this segment is a static string + $segments[] = $item; + continue; + } + + // This segment has a parameter: first element is the name + if (!array_key_exists($item[0], $data)) { + // we don't have a data element for this segment: cancel + // testing this routeData item, so that we can try a less + // specific routeData item. + $segments = []; + $segmentName = $item[0]; + break; + } + $segments[] = $data[$item[0]]; + } + if (!empty($segments)) { + // we found all the parameters for this route data, no need to check + // less specific ones + break; + } + } + + if (empty($segments)) { + throw new InvalidArgumentException('Missing data for URL segment: ' . $segmentName); + } + $url = implode('', $segments); + + if ($queryParams) { + $url .= '?' . http_build_query($queryParams); + } + + return $url; + } + + + /** + * Build the path for a named route including the base path + * + * @param string $name Route name + * @param array $data Named argument replacement data + * @param array $queryParams Optional query string parameters + * + * @return string + * + * @throws RuntimeException If named route does not exist + * @throws InvalidArgumentException If required data not provided + */ + public function pathFor($name, array $data = [], array $queryParams = []) + { + $url = $this->relativePathFor($name, $data, $queryParams); + + if ($this->basePath) { + $url = $this->basePath . $url; + } + + return $url; + } + + /** + * Build the path for a named route. + * + * This method is deprecated. Use pathFor() from now on. + * + * @param string $name Route name + * @param array $data Named argument replacement data + * @param array $queryParams Optional query string parameters + * + * @return string + * + * @throws RuntimeException If named route does not exist + * @throws InvalidArgumentException If required data not provided + */ + public function urlFor($name, array $data = [], array $queryParams = []) + { + trigger_error('urlFor() is deprecated. Use pathFor() instead.', E_USER_DEPRECATED); + return $this->pathFor($name, $data, $queryParams); + } +} diff --git a/src/vendor/slim/slim/composer.json b/src/vendor/slim/slim/composer.json new file mode 100644 index 0000000..808eb9d --- /dev/null +++ b/src/vendor/slim/slim/composer.json @@ -0,0 +1,57 @@ +{ + "name": "slim/slim", + "type": "library", + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "keywords": ["framework","micro","api","router"], + "homepage": "http://slimframework.com", + "license": "MIT", + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + } + ], + "require": { + "php": ">=5.5.0", + "pimple/pimple": "^3.0", + "psr/http-message": "^1.0", + "nikic/fast-route": "^1.0", + "container-interop/container-interop": "^1.1" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^2.5", + "phpunit/phpunit": "^4.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "scripts": { + "test": [ + "@phpunit", + "@phpcs" + ], + "phpunit": "php vendor/bin/phpunit", + "phpcs": "php vendor/bin/phpcs" + } +} diff --git a/src/vendor/slim/twig-view/.gitignore b/src/vendor/slim/twig-view/.gitignore new file mode 100644 index 0000000..987e2a2 --- /dev/null +++ b/src/vendor/slim/twig-view/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor diff --git a/src/vendor/slim/twig-view/.travis.yml b/src/vendor/slim/twig-view/.travis.yml new file mode 100644 index 0000000..6ebb22a --- /dev/null +++ b/src/vendor/slim/twig-view/.travis.yml @@ -0,0 +1,11 @@ +language: php + +php: + - 5.5 + - 5.6 + - 7.0 + - hhvm + +before_script: composer install + +script: phpunit --coverage-text --configuration phpunit.xml.dist diff --git a/src/vendor/slim/twig-view/CONTRIBUTING.md b/src/vendor/slim/twig-view/CONTRIBUTING.md new file mode 100644 index 0000000..cfbeff0 --- /dev/null +++ b/src/vendor/slim/twig-view/CONTRIBUTING.md @@ -0,0 +1,19 @@ +# How to Contribute + +## Pull Requests + +1. Fork this repository +2. Create a new branch for each feature or improvement +3. Send a pull request from each feature branch + +It is very important to separate new features or improvements into separate feature branches, and to send a pull request for each branch. This allows me to review and pull in new features or improvements individually. + +## Style Guide + +All pull requests must adhere to the [PSR-2 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). + +## Unit Testing + +All pull requests must be accompanied by passing unit tests and complete code coverage. The Slim Framework uses phpunit for testing. + +[Learn about PHPUnit](https://github.com/sebastianbergmann/phpunit/) diff --git a/src/vendor/slim/twig-view/LICENSE.md b/src/vendor/slim/twig-view/LICENSE.md new file mode 100644 index 0000000..6df4c84 --- /dev/null +++ b/src/vendor/slim/twig-view/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Josh Lockhart + +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/src/vendor/slim/twig-view/README.md b/src/vendor/slim/twig-view/README.md new file mode 100644 index 0000000..6e971f1 --- /dev/null +++ b/src/vendor/slim/twig-view/README.md @@ -0,0 +1,83 @@ +# Slim Framework Twig View + +[![Build Status](https://travis-ci.org/slimphp/Twig-View.svg?branch=master)](https://travis-ci.org/slimphp/Twig-View) + +This is a Slim Framework view helper built on top of the Twig templating component. You can use this component to create and render templates in your Slim Framework application. + +## Install + +Via [Composer](https://getcomposer.org/) + +```bash +$ composer require slim/twig-view +``` + +Requires Slim Framework 3 and PHP 5.5.0 or newer. + +## Usage + +```php +// Create Slim app +$app = new \Slim\App(); + +// Fetch DI Container +$container = $app->getContainer(); + +// Register Twig View helper +$container['view'] = function ($c) { + $view = new \Slim\Views\Twig('path/to/templates', [ + 'cache' => 'path/to/cache' + ]); + + // Instantiate and add Slim specific extension + $basePath = rtrim(str_ireplace('index.php', '', $c['request']->getUri()->getBasePath()), '/'); + $view->addExtension(new Slim\Views\TwigExtension($c['router'], $basePath)); + + return $view; +}; + +// Define named route +$app->get('/hello/{name}', function ($request, $response, $args) { + return $this->view->render($response, 'profile.html', [ + 'name' => $args['name'] + ]); +})->setName('profile'); + +// Run app +$app->run(); +``` + +## Custom template functions + +This component exposes a custom `path_for()` function to your Twig templates. You can use this function to generate complete URLs to any Slim application named route. This is an example Twig template: + + {% extends "layout.html" %} + + {% block body %} +

User List

+ + {% endblock %} + +## Testing + +```bash +phpunit +``` + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details. + +## Security + +If you discover any security related issues, please email security@slimframework.com instead of using the issue tracker. + +## Credits + +- [Josh Lockhart](https://github.com/codeguy) + +## License + +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. diff --git a/src/vendor/slim/twig-view/composer.json b/src/vendor/slim/twig-view/composer.json new file mode 100644 index 0000000..2c71320 --- /dev/null +++ b/src/vendor/slim/twig-view/composer.json @@ -0,0 +1,28 @@ +{ + "name": "slim/twig-view", + "type": "library", + "description": "Slim Framework 3 view helper built on top of the Twig templating component", + "keywords": ["slim","framework","view","template","twig"], + "homepage": "http://slimframework.com", + "license": "MIT", + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "require": { + "php": ">=5.5.0", + "twig/twig": "^1.18", + "psr/http-message": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.0" + }, + "autoload": { + "psr-4": { + "Slim\\Views\\": "src" + } + } +} diff --git a/src/vendor/slim/twig-view/phpunit.xml.dist b/src/vendor/slim/twig-view/phpunit.xml.dist new file mode 100644 index 0000000..144ae0c --- /dev/null +++ b/src/vendor/slim/twig-view/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + + ./tests/ + + + + + + ./src + + + diff --git a/src/vendor/slim/twig-view/src/Twig.php b/src/vendor/slim/twig-view/src/Twig.php new file mode 100644 index 0000000..c4de879 --- /dev/null +++ b/src/vendor/slim/twig-view/src/Twig.php @@ -0,0 +1,231 @@ +loader = is_string($path) + ? new \Twig_Loader_Filesystem($path) + : $this->addPaths($path); + + $this->environment = new \Twig_Environment($this->loader, $settings); + } + + /******************************************************************************** + * Methods + *******************************************************************************/ + + /** + * Proxy method to add an extension to the Twig environment + * + * @param \Twig_ExtensionInterface $extension A single extension instance or an array of instances + */ + public function addExtension(\Twig_ExtensionInterface $extension) + { + $this->environment->addExtension($extension); + } + + + /** + * Fetch rendered template + * + * @param string $template Template pathname relative to templates directory + * @param array $data Associative array of template variables + * + * @return string + */ + public function fetch($template, $data = []) + { + $data = array_merge($this->defaultVariables, $data); + + return $this->environment->loadTemplate($template)->render($data); + } + + /** + * Output rendered template + * + * @param ResponseInterface $response + * @param string $template Template pathname relative to templates directory + * @param array $data Associative array of template variables + * @return ResponseInterface + */ + public function render(ResponseInterface $response, $template, $data = []) + { + $response->getBody()->write($this->fetch($template, $data)); + + return $response; + } + + /** + * Add a selection of paths with the desired namespace + * + * @param array $paths + * @return \Twig_Loader_Filesystem + */ + private function addPaths(array $paths) + { + $loader = new \Twig_Loader_Filesystem(); + + foreach ($paths as $namespace => $path) { + if (is_string($namespace)) { + $loader->setPaths($path, $namespace); + } else { + $loader->addPath($path); + } + } + + return $loader; + } + + /******************************************************************************** + * Accessors + *******************************************************************************/ + + /** + * Return Twig loader + * + * @return \Twig_LoaderInterface + */ + public function getLoader() + { + return $this->loader; + } + + /** + * Return Twig environment + * + * @return \Twig_Environment + */ + public function getEnvironment() + { + return $this->environment; + } + + /******************************************************************************** + * ArrayAccess interface + *******************************************************************************/ + + /** + * Does this collection have a given key? + * + * @param string $key The data key + * + * @return bool + */ + public function offsetExists($key) + { + return array_key_exists($key, $this->defaultVariables); + } + + /** + * Get collection item for key + * + * @param string $key The data key + * + * @return mixed The key's value, or the default value + */ + public function offsetGet($key) + { + return $this->defaultVariables[$key]; + } + + /** + * Set collection item + * + * @param string $key The data key + * @param mixed $value The data value + */ + public function offsetSet($key, $value) + { + $this->defaultVariables[$key] = $value; + } + + /** + * Remove item from collection + * + * @param string $key The data key + */ + public function offsetUnset($key) + { + unset($this->defaultVariables[$key]); + } + + /******************************************************************************** + * Countable interface + *******************************************************************************/ + + /** + * Get number of items in collection + * + * @return int + */ + public function count() + { + return count($this->defaultVariables); + } + + /******************************************************************************** + * IteratorAggregate interface + *******************************************************************************/ + + /** + * Get collection iterator + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->defaultVariables); + } +} diff --git a/src/vendor/slim/twig-view/src/TwigExtension.php b/src/vendor/slim/twig-view/src/TwigExtension.php new file mode 100644 index 0000000..7a24c04 --- /dev/null +++ b/src/vendor/slim/twig-view/src/TwigExtension.php @@ -0,0 +1,67 @@ +router = $router; + $this->uri = $uri; + } + + public function getName() + { + return 'slim'; + } + + public function getFunctions() + { + return [ + new \Twig_SimpleFunction('path_for', array($this, 'pathFor')), + new \Twig_SimpleFunction('base_url', array($this, 'baseUrl')), + ]; + } + + public function pathFor($name, $data = [], $queryParams = [], $appName = 'default') + { + return $this->router->pathFor($name, $data, $queryParams); + } + + public function baseUrl() + { + if (is_string($this->uri)) { + return $this->uri; + } + if (method_exists($this->uri, 'getBaseUrl')) { + return $this->uri->getBaseUrl(); + } + } + + /** + * Set the base url + * + * @param string|Slim\Http\Uri $baseUrl + * @return void + */ + public function setBaseUrl($baseUrl) + { + $this->uri = $baseUrl; + } +} diff --git a/src/vendor/slim/twig-view/tests/TwigTest.php b/src/vendor/slim/twig-view/tests/TwigTest.php new file mode 100644 index 0000000..79fcaf9 --- /dev/null +++ b/src/vendor/slim/twig-view/tests/TwigTest.php @@ -0,0 +1,161 @@ +fetch('example.html', [ + 'name' => 'Josh' + ]); + + $this->assertEquals("

Hi, my name is Josh.

\n", $output); + } + + public function testSingleNamespaceAndMultipleDirectories() + { + $weekday = (new \DateTimeImmutable('2016-03-08'))->format('l'); + + $view = new Twig( + [ + 'namespace' => [ + __DIR__.'/another', + __DIR__.'/templates', + __DIR__.'/multi', + ], + ] + ); + + $anotherDirectory = $view->fetch('@namespace/example.html', [ + 'name' => 'Peter' + ]); + + $templatesDirectory = $view->fetch('@namespace/another_example.html', [ + 'name' => 'Peter', + 'gender' => 'male', + ]); + + $outputMulti = $view->fetch('@namespace/directory/template/example.html', [ + 'weekday' => $weekday, + ]); + + $this->assertEquals("

Hi, my name is Peter.

\n", $anotherDirectory); + $this->assertEquals("

Hi, my name is Peter and I am male.

\n", $templatesDirectory); + $this->assertEquals('Happy Tuesday!', $outputMulti); + } + + public function testArrayWithASingleTemplateWithANamespace() + { + $views = new Twig([ + 'One' => [ + __DIR__.'/templates', + ], + ]); + + $output = $views->fetch('@One/example.html', [ + 'name' => 'Josh' + ]); + + $this->assertEquals("

Hi, my name is Josh.

\n", $output); + } + + public function testASingleTemplateWithANamespace() + { + $views = new Twig([ + 'One' => __DIR__.'/templates', + ]); + + $output = $views->fetch('@One/example.html', [ + 'name' => 'Josh' + ]); + + $this->assertEquals("

Hi, my name is Josh.

\n", $output); + } + + public function testMultipleTemplatesWithMultipleNamespace() + { + $weekday = (new \DateTimeImmutable('2016-03-08'))->format('l'); + + $views = new Twig([ + 'One' => __DIR__.'/templates', + 'Two' => __DIR__.'/another', + 'Three' => [ + __DIR__.'/multi', + ], + ]); + + $outputOne = $views->fetch('@One/example.html', [ + 'name' => 'Peter' + ]); + + $outputTwo = $views->fetch('@Two/another_example.html', [ + 'name' => 'Peter', + 'gender' => 'male' + ]); + + $outputThree = $views->fetch('@Three/directory/template/example.html', [ + 'weekday' => $weekday, + ]); + + $this->assertEquals("

Hi, my name is Peter.

\n", $outputOne); + $this->assertEquals("

Hi, my name is Peter and I am male.

\n", $outputTwo); + $this->assertEquals('Happy Tuesday!', $outputThree); + } + + public function testMultipleDirectoriesWithoutNamespaces() + { + $weekday = (new \DateTimeImmutable('2016-03-08'))->format('l'); + $view = new Twig([__DIR__.'/multi/', __DIR__.'/another/']); + + $rootDirectory = $view->fetch('directory/template/example.html', [ + 'weekday' => $weekday, + ]); + $multiDirectory = $view->fetch('another_example.html', [ + 'name' => 'Peter', + 'gender' => 'male', + ]); + + $this->assertEquals('Happy Tuesday!', $rootDirectory); + $this->assertEquals("

Hi, my name is Peter and I am male.

\n", $multiDirectory); + } + + public function testRender() + { + $view = new Twig(dirname(__FILE__) . '/templates'); + + $mockBody = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->disableOriginalConstructor() + ->getMock(); + + $mockResponse = $this->getMockBuilder('Psr\Http\Message\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $mockBody->expects($this->once()) + ->method('write') + ->with("

Hi, my name is Josh.

\n") + ->willReturn(28); + + $mockResponse->expects($this->once()) + ->method('getBody') + ->willReturn($mockBody); + + $response = $view->render($mockResponse, 'example.html', [ + 'name' => 'Josh' + ]); + $this->assertInstanceOf('Psr\Http\Message\ResponseInterface', $response); + } +} diff --git a/src/vendor/slim/twig-view/tests/another/another_example.html b/src/vendor/slim/twig-view/tests/another/another_example.html new file mode 100644 index 0000000..b61cf19 --- /dev/null +++ b/src/vendor/slim/twig-view/tests/another/another_example.html @@ -0,0 +1 @@ +

Hi, my name is {{ name }} and I am {{ gender }}.

diff --git a/src/vendor/slim/twig-view/tests/multi/directory/template/example.html b/src/vendor/slim/twig-view/tests/multi/directory/template/example.html new file mode 100644 index 0000000..1f1994d --- /dev/null +++ b/src/vendor/slim/twig-view/tests/multi/directory/template/example.html @@ -0,0 +1 @@ +Happy {{ weekday }}! \ No newline at end of file diff --git a/src/vendor/slim/twig-view/tests/templates/example.html b/src/vendor/slim/twig-view/tests/templates/example.html new file mode 100644 index 0000000..e1e7d85 --- /dev/null +++ b/src/vendor/slim/twig-view/tests/templates/example.html @@ -0,0 +1 @@ +

Hi, my name is {{ name }}.

diff --git a/src/vendor/twig/twig/.editorconfig b/src/vendor/twig/twig/.editorconfig new file mode 100644 index 0000000..270f1d1 --- /dev/null +++ b/src/vendor/twig/twig/.editorconfig @@ -0,0 +1,18 @@ +; top-most EditorConfig file +root = true + +; Unix-style newlines +[*] +end_of_line = LF + +[*.php] +indent_style = space +indent_size = 4 + +[*.test] +indent_style = space +indent_size = 4 + +[*.rst] +indent_style = space +indent_size = 4 diff --git a/src/vendor/twig/twig/.gitignore b/src/vendor/twig/twig/.gitignore new file mode 100644 index 0000000..3110362 --- /dev/null +++ b/src/vendor/twig/twig/.gitignore @@ -0,0 +1,5 @@ +/build +/composer.lock +/ext/twig/autom4te.cache/ +/phpunit.xml +/vendor diff --git a/src/vendor/twig/twig/.travis.yml b/src/vendor/twig/twig/.travis.yml new file mode 100644 index 0000000..41b7510 --- /dev/null +++ b/src/vendor/twig/twig/.travis.yml @@ -0,0 +1,44 @@ +language: php + +sudo: false + +cache: + directories: + - vendor + - $HOME/.composer/cache/files + +php: + - 5.2 + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - hhvm + +env: + - TWIG_EXT=no + - TWIG_EXT=yes + +before_install: + - if [[ ! $TRAVIS_PHP_VERSION = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi + +install: + # Composer is not available on PHP 5.2 + - if [ ${TRAVIS_PHP_VERSION:0:3} != "5.2" ]; then travis_retry composer install; fi + +before_script: + - if [ "$TWIG_EXT" == "yes" ]; then sh -c "cd ext/twig && phpize && ./configure --enable-twig && make && make install"; fi + - if [ "$TWIG_EXT" == "yes" ]; then echo "extension=twig.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi + - if [ ${TRAVIS_PHP_VERSION:0:3} == "5.2" ]; then sed -i.bak "s|vendor/autoload.php|test/bootstrap.php|" phpunit.xml.dist; fi + +matrix: + fast_finish: true + exclude: + - php: hhvm + env: TWIG_EXT=yes + - php: 7.0 + env: TWIG_EXT=yes + - php: 7.1 + env: TWIG_EXT=yes diff --git a/src/vendor/twig/twig/CHANGELOG b/src/vendor/twig/twig/CHANGELOG new file mode 100644 index 0000000..65b513f --- /dev/null +++ b/src/vendor/twig/twig/CHANGELOG @@ -0,0 +1,913 @@ +* 1.29.0 (2016-12-13) + + * fixed sandbox being left enabled if an exception is thrown while rendering + * marked some classes as being final (via @final) + * made Twig_Error report real source path when possible + * added support for {{ _self }} to provide an upgrade path from 1.x to 2.0 (replaces {{ _self.templateName }}) + * deprecated silent display of undefined blocks + * deprecated support for mbstring.func_overload != 0 + +* 1.28.2 (2016-11-23) + + * fixed precedence between getFoo() and isFoo() in Twig_Template::getAttribute() + * improved a deprecation message + +* 1.28.1 (2016-11-18) + + * fixed block() function when used with a template argument + +* 1.28.0 (2016-11-17) + + * added support for the PHP 7 null coalescing operator for the ?? Twig implementation + * exposed a way to access template data and methods in a portable way + * changed context access to use the PHP 7 null coalescing operator when available + * added the "with" tag + * added support for a custom template on the block() function + * added "is defined" support for block() and constant() + * optimized the way attributes are fetched + +* 1.27.0 (2016-10-25) + + * deprecated Twig_Parser::getEnvironment() + * deprecated Twig_Parser::addHandler() and Twig_Parser::addNodeVisitor() + * deprecated Twig_Compiler::addIndentation() + * fixed regression when registering two extensions having the same class name + * deprecated Twig_LoaderInterface::getSource() (implement Twig_SourceContextLoaderInterface instead) + * fixed the filesystem loader with relative paths + * deprecated Twig_Node::getLine() in favor of Twig_Node::getTemplateLine() + * deprecated Twig_Template::getSource() in favor of Twig_Template::getSourceContext() + * deprecated Twig_Node::getFilename() in favor of Twig_Node::getTemplateName() + * deprecated the "filename" escaping strategy (use "name" instead) + * added Twig_Source to hold information about the original template + * deprecated Twig_Error::getTemplateFile() and Twig_Error::setTemplateFile() in favor of Twig_Error::getTemplateName() and Twig_Error::setTemplateName() + * deprecated Parser::getFilename() + * fixed template paths when a template name contains a protocol like vfs:// + * improved debugging with Twig_Sandbox_SecurityError exceptions for disallowed methods and properties + +* 1.26.1 (2016-10-05) + + * removed template source code from generated template classes when debug is disabled + * fixed default implementation of Twig_Template::getDebugInfo() for better BC + * fixed regression on static calls for functions/filters/tests + +* 1.26.0 (2016-10-02) + + * added template cache invalidation based on more environment options + * added a missing deprecation notice + * fixed template paths when a template is stored in a PHAR file + * allowed filters/functions/tests implementation to use a different class than the extension they belong to + * deprecated Twig_ExtensionInterface::getName() + +* 1.25.0 (2016-09-21) + + * changed the way we store template source in template classes + * removed usage of realpath in cache keys + * fixed Twig cache sharing when used with different versions of PHP + * removed embed parent workaround for simple use cases + * deprecated the ability to store non Node instances in Node::$nodes + * deprecated Twig_Environment::getLexer(), Twig_Environment::getParser(), Twig_Environment::getCompiler() + * deprecated Twig_Compiler::getFilename() + +* 1.24.2 (2016-09-01) + + * fixed static callables + * fixed a potential PHP warning when loading the cache + * fixed a case where the autoescaping does not work as expected + +* 1.24.1 (2016-05-30) + + * fixed reserved keywords (forbids true, false, null and none keywords for variables names) + * fixed support for PHP7 (Throwable support) + * marked the following methods as being internals on Twig_Environment: + getFunctions(), getFilters(), getTests(), getFunction(), getFilter(), getTest(), + getTokenParsers(), getTags(), getNodeVisitors(), getUnaryOperators(), getBinaryOperators(), + getFunctions(), getFilters(), getGlobals(), initGlobals(), initExtensions(), and initExtension() + +* 1.24.0 (2016-01-25) + + * adding support for the ?? operator + * fixed the defined test when used on a constant, a map, or a sequence + * undeprecated _self (should only be used to get the template name, not the template instance) + * fixed parsing on PHP7 + +* 1.23.3 (2016-01-11) + + * fixed typo + +* 1.23.2 (2015-01-11) + + * added versions in deprecated messages + * made file cache tolerant for trailing (back)slashes on directory configuration + * deprecated unused Twig_Node_Expression_ExtensionReference class + +* 1.23.1 (2015-11-05) + + * fixed some exception messages which triggered PHP warnings + * fixed BC on Twig_Test_NodeTestCase + +* 1.23.0 (2015-10-29) + + * deprecated the possibility to override an extension by registering another one with the same name + * deprecated Twig_ExtensionInterface::getGlobals() (added Twig_Extension_GlobalsInterface for BC) + * deprecated Twig_ExtensionInterface::initRuntime() (added Twig_Extension_InitRuntimeInterface for BC) + * deprecated Twig_Environment::computeAlternatives() + +* 1.22.3 (2015-10-13) + + * fixed regression when using null as a cache strategy + * improved performance when checking template freshness + * fixed warnings when loaded templates do not exist + * fixed template class name generation to prevent possible collisions + * fixed logic for custom escapers to call them even on integers and null values + * changed template cache names to take into account the Twig C extension + +* 1.22.2 (2015-09-22) + + * fixed a race condition in template loading + +* 1.22.1 (2015-09-15) + + * fixed regression in template_from_string + +* 1.22.0 (2015-09-13) + + * made Twig_Test_IntegrationTestCase more flexible + * added an option to force PHP bytecode invalidation when writing a compiled template into the cache + * fixed the profiler duration for the root node + * changed template cache names to take into account enabled extensions + * deprecated Twig_Environment::clearCacheFiles(), Twig_Environment::getCacheFilename(), + Twig_Environment::writeCacheFile(), and Twig_Environment::getTemplateClassPrefix() + * added a way to override the filesystem template cache system + * added a way to get the original template source from Twig_Template + +* 1.21.2 (2015-09-09) + + * fixed variable names for the deprecation triggering code + * fixed escaping strategy detection based on filename + * added Traversable support for replace, merge, and sort + * deprecated support for character by character replacement for the "replace" filter + +* 1.21.1 (2015-08-26) + + * fixed regression when using the deprecated Twig_Test_* classes + +* 1.21.0 (2015-08-24) + + * added deprecation notices for deprecated features + * added a deprecation "framework" for filters/functions/tests and test fixtures + +* 1.20.0 (2015-08-12) + + * forbid access to the Twig environment from templates and internal parts of Twig_Template + * fixed limited RCEs when in sandbox mode + * deprecated Twig_Template::getEnvironment() + * deprecated the _self variable for usage outside of the from and import tags + * added Twig_BaseNodeVisitor to ease the compatibility of node visitors + between 1.x and 2.x + +* 1.19.0 (2015-07-31) + + * fixed wrong error message when including an undefined template in a child template + * added support for variadic filters, functions, and tests + * added support for extra positional arguments in macros + * added ignore_missing flag to the source function + * fixed batch filter with zero items + * deprecated Twig_Environment::clearTemplateCache() + * fixed sandbox disabling when using the include function + +* 1.18.2 (2015-06-06) + + * fixed template/line guessing in exceptions for nested templates + * optimized the number of inodes and the size of realpath cache when using the cache + +* 1.18.1 (2015-04-19) + + * fixed memory leaks in the C extension + * deprecated Twig_Loader_String + * fixed the slice filter when used with a SimpleXMLElement object + * fixed filesystem loader when trying to load non-files (like directories) + +* 1.18.0 (2015-01-25) + + * fixed some error messages where the line was wrong (unknown variables or argument names) + * added a new way to customize the main Module node (via empty nodes) + * added Twig_Environment::createTemplate() to create a template from a string + * added a profiler + * fixed filesystem loader cache when different file paths are used for the same template + +* 1.17.0 (2015-01-14) + + * added a 'filename' autoescaping strategy, which dynamically chooses the + autoescaping strategy for a template based on template file extension. + +* 1.16.3 (2014-12-25) + + * fixed regression for dynamic parent templates + * fixed cache management with statcache + * fixed a regression in the slice filter + +* 1.16.2 (2014-10-17) + + * fixed timezone on dates as strings + * fixed 2-words test names when a custom node class is not used + * fixed macros when using an argument named like a PHP super global (like GET or POST) + * fixed date_modify when working with DateTimeImmutable + * optimized for loops + * fixed multi-byte characters handling in the split filter + * fixed a regression in the in operator + * fixed a regression in the slice filter + +* 1.16.1 (2014-10-10) + + * improved error reporting in a sandboxed template + * fixed missing error file/line information under certain circumstances + * fixed wrong error line number in some error messages + * fixed the in operator to use strict comparisons + * sped up the slice filter + * fixed for mb function overload mb_substr acting different + * fixed the attribute() function when passing a variable for the arguments + +* 1.16.0 (2014-07-05) + + * changed url_encode to always encode according to RFC 3986 + * fixed inheritance in a 'use'-hierarchy + * removed the __toString policy check when the sandbox is disabled + * fixed recursively calling blocks in templates with inheritance + +* 1.15.1 (2014-02-13) + + * fixed the conversion of the special '0000-00-00 00:00' date + * added an error message when trying to import an undefined block from a trait + * fixed a C extension crash when accessing defined but uninitialized property. + +* 1.15.0 (2013-12-06) + + * made ignoreStrictCheck in Template::getAttribute() works with __call() methods throwing BadMethodCallException + * added min and max functions + * added the round filter + * fixed a bug that prevented the optimizers to be enabled/disabled selectively + * fixed first and last filters for UTF-8 strings + * added a source function to include the content of a template without rendering it + * fixed the C extension sandbox behavior when get or set is prepend to method name + +* 1.14.2 (2013-10-30) + + * fixed error filename/line when an error occurs in an included file + * allowed operators that contain whitespaces to have more than one whitespace + * allowed tests to be made of 1 or 2 words (like "same as" or "divisible by") + +* 1.14.1 (2013-10-15) + + * made it possible to use named operators as variables + * fixed the possibility to have a variable named 'matches' + * added support for PHP 5.5 DateTimeInterface + +* 1.14.0 (2013-10-03) + + * fixed usage of the html_attr escaping strategy to avoid double-escaping with the html strategy + * added new operators: ends with, starts with, and matches + * fixed some compatibility issues with HHVM + * added a way to add custom escaping strategies + * fixed the C extension compilation on Windows + * fixed the batch filter when using a fill argument with an exact match of elements to batch + * fixed the filesystem loader cache when a template name exists in several namespaces + * fixed template_from_string when the template includes or extends other ones + * fixed a crash of the C extension on an edge case + +* 1.13.2 (2013-08-03) + + * fixed the error line number for an error occurs in and embedded template + * fixed crashes of the C extension on some edge cases + +* 1.13.1 (2013-06-06) + + * added the possibility to ignore the filesystem constructor argument in Twig_Loader_Filesystem + * fixed Twig_Loader_Chain::exists() for a loader which implements Twig_ExistsLoaderInterface + * adjusted backtrace call to reduce memory usage when an error occurs + * added support for object instances as the second argument of the constant test + * fixed the include function when used in an assignment + +* 1.13.0 (2013-05-10) + + * fixed getting a numeric-like item on a variable ('09' for instance) + * fixed getting a boolean or float key on an array, so it is consistent with PHP's array access: + `{{ array[false] }}` behaves the same as `echo $array[false];` (equals `$array[0]`) + * made the escape filter 20% faster for happy path (escaping string for html with UTF-8) + * changed ☃ to § in tests + * enforced usage of named arguments after positional ones + +* 1.12.3 (2013-04-08) + + * fixed a security issue in the filesystem loader where it was possible to include a template one + level above the configured path + * fixed fatal error that should be an exception when adding a filter/function/test too late + * added a batch filter + * added support for encoding an array as query string in the url_encode filter + +* 1.12.2 (2013-02-09) + + * fixed the timezone used by the date filter and function when the given date contains a timezone (like 2010-01-28T15:00:00+02:00) + * fixed globals when getGlobals is called early on + * added the first and last filter + +* 1.12.1 (2013-01-15) + + * added support for object instances as the second argument of the constant function + * relaxed globals management to avoid a BC break + * added support for {{ some_string[:2] }} + +* 1.12.0 (2013-01-08) + + * added verbatim as an alias for the raw tag to avoid confusion with the raw filter + * fixed registration of tests and functions as anonymous functions + * fixed globals management + +* 1.12.0-RC1 (2012-12-29) + + * added an include function (does the same as the include tag but in a more flexible way) + * added the ability to use any PHP callable to define filters, functions, and tests + * added a syntax error when using a loop variable that is not defined + * added the ability to set default values for macro arguments + * added support for named arguments for filters, tests, and functions + * moved filters/functions/tests syntax errors to the parser + * added support for extended ternary operator syntaxes + +* 1.11.1 (2012-11-11) + + * fixed debug info line numbering (was off by 2) + * fixed escaping when calling a macro inside another one (regression introduced in 1.9.1) + * optimized variable access on PHP 5.4 + * fixed a crash of the C extension when an exception was thrown from a macro called without being imported (using _self.XXX) + +* 1.11.0 (2012-11-07) + + * fixed macro compilation when a variable name is a PHP reserved keyword + * changed the date filter behavior to always apply the default timezone, except if false is passed as the timezone + * fixed bitwise operator precedences + * added the template_from_string function + * fixed default timezone usage for the date function + * optimized the way Twig exceptions are managed (to make them faster) + * added Twig_ExistsLoaderInterface (implementing this interface in your loader make the chain loader much faster) + +* 1.10.3 (2012-10-19) + + * fixed wrong template location in some error messages + * reverted a BC break introduced in 1.10.2 + * added a split filter + +* 1.10.2 (2012-10-15) + + * fixed macro calls on PHP 5.4 + +* 1.10.1 (2012-10-15) + + * made a speed optimization to macro calls when imported via the "import" tag + * fixed C extension compilation on Windows + * fixed a segfault in the C extension when using DateTime objects + +* 1.10.0 (2012-09-28) + + * extracted functional tests framework to make it reusable for third-party extensions + * added namespaced templates support in Twig_Loader_Filesystem + * added Twig_Loader_Filesystem::prependPath() + * fixed an error when a token parser pass a closure as a test to the subparse() method + +* 1.9.2 (2012-08-25) + + * fixed the in operator for objects that contain circular references + * fixed the C extension when accessing a public property of an object implementing the \ArrayAccess interface + +* 1.9.1 (2012-07-22) + + * optimized macro calls when auto-escaping is on + * fixed wrong parent class for Twig_Function_Node + * made Twig_Loader_Chain more explicit about problems + +* 1.9.0 (2012-07-13) + + * made the parsing independent of the template loaders + * fixed exception trace when an error occurs when rendering a child template + * added escaping strategies for CSS, URL, and HTML attributes + * fixed nested embed tag calls + * added the date_modify filter + +* 1.8.3 (2012-06-17) + + * fixed paths in the filesystem loader when passing a path that ends with a slash or a backslash + * fixed escaping when a project defines a function named html or js + * fixed chmod mode to apply the umask correctly + +* 1.8.2 (2012-05-30) + + * added the abs filter + * fixed a regression when using a number in template attributes + * fixed compiler when mbstring.func_overload is set to 2 + * fixed DateTimeZone support in date filter + +* 1.8.1 (2012-05-17) + + * fixed a regression when dealing with SimpleXMLElement instances in templates + * fixed "is_safe" value for the "dump" function when "html_errors" is not defined in php.ini + * switched to use mbstring whenever possible instead of iconv (you might need to update your encoding as mbstring and iconv encoding names sometimes differ) + +* 1.8.0 (2012-05-08) + + * enforced interface when adding tests, filters, functions, and node visitors from extensions + * fixed a side-effect of the date filter where the timezone might be changed + * simplified usage of the autoescape tag; the only (optional) argument is now the escaping strategy or false (with a BC layer) + * added a way to dynamically change the auto-escaping strategy according to the template "filename" + * changed the autoescape option to also accept a supported escaping strategy (for BC, true is equivalent to html) + * added an embed tag + +* 1.7.0 (2012-04-24) + + * fixed a PHP warning when using CIFS + * fixed template line number in some exceptions + * added an iterable test + * added an error when defining two blocks with the same name in a template + * added the preserves_safety option for filters + * fixed a PHP notice when trying to access a key on a non-object/array variable + * enhanced error reporting when the template file is an instance of SplFileInfo + * added Twig_Environment::mergeGlobals() + * added compilation checks to avoid misuses of the sandbox tag + * fixed filesystem loader freshness logic for high traffic websites + * fixed random function when charset is null + +* 1.6.5 (2012-04-11) + + * fixed a regression when a template only extends another one without defining any blocks + +* 1.6.4 (2012-04-02) + + * fixed PHP notice in Twig_Error::guessTemplateLine() introduced in 1.6.3 + * fixed performance when compiling large files + * optimized parent template creation when the template does not use dynamic inheritance + +* 1.6.3 (2012-03-22) + + * fixed usage of Z_ADDREF_P for PHP 5.2 in the C extension + * fixed compilation of numeric values used in templates when using a locale where the decimal separator is not a dot + * made the strategy used to guess the real template file name and line number in exception messages much faster and more accurate + +* 1.6.2 (2012-03-18) + + * fixed sandbox mode when used with inheritance + * added preserveKeys support for the slice filter + * fixed the date filter when a DateTime instance is passed with a specific timezone + * added a trim filter + +* 1.6.1 (2012-02-29) + + * fixed Twig C extension + * removed the creation of Twig_Markup instances when not needed + * added a way to set the default global timezone for dates + * fixed the slice filter on strings when the length is not specified + * fixed the creation of the cache directory in case of a race condition + +* 1.6.0 (2012-02-04) + + * fixed raw blocks when used with the whitespace trim option + * made a speed optimization to macro calls when imported via the "from" tag + * fixed globals, parsers, visitors, filters, tests, and functions management in Twig_Environment when a new one or new extension is added + * fixed the attribute function when passing arguments + * added slice notation support for the [] operator (syntactic sugar for the slice operator) + * added a slice filter + * added string support for the reverse filter + * fixed the empty test and the length filter for Twig_Markup instances + * added a date function to ease date comparison + * fixed unary operators precedence + * added recursive parsing support in the parser + * added string and integer handling for the random function + +* 1.5.1 (2012-01-05) + + * fixed a regression when parsing strings + +* 1.5.0 (2012-01-04) + + * added Traversable objects support for the join filter + +* 1.5.0-RC2 (2011-12-30) + + * added a way to set the default global date interval format + * fixed the date filter for DateInterval instances (setTimezone() does not exist for them) + * refactored Twig_Template::display() to ease its extension + * added a number_format filter + +* 1.5.0-RC1 (2011-12-26) + + * removed the need to quote hash keys + * allowed hash keys to be any expression + * added a do tag + * added a flush tag + * added support for dynamically named filters and functions + * added a dump function to help debugging templates + * added a nl2br filter + * added a random function + * added a way to change the default format for the date filter + * fixed the lexer when an operator ending with a letter ends a line + * added string interpolation support + * enhanced exceptions for unknown filters, functions, tests, and tags + +* 1.4.0 (2011-12-07) + + * fixed lexer when using big numbers (> PHP_INT_MAX) + * added missing preserveKeys argument to the reverse filter + * fixed macros containing filter tag calls + +* 1.4.0-RC2 (2011-11-27) + + * removed usage of Reflection in Twig_Template::getAttribute() + * added a C extension that can optionally replace Twig_Template::getAttribute() + * added negative timestamp support to the date filter + +* 1.4.0-RC1 (2011-11-20) + + * optimized variable access when using PHP 5.4 + * changed the precedence of the .. operator to be more consistent with languages that implements such a feature like Ruby + * added an Exception to Twig_Loader_Array::isFresh() method when the template does not exist to be consistent with other loaders + * added Twig_Function_Node to allow more complex functions to have their own Node class + * added Twig_Filter_Node to allow more complex filters to have their own Node class + * added Twig_Test_Node to allow more complex tests to have their own Node class + * added a better error message when a template is empty but contain a BOM + * fixed "in" operator for empty strings + * fixed the "defined" test and the "default" filter (now works with more than one call (foo.bar.foo) and for both values of the strict_variables option) + * changed the way extensions are loaded (addFilter/addFunction/addGlobal/addTest/addNodeVisitor/addTokenParser/addExtension can now be called in any order) + * added Twig_Environment::display() + * made the escape filter smarter when the encoding is not supported by PHP + * added a convert_encoding filter + * moved all node manipulations outside the compile() Node method + * made several speed optimizations + +* 1.3.0 (2011-10-08) + +no changes + +* 1.3.0-RC1 (2011-10-04) + + * added an optimization for the parent() function + * added cache reloading when auto_reload is true and an extension has been modified + * added the possibility to force the escaping of a string already marked as safe (instance of Twig_Markup) + * allowed empty templates to be used as traits + * added traits support for the "parent" function + +* 1.2.0 (2011-09-13) + +no changes + +* 1.2.0-RC1 (2011-09-10) + + * enhanced the exception when a tag remains unclosed + * added support for empty Countable objects for the "empty" test + * fixed algorithm that determines if a template using inheritance is valid (no output between block definitions) + * added better support for encoding problems when escaping a string (available as of PHP 5.4) + * added a way to ignore a missing template when using the "include" tag ({% include "foo" ignore missing %}) + * added support for an array of templates to the "include" and "extends" tags ({% include ['foo', 'bar'] %}) + * added support for bitwise operators in expressions + * added the "attribute" function to allow getting dynamic attributes on variables + * added Twig_Loader_Chain + * added Twig_Loader_Array::setTemplate() + * added an optimization for the set tag when used to capture a large chunk of static text + * changed name regex to match PHP one "[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*" (works for blocks, tags, functions, filters, and macros) + * removed the possibility to use the "extends" tag from a block + * added "if" modifier support to "for" loops + +* 1.1.2 (2011-07-30) + + * fixed json_encode filter on PHP 5.2 + * fixed regression introduced in 1.1.1 ({{ block(foo|lower) }}) + * fixed inheritance when using conditional parents + * fixed compilation of templates when the body of a child template is not empty + * fixed output when a macro throws an exception + * fixed a parsing problem when a large chunk of text is enclosed in a comment tag + * added PHPDoc for all Token parsers and Core extension functions + +* 1.1.1 (2011-07-17) + + * added a performance optimization in the Optimizer (also helps to lower the number of nested level calls) + * made some performance improvement for some edge cases + +* 1.1.0 (2011-06-28) + + * fixed json_encode filter + +* 1.1.0-RC3 (2011-06-24) + + * fixed method case-sensitivity when using the sandbox mode + * added timezone support for the date filter + * fixed possible security problems with NUL bytes + +* 1.1.0-RC2 (2011-06-16) + + * added an exception when the template passed to "use" is not a string + * made 'a.b is defined' not throw an exception if a is not defined (in strict mode) + * added {% line \d+ %} directive + +* 1.1.0-RC1 (2011-05-28) + +Flush your cache after upgrading. + + * fixed date filter when using a timestamp + * fixed the defined test for some cases + * fixed a parsing problem when a large chunk of text is enclosed in a raw tag + * added support for horizontal reuse of template blocks (see docs for more information) + * added whitespace control modifier to all tags (see docs for more information) + * added null as an alias for none (the null test is also an alias for the none test now) + * made TRUE, FALSE, NONE equivalent to their lowercase counterparts + * wrapped all compilation and runtime exceptions with Twig_Error_Runtime and added logic to guess the template name and line + * moved display() method to Twig_Template (generated templates should now use doDisplay() instead) + +* 1.0.0 (2011-03-27) + + * fixed output when using mbstring + * fixed duplicate call of methods when using the sandbox + * made the charset configurable for the escape filter + +* 1.0.0-RC2 (2011-02-21) + + * changed the way {% set %} works when capturing (the content is now marked as safe) + * added support for macro name in the endmacro tag + * make Twig_Error compatible with PHP 5.3.0 > + * fixed an infinite loop on some Windows configurations + * fixed the "length" filter for numbers + * fixed Template::getAttribute() as properties in PHP are case sensitive + * removed coupling between Twig_Node and Twig_Template + * fixed the ternary operator precedence rule + +* 1.0.0-RC1 (2011-01-09) + +Backward incompatibilities: + + * the "items" filter, which has been deprecated for quite a long time now, has been removed + * the "range" filter has been converted to a function: 0|range(10) -> range(0, 10) + * the "constant" filter has been converted to a function: {{ some_date|date('DATE_W3C'|constant) }} -> {{ some_date|date(constant('DATE_W3C')) }} + * the "cycle" filter has been converted to a function: {{ ['odd', 'even']|cycle(i) }} -> {{ cycle(['odd', 'even'], i) }} + * the "for" tag does not support "joined by" anymore + * the "autoescape" first argument is now "true"/"false" (instead of "on"/"off") + * the "parent" tag has been replaced by a "parent" function ({{ parent() }} instead of {% parent %}) + * the "display" tag has been replaced by a "block" function ({{ block('title') }} instead of {% display title %}) + * removed the grammar and simple token parser (moved to the Twig Extensions repository) + +Changes: + + * added "needs_context" option for filters and functions (the context is then passed as a first argument) + * added global variables support + * made macros return their value instead of echoing directly (fixes calling a macro in sandbox mode) + * added the "from" tag to import macros as functions + * added support for functions (a function is just syntactic sugar for a getAttribute() call) + * made macros callable when sandbox mode is enabled + * added an exception when a macro uses a reserved name + * the "default" filter now uses the "empty" test instead of just checking for null + * added the "empty" test + +* 0.9.10 (2010-12-16) + +Backward incompatibilities: + + * The Escaper extension is enabled by default, which means that all displayed + variables are now automatically escaped. You can revert to the previous + behavior by removing the extension via $env->removeExtension('escaper') + or just set the 'autoescape' option to 'false'. + * removed the "without loop" attribute for the "for" tag (not needed anymore + as the Optimizer take care of that for most cases) + * arrays and hashes have now a different syntax + * arrays keep the same syntax with square brackets: [1, 2] + * hashes now use curly braces (["a": "b"] should now be written as {"a": "b"}) + * support for "arrays with keys" and "hashes without keys" is not supported anymore ([1, "foo": "bar"] or {"foo": "bar", 1}) + * the i18n extension is now part of the Twig Extensions repository + +Changes: + + * added the merge filter + * removed 'is_escaper' option for filters (a left over from the previous version) -- you must use 'is_safe' now instead + * fixed usage of operators as method names (like is, in, and not) + * changed the order of execution for node visitors + * fixed default() filter behavior when used with strict_variables set to on + * fixed filesystem loader compatibility with PHAR files + * enhanced error messages when an unexpected token is parsed in an expression + * fixed filename not being added to syntax error messages + * added the autoescape option to enable/disable autoescaping + * removed the newline after a comment (mimics PHP behavior) + * added a syntax error exception when parent block is used on a template that does not extend another one + * made the Escaper extension enabled by default + * fixed sandbox extension when used with auto output escaping + * fixed escaper when wrapping a Twig_Node_Print (the original class must be preserved) + * added an Optimizer extension (enabled by default; optimizes "for" loops and "raw" filters) + * added priority to node visitors + +* 0.9.9 (2010-11-28) + +Backward incompatibilities: + * the self special variable has been renamed to _self + * the odd and even filters are now tests: + {{ foo|odd }} must now be written {{ foo is odd }} + * the "safe" filter has been renamed to "raw" + * in Node classes, + sub-nodes are now accessed via getNode() (instead of property access) + attributes via getAttribute() (instead of array access) + * the urlencode filter had been renamed to url_encode + * the include tag now merges the passed variables with the current context by default + (the old behavior is still possible by adding the "only" keyword) + * moved Exceptions to Twig_Error_* (Twig_SyntaxError/Twig_RuntimeError are now Twig_Error_Syntax/Twig_Error_Runtime) + * removed support for {{ 1 < i < 3 }} (use {{ i > 1 and i < 3 }} instead) + * the "in" filter has been removed ({{ a|in(b) }} should now be written {{ a in b }}) + +Changes: + * added file and line to Twig_Error_Runtime exceptions thrown from Twig_Template + * changed trans tag to accept any variable for the plural count + * fixed sandbox mode (__toString() method check was not enforced if called implicitly from complex statements) + * added the ** (power) operator + * changed the algorithm used for parsing expressions + * added the spaceless tag + * removed trim_blocks option + * added support for is*() methods for attributes (foo.bar now looks for foo->getBar() or foo->isBar()) + * changed all exceptions to extend Twig_Error + * fixed unary expressions ({{ not(1 or 0) }}) + * fixed child templates (with an extend tag) that uses one or more imports + * added support for {{ 1 not in [2, 3] }} (more readable than the current {{ not (1 in [2, 3]) }}) + * escaping has been rewritten + * the implementation of template inheritance has been rewritten + (blocks can now be called individually and still work with inheritance) + * fixed error handling for if tag when a syntax error occurs within a subparse process + * added a way to implement custom logic for resolving token parsers given a tag name + * fixed js escaper to be stricter (now uses a whilelist-based js escaper) + * added the following filers: "constant", "trans", "replace", "json_encode" + * added a "constant" test + * fixed objects with __toString() not being autoescaped + * fixed subscript expressions when calling __call() (methods now keep the case) + * added "test" feature (accessible via the "is" operator) + * removed the debug tag (should be done in an extension) + * fixed trans tag when no vars are used in plural form + * fixed race condition when writing template cache + * added the special _charset variable to reference the current charset + * added the special _context variable to reference the current context + * renamed self to _self (to avoid conflict) + * fixed Twig_Template::getAttribute() for protected properties + +* 0.9.8 (2010-06-28) + +Backward incompatibilities: + * the trans tag plural count is now attached to the plural tag: + old: `{% trans count %}...{% plural %}...{% endtrans %}` + new: `{% trans %}...{% plural count %}...{% endtrans %}` + + * added a way to translate strings coming from a variable ({% trans var %}) + * fixed trans tag when used with the Escaper extension + * fixed default cache umask + * removed Twig_Template instances from the debug tag output + * fixed objects with __isset() defined + * fixed set tag when used with a capture + * fixed type hinting for Twig_Environment::addFilter() method + +* 0.9.7 (2010-06-12) + +Backward incompatibilities: + * changed 'as' to '=' for the set tag ({% set title as "Title" %} must now be {% set title = "Title" %}) + * removed the sandboxed attribute of the include tag (use the new sandbox tag instead) + * refactored the Node system (if you have custom nodes, you will have to update them to use the new API) + + * added self as a special variable that refers to the current template (useful for importing macros from the current template) + * added Twig_Template instance support to the include tag + * added support for dynamic and conditional inheritance ({% extends some_var %} and {% extends standalone ? "minimum" : "base" %}) + * added a grammar sub-framework to ease the creation of custom tags + * fixed the for tag for large arrays (some loop variables are now only available for arrays and objects that implement the Countable interface) + * removed the Twig_Resource::resolveMissingFilter() method + * fixed the filter tag which did not apply filtering to included files + * added a bunch of unit tests + * added a bunch of phpdoc + * added a sandbox tag in the sandbox extension + * changed the date filter to support any date format supported by DateTime + * added strict_variable setting to throw an exception when an invalid variable is used in a template (disabled by default) + * added the lexer, parser, and compiler as arguments to the Twig_Environment constructor + * changed the cache option to only accepts an explicit path to a cache directory or false + * added a way to add token parsers, filters, and visitors without creating an extension + * added three interfaces: Twig_NodeInterface, Twig_TokenParserInterface, and Twig_FilterInterface + * changed the generated code to match the new coding standards + * fixed sandbox mode (__toString() method check was not enforced if called implicitly from a simple statement like {{ article }}) + * added an exception when a child template has a non-empty body (as it is always ignored when rendering) + +* 0.9.6 (2010-05-12) + + * fixed variables defined outside a loop and for which the value changes in a for loop + * fixed the test suite for PHP 5.2 and older versions of PHPUnit + * added support for __call() in expression resolution + * fixed node visiting for macros (macros are now visited by visitors as any other node) + * fixed nested block definitions with a parent call (rarely useful but nonetheless supported now) + * added the cycle filter + * fixed the Lexer when mbstring.func_overload is used with an mbstring.internal_encoding different from ASCII + * added a long-syntax for the set tag ({% set foo %}...{% endset %}) + * unit tests are now powered by PHPUnit + * added support for gettext via the `i18n` extension + * fixed twig_capitalize_string_filter() and fixed twig_length_filter() when used with UTF-8 values + * added a more useful exception if an if tag is not closed properly + * added support for escaping strategy in the autoescape tag + * fixed lexer when a template has a big chunk of text between/in a block + +* 0.9.5 (2010-01-20) + +As for any new release, don't forget to remove all cached templates after +upgrading. + +If you have defined custom filters, you MUST upgrade them for this release. To +upgrade, replace "array" with "new Twig_Filter_Function", and replace the +environment constant by the "needs_environment" option: + + // before + 'even' => array('twig_is_even_filter', false), + 'escape' => array('twig_escape_filter', true), + + // after + 'even' => new Twig_Filter_Function('twig_is_even_filter'), + 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)), + +If you have created NodeTransformer classes, you will need to upgrade them to +the new interface (please note that the interface is not yet considered +stable). + + * fixed list nodes that did not extend the Twig_NodeListInterface + * added the "without loop" option to the for tag (it disables the generation of the loop variable) + * refactored node transformers to node visitors + * fixed automatic-escaping for blocks + * added a way to specify variables to pass to an included template + * changed the automatic-escaping rules to be more sensible and more configurable in custom filters (the documentation lists all the rules) + * improved the filter system to allow object methods to be used as filters + * changed the Array and String loaders to actually make use of the cache mechanism + * included the default filter function definitions in the extension class files directly (Core, Escaper) + * added the // operator (like the floor() PHP function) + * added the .. operator (as a syntactic sugar for the range filter when the step is 1) + * added the in operator (as a syntactic sugar for the in filter) + * added the following filters in the Core extension: in, range + * added support for arrays (same behavior as in PHP, a mix between lists and dictionaries, arrays and hashes) + * enhanced some error messages to provide better feedback in case of parsing errors + +* 0.9.4 (2009-12-02) + +If you have custom loaders, you MUST upgrade them for this release: The +Twig_Loader base class has been removed, and the Twig_LoaderInterface has also +been changed (see the source code for more information or the documentation). + + * added support for DateTime instances for the date filter + * fixed loop.last when the array only has one item + * made it possible to insert newlines in tag and variable blocks + * fixed a bug when a literal '\n' were present in a template text + * fixed bug when the filename of a template contains */ + * refactored loaders + +* 0.9.3 (2009-11-11) + +This release is NOT backward compatible with the previous releases. + + The loaders do not take the cache and autoReload arguments anymore. Instead, + the Twig_Environment class has two new options: cache and auto_reload. + Upgrading your code means changing this kind of code: + + $loader = new Twig_Loader_Filesystem('/path/to/templates', '/path/to/compilation_cache', true); + $twig = new Twig_Environment($loader); + + to something like this: + + $loader = new Twig_Loader_Filesystem('/path/to/templates'); + $twig = new Twig_Environment($loader, array( + 'cache' => '/path/to/compilation_cache', + 'auto_reload' => true, + )); + + * deprecated the "items" filter as it is not needed anymore + * made cache and auto_reload options of Twig_Environment instead of arguments of Twig_Loader + * optimized template loading speed + * removed output when an error occurs in a template and render() is used + * made major speed improvements for loops (up to 300% on even the smallest loops) + * added properties as part of the sandbox mode + * added public properties support (obj.item can now be the item property on the obj object) + * extended set tag to support expression as value ({% set foo as 'foo' ~ 'bar' %} ) + * fixed bug when \ was used in HTML + +* 0.9.2 (2009-10-29) + + * made some speed optimizations + * changed the cache extension to .php + * added a js escaping strategy + * added support for short block tag + * changed the filter tag to allow chained filters + * made lexer more flexible as you can now change the default delimiters + * added set tag + * changed default directory permission when cache dir does not exist (more secure) + * added macro support + * changed filters first optional argument to be a Twig_Environment instance instead of a Twig_Template instance + * made Twig_Autoloader::autoload() a static method + * avoid writing template file if an error occurs + * added $ escaping when outputting raw strings + * enhanced some error messages to ease debugging + * fixed empty cache files when the template contains an error + +* 0.9.1 (2009-10-14) + + * fixed a bug in PHP 5.2.6 + * fixed numbers with one than one decimal + * added support for method calls with arguments ({{ foo.bar('a', 43) }}) + * made small speed optimizations + * made minor tweaks to allow better extensibility and flexibility + +* 0.9.0 (2009-10-12) + + * Initial release diff --git a/src/vendor/twig/twig/LICENSE b/src/vendor/twig/twig/LICENSE new file mode 100644 index 0000000..cc74f81 --- /dev/null +++ b/src/vendor/twig/twig/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2009-2016 by the Twig Team. + +Some 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. + + * The names of the contributors may not 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/src/vendor/twig/twig/README.rst b/src/vendor/twig/twig/README.rst new file mode 100644 index 0000000..81737b0 --- /dev/null +++ b/src/vendor/twig/twig/README.rst @@ -0,0 +1,15 @@ +Twig, the flexible, fast, and secure template language for PHP +============================================================== + +Twig is a template language for PHP, released under the new BSD license (code +and documentation). + +Twig uses a syntax similar to the Django and Jinja template languages which +inspired the Twig runtime environment. + +More Information +---------------- + +Read the `documentation`_ for more information. + +.. _documentation: http://twig.sensiolabs.org/documentation diff --git a/src/vendor/twig/twig/composer.json b/src/vendor/twig/twig/composer.json new file mode 100644 index 0000000..5bf8a43 --- /dev/null +++ b/src/vendor/twig/twig/composer.json @@ -0,0 +1,46 @@ +{ + "name": "twig/twig", + "type": "library", + "description": "Twig, the flexible, fast, and secure template language for PHP", + "keywords": ["templating"], + "homepage": "http://twig.sensiolabs.org", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "homepage": "http://twig.sensiolabs.org/contributors", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "support": { + "forum": "https://groups.google.com/forum/#!forum/twig-users" + }, + "require": { + "php": ">=5.2.7" + }, + "require-dev": { + "symfony/phpunit-bridge": "~3.2@dev", + "symfony/debug": "~2.7" + }, + "autoload": { + "psr-0" : { + "Twig_" : "lib/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.29-dev" + } + } +} diff --git a/src/vendor/twig/twig/doc/advanced.rst b/src/vendor/twig/twig/doc/advanced.rst new file mode 100644 index 0000000..8ba3e01 --- /dev/null +++ b/src/vendor/twig/twig/doc/advanced.rst @@ -0,0 +1,957 @@ +Extending Twig +============== + +.. caution:: + + This section describes how to extend Twig as of **Twig 1.12**. If you are + using an older version, read the :doc:`legacy` chapter + instead. + +Twig can be extended in many ways; you can add extra tags, filters, tests, +operators, global variables, and functions. You can even extend the parser +itself with node visitors. + +.. note:: + + The first section of this chapter describes how to extend Twig easily. If + you want to reuse your changes in different projects or if you want to + share them with others, you should then create an extension as described + in the following section. + +.. caution:: + + When extending Twig without creating an extension, Twig won't be able to + recompile your templates when the PHP code is updated. To see your changes + in real-time, either disable template caching or package your code into an + extension (see the next section of this chapter). + +Before extending Twig, you must understand the differences between all the +different possible extension points and when to use them. + +First, remember that Twig has two main language constructs: + +* ``{{ }}``: used to print the result of an expression evaluation; + +* ``{% %}``: used to execute statements. + +To understand why Twig exposes so many extension points, let's see how to +implement a *Lorem ipsum* generator (it needs to know the number of words to +generate). + +You can use a ``lipsum`` *tag*: + +.. code-block:: jinja + + {% lipsum 40 %} + +That works, but using a tag for ``lipsum`` is not a good idea for at least +three main reasons: + +* ``lipsum`` is not a language construct; +* The tag outputs something; +* The tag is not flexible as you cannot use it in an expression: + + .. code-block:: jinja + + {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }} + +In fact, you rarely need to create tags; and that's good news because tags are +the most complex extension point of Twig. + +Now, let's use a ``lipsum`` *filter*: + +.. code-block:: jinja + + {{ 40|lipsum }} + +Again, it works, but it looks weird. A filter transforms the passed value to +something else but here we use the value to indicate the number of words to +generate (so, ``40`` is an argument of the filter, not the value we want to +transform). + +Next, let's use a ``lipsum`` *function*: + +.. code-block:: jinja + + {{ lipsum(40) }} + +Here we go. For this specific example, the creation of a function is the +extension point to use. And you can use it anywhere an expression is accepted: + +.. code-block:: jinja + + {{ 'some text' ~ lipsum(40) ~ 'some more text' }} + + {% set lipsum = lipsum(40) %} + +Last but not the least, you can also use a *global* object with a method able +to generate lorem ipsum text: + +.. code-block:: jinja + + {{ text.lipsum(40) }} + +As a rule of thumb, use functions for frequently used features and global +objects for everything else. + +Keep in mind the following when you want to extend Twig: + +========== ========================== ========== ========================= +What? Implementation difficulty? How often? When? +========== ========================== ========== ========================= +*macro* trivial frequent Content generation +*global* trivial frequent Helper object +*function* trivial frequent Content generation +*filter* trivial frequent Value transformation +*tag* complex rare DSL language construct +*test* trivial rare Boolean decision +*operator* trivial rare Values transformation +========== ========================== ========== ========================= + +Globals +------- + +A global variable is like any other template variable, except that it's +available in all templates and macros:: + + $twig = new Twig_Environment($loader); + $twig->addGlobal('text', new Text()); + +You can then use the ``text`` variable anywhere in a template: + +.. code-block:: jinja + + {{ text.lipsum(40) }} + +Filters +------- + +Creating a filter is as simple as associating a name with a PHP callable:: + + // an anonymous function + $filter = new Twig_SimpleFilter('rot13', function ($string) { + return str_rot13($string); + }); + + // or a simple PHP function + $filter = new Twig_SimpleFilter('rot13', 'str_rot13'); + + // or a class static method + $filter = new Twig_SimpleFilter('rot13', array('SomeClass', 'rot13Filter')); + $filter = new Twig_SimpleFilter('rot13', 'SomeClass::rot13Filter'); + + // or a class method + $filter = new Twig_SimpleFilter('rot13', array($this, 'rot13Filter')); + // the one below needs a runtime implementation (see below for more information) + $filter = new Twig_SimpleFilter('rot13', array('SomeClass', 'rot13Filter')); + +The first argument passed to the ``Twig_SimpleFilter`` constructor is the name +of the filter you will use in templates and the second one is the PHP callable +to associate with it. + +Then, add the filter to your Twig environment:: + + $twig = new Twig_Environment($loader); + $twig->addFilter($filter); + +And here is how to use it in a template: + +.. code-block:: jinja + + {{ 'Twig'|rot13 }} + + {# will output Gjvt #} + +When called by Twig, the PHP callable receives the left side of the filter +(before the pipe ``|``) as the first argument and the extra arguments passed +to the filter (within parentheses ``()``) as extra arguments. + +For instance, the following code: + +.. code-block:: jinja + + {{ 'TWIG'|lower }} + {{ now|date('d/m/Y') }} + +is compiled to something like the following:: + + + + +The ``Twig_SimpleFilter`` class takes an array of options as its last +argument:: + + $filter = new Twig_SimpleFilter('rot13', 'str_rot13', $options); + +Environment-aware Filters +~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to access the current environment instance in your filter, set the +``needs_environment`` option to ``true``; Twig will pass the current +environment as the first argument to the filter call:: + + $filter = new Twig_SimpleFilter('rot13', function (Twig_Environment $env, $string) { + // get the current charset for instance + $charset = $env->getCharset(); + + return str_rot13($string); + }, array('needs_environment' => true)); + +Context-aware Filters +~~~~~~~~~~~~~~~~~~~~~ + +If you want to access the current context in your filter, set the +``needs_context`` option to ``true``; Twig will pass the current context as +the first argument to the filter call (or the second one if +``needs_environment`` is also set to ``true``):: + + $filter = new Twig_SimpleFilter('rot13', function ($context, $string) { + // ... + }, array('needs_context' => true)); + + $filter = new Twig_SimpleFilter('rot13', function (Twig_Environment $env, $context, $string) { + // ... + }, array('needs_context' => true, 'needs_environment' => true)); + +Automatic Escaping +~~~~~~~~~~~~~~~~~~ + +If automatic escaping is enabled, the output of the filter may be escaped +before printing. If your filter acts as an escaper (or explicitly outputs HTML +or JavaScript code), you will want the raw output to be printed. In such a +case, set the ``is_safe`` option:: + + $filter = new Twig_SimpleFilter('nl2br', 'nl2br', array('is_safe' => array('html'))); + +Some filters may need to work on input that is already escaped or safe, for +example when adding (safe) HTML tags to originally unsafe output. In such a +case, set the ``pre_escape`` option to escape the input data before it is run +through your filter:: + + $filter = new Twig_SimpleFilter('somefilter', 'somefilter', array('pre_escape' => 'html', 'is_safe' => array('html'))); + +Variadic Filters +~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.19 + Support for variadic filters was added in Twig 1.19. + +When a filter should accept an arbitrary number of arguments, set the +``is_variadic`` option to ``true``; Twig will pass the extra arguments as the +last argument to the filter call as an array:: + + $filter = new Twig_SimpleFilter('thumbnail', function ($file, array $options = array()) { + // ... + }, array('is_variadic' => true)); + +Be warned that named arguments passed to a variadic filter cannot be checked +for validity as they will automatically end up in the option array. + +Dynamic Filters +~~~~~~~~~~~~~~~ + +A filter name containing the special ``*`` character is a dynamic filter as +the ``*`` can be any string:: + + $filter = new Twig_SimpleFilter('*_path', function ($name, $arguments) { + // ... + }); + +The following filters will be matched by the above defined dynamic filter: + +* ``product_path`` +* ``category_path`` + +A dynamic filter can define more than one dynamic parts:: + + $filter = new Twig_SimpleFilter('*_path_*', function ($name, $suffix, $arguments) { + // ... + }); + +The filter will receive all dynamic part values before the normal filter +arguments, but after the environment and the context. For instance, a call to +``'foo'|a_path_b()`` will result in the following arguments to be passed to +the filter: ``('a', 'b', 'foo')``. + +Deprecated Filters +~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.21 + Support for deprecated filters was added in Twig 1.21. + +You can mark a filter as being deprecated by setting the ``deprecated`` option +to ``true``. You can also give an alternative filter that replaces the +deprecated one when that makes sense:: + + $filter = new Twig_SimpleFilter('obsolete', function () { + // ... + }, array('deprecated' => true, 'alternative' => 'new_one')); + +When a filter is deprecated, Twig emits a deprecation notice when compiling a +template using it. See :ref:`deprecation-notices` for more information. + +Functions +--------- + +Functions are defined in the exact same way as filters, but you need to create +an instance of ``Twig_SimpleFunction``:: + + $twig = new Twig_Environment($loader); + $function = new Twig_SimpleFunction('function_name', function () { + // ... + }); + $twig->addFunction($function); + +Functions support the same features as filters, except for the ``pre_escape`` +and ``preserves_safety`` options. + +Tests +----- + +Tests are defined in the exact same way as filters and functions, but you need +to create an instance of ``Twig_SimpleTest``:: + + $twig = new Twig_Environment($loader); + $test = new Twig_SimpleTest('test_name', function () { + // ... + }); + $twig->addTest($test); + +Tests allow you to create custom application specific logic for evaluating +boolean conditions. As a simple example, let's create a Twig test that checks if +objects are 'red':: + + $twig = new Twig_Environment($loader); + $test = new Twig_SimpleTest('red', function ($value) { + if (isset($value->color) && $value->color == 'red') { + return true; + } + if (isset($value->paint) && $value->paint == 'red') { + return true; + } + return false; + }); + $twig->addTest($test); + +Test functions should always return true/false. + +When creating tests you can use the ``node_class`` option to provide custom test +compilation. This is useful if your test can be compiled into PHP primitives. +This is used by many of the tests built into Twig:: + + $twig = new Twig_Environment($loader); + $test = new Twig_SimpleTest( + 'odd', + null, + array('node_class' => 'Twig_Node_Expression_Test_Odd')); + $twig->addTest($test); + + class Twig_Node_Expression_Test_Odd extends Twig_Node_Expression_Test + { + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' % 2 == 1') + ->raw(')') + ; + } + } + +The above example shows how you can create tests that use a node class. The +node class has access to one sub-node called 'node'. This sub-node contains the +value that is being tested. When the ``odd`` filter is used in code such as: + +.. code-block:: jinja + + {% if my_value is odd %} + +The ``node`` sub-node will contain an expression of ``my_value``. Node-based +tests also have access to the ``arguments`` node. This node will contain the +various other arguments that have been provided to your test. + +If you want to pass a variable number of positional or named arguments to the +test, set the ``is_variadic`` option to ``true``. Tests also support dynamic +name feature as filters and functions. + +Tags +---- + +One of the most exciting features of a template engine like Twig is the +possibility to define new language constructs. This is also the most complex +feature as you need to understand how Twig's internals work. + +Let's create a simple ``set`` tag that allows the definition of simple +variables from within a template. The tag can be used like follows: + +.. code-block:: jinja + + {% set name = "value" %} + + {{ name }} + + {# should output value #} + +.. note:: + + The ``set`` tag is part of the Core extension and as such is always + available. The built-in version is slightly more powerful and supports + multiple assignments by default (cf. the template designers chapter for + more information). + +Three steps are needed to define a new tag: + +* Defining a Token Parser class (responsible for parsing the template code); + +* Defining a Node class (responsible for converting the parsed code to PHP); + +* Registering the tag. + +Registering a new tag +~~~~~~~~~~~~~~~~~~~~~ + +Adding a tag is as simple as calling the ``addTokenParser`` method on the +``Twig_Environment`` instance:: + + $twig = new Twig_Environment($loader); + $twig->addTokenParser(new Project_Set_TokenParser()); + +Defining a Token Parser +~~~~~~~~~~~~~~~~~~~~~~~ + +Now, let's see the actual code of this class:: + + class Project_Set_TokenParser extends Twig_TokenParser + { + public function parse(Twig_Token $token) + { + $parser = $this->parser; + $stream = $parser->getStream(); + + $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); + $stream->expect(Twig_Token::OPERATOR_TYPE, '='); + $value = $parser->getExpressionParser()->parseExpression(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new Project_Set_Node($name, $value, $token->getLine(), $this->getTag()); + } + + public function getTag() + { + return 'set'; + } + } + +The ``getTag()`` method must return the tag we want to parse, here ``set``. + +The ``parse()`` method is invoked whenever the parser encounters a ``set`` +tag. It should return a ``Twig_Node`` instance that represents the node (the +``Project_Set_Node`` calls creating is explained in the next section). + +The parsing process is simplified thanks to a bunch of methods you can call +from the token stream (``$this->parser->getStream()``): + +* ``getCurrent()``: Gets the current token in the stream. + +* ``next()``: Moves to the next token in the stream, *but returns the old one*. + +* ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether + the current token is of a particular type or value (or both). The value may be an + array of several possible values. + +* ``expect($type[, $value[, $message]])``: If the current token isn't of the given + type/value a syntax error is thrown. Otherwise, if the type and value are correct, + the token is returned and the stream moves to the next token. + +* ``look()``: Looks a the next token without consuming it. + +Parsing expressions is done by calling the ``parseExpression()`` like we did for +the ``set`` tag. + +.. tip:: + + Reading the existing ``TokenParser`` classes is the best way to learn all + the nitty-gritty details of the parsing process. + +Defining a Node +~~~~~~~~~~~~~~~ + +The ``Project_Set_Node`` class itself is rather simple:: + + class Project_Set_Node extends Twig_Node + { + public function __construct($name, Twig_Node_Expression $value, $line, $tag = null) + { + parent::__construct(array('value' => $value), array('name' => $name), $line, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('$context[\''.$this->getAttribute('name').'\'] = ') + ->subcompile($this->getNode('value')) + ->raw(";\n") + ; + } + } + +The compiler implements a fluid interface and provides methods that helps the +developer generate beautiful and readable PHP code: + +* ``subcompile()``: Compiles a node. + +* ``raw()``: Writes the given string as is. + +* ``write()``: Writes the given string by adding indentation at the beginning + of each line. + +* ``string()``: Writes a quoted string. + +* ``repr()``: Writes a PHP representation of a given value (see + ``Twig_Node_For`` for a usage example). + +* ``addDebugInfo()``: Adds the line of the original template file related to + the current node as a comment. + +* ``indent()``: Indents the generated code (see ``Twig_Node_Block`` for a + usage example). + +* ``outdent()``: Outdents the generated code (see ``Twig_Node_Block`` for a + usage example). + +.. _creating_extensions: + +Creating an Extension +--------------------- + +The main motivation for writing an extension is to move often used code into a +reusable class like adding support for internationalization. An extension can +define tags, filters, tests, operators, global variables, functions, and node +visitors. + +Most of the time, it is useful to create a single extension for your project, +to host all the specific tags and filters you want to add to Twig. + +.. tip:: + + When packaging your code into an extension, Twig is smart enough to + recompile your templates whenever you make a change to it (when + ``auto_reload`` is enabled). + +.. note:: + + Before writing your own extensions, have a look at the Twig official + extension repository: http://github.com/twigphp/Twig-extensions. + +An extension is a class that implements the following interface:: + + interface Twig_ExtensionInterface + { + /** + * Initializes the runtime environment. + * + * This is where you can load some file that contains filter functions for instance. + * + * @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_InitRuntimeInterface instead + */ + function initRuntime(Twig_Environment $environment); + + /** + * Returns the token parser instances to add to the existing list. + * + * @return (Twig_TokenParserInterface|Twig_TokenParserBrokerInterface)[] + */ + function getTokenParsers(); + + /** + * Returns the node visitor instances to add to the existing list. + * + * @return Twig_NodeVisitorInterface[] + */ + function getNodeVisitors(); + + /** + * Returns a list of filters to add to the existing list. + * + * @return Twig_SimpleFilter[] + */ + function getFilters(); + + /** + * Returns a list of tests to add to the existing list. + * + * @return Twig_SimpleTest[] + */ + function getTests(); + + /** + * Returns a list of functions to add to the existing list. + * + * @return Twig_SimpleFunction[] + */ + function getFunctions(); + + /** + * Returns a list of operators to add to the existing list. + * + * @return array First array of unary operators, second array of binary operators + */ + function getOperators(); + + /** + * Returns a list of global variables to add to the existing list. + * + * @return array An array of global variables + * + * @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_GlobalsInterface instead + */ + function getGlobals(); + + /** + * Returns the name of the extension. + * + * @return string The extension name + * + * @deprecated since 1.26 (to be removed in 2.0), not used anymore internally + */ + function getName(); + } + +To keep your extension class clean and lean, inherit from the built-in +``Twig_Extension`` class instead of implementing the interface as it provides +empty implementations for all methods: + + class Project_Twig_Extension extends Twig_Extension + { + } + +Of course, this extension does nothing for now. We will customize it in the +next sections. + +.. note:: + + Prior to Twig 1.26, you must implement the ``getName()`` method which must + return a unique identifier for the extension. + +Twig does not care where you save your extension on the filesystem, as all +extensions must be registered explicitly to be available in your templates. + +You can register an extension by using the ``addExtension()`` method on your +main ``Environment`` object:: + + $twig = new Twig_Environment($loader); + $twig->addExtension(new Project_Twig_Extension()); + +.. tip:: + + The Twig core extensions are great examples of how extensions work. + +Globals +~~~~~~~ + +Global variables can be registered in an extension via the ``getGlobals()`` +method:: + + class Project_Twig_Extension extends Twig_Extension implements Twig_Extension_GlobalsInterface + { + public function getGlobals() + { + return array( + 'text' => new Text(), + ); + } + + // ... + } + +Functions +~~~~~~~~~ + +Functions can be registered in an extension via the ``getFunctions()`` +method:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getFunctions() + { + return array( + new Twig_SimpleFunction('lipsum', 'generate_lipsum'), + ); + } + + // ... + } + +Filters +~~~~~~~ + +To add a filter to an extension, you need to override the ``getFilters()`` +method. This method must return an array of filters to add to the Twig +environment:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getFilters() + { + return array( + new Twig_SimpleFilter('rot13', 'str_rot13'), + ); + } + + // ... + } + +Tags +~~~~ + +Adding a tag in an extension can be done by overriding the +``getTokenParsers()`` method. This method must return an array of tags to add +to the Twig environment:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getTokenParsers() + { + return array(new Project_Set_TokenParser()); + } + + // ... + } + +In the above code, we have added a single new tag, defined by the +``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is +responsible for parsing the tag and compiling it to PHP. + +Operators +~~~~~~~~~ + +The ``getOperators()`` methods lets you add new operators. Here is how to add +``!``, ``||``, and ``&&`` operators:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getOperators() + { + return array( + array( + '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'), + ), + array( + '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + ), + ); + } + + // ... + } + +Tests +~~~~~ + +The ``getTests()`` method lets you add new test functions:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getTests() + { + return array( + new Twig_SimpleTest('even', 'twig_test_even'), + ); + } + + // ... + } + +Definition vs Runtime +~~~~~~~~~~~~~~~~~~~~~ + +Twig filters, functions, and tests runtime implementations can be defined as +any valid PHP callable: + +* **functions/static methods**: Simple to implement and fast (used by all Twig + core extensions); but it is hard for the runtime to depend on external + objects; + +* **closures**: Simple to implement; + +* **object methods**: More flexible and required if your runtime code depends + on external objects. + +The simplest way to use methods is to define them on the extension itself:: + + class Project_Twig_Extension extends Twig_Extension + { + private $rot13Provider; + + public function __construct($rot13Provider) + { + $this->rot13Provider = $rot13Provider; + } + + public function getFunctions() + { + return array( + new Twig_SimpleFunction('rot13', array($this, 'rot13')), + ); + } + + public function rot13($value) + { + return $rot13Provider->rot13($value); + } + } + +This is very convenient but not recommended as it makes template compilation +depend on runtime dependencies even if they are not needed (think for instance +as a dependency that connects to a database engine). + +As of Twig 1.26, you can easily decouple the extension definitions from their +runtime implementations by registering a ``Twig_RuntimeLoaderInterface`` +instance on the environment that knows how to instantiate such runtime classes +(runtime classes must be autoload-able):: + + class RuntimeLoader implements Twig_RuntimeLoaderInterface + { + public function load($class) + { + // implement the logic to create an instance of $class + // and inject its dependencies + // most of the time, it means using your dependency injection container + if ('Project_Twig_RuntimeExtension' === $class) { + return new $class(new Rot13Provider()); + } else { + // ... + } + } + } + + $twig->addRuntimeLoader(new RuntimeLoader()); + +It is now possible to move the runtime logic to a new +``Project_Twig_RuntimeExtension`` class and use it directly in the extension:: + + class Project_Twig_RuntimeExtension extends Twig_Extension + { + private $rot13Provider; + + public function __construct($rot13Provider) + { + $this->rot13Provider = $rot13Provider; + } + + public function rot13($value) + { + return $rot13Provider->rot13($value); + } + } + + class Project_Twig_Extension extends Twig_Extension + { + public function getFunctions() + { + return array( + new Twig_SimpleFunction('rot13', array('Project_Twig_RuntimeExtension', 'rot13')), + // or + new Twig_SimpleFunction('rot13', 'Project_Twig_RuntimeExtension::rot13'), + ); + } + } + +Overloading +----------- + +To overload an already defined filter, test, operator, global variable, or +function, re-define it in an extension and register it **as late as +possible** (order matters):: + + class MyCoreExtension extends Twig_Extension + { + public function getFilters() + { + return array( + new Twig_SimpleFilter('date', array($this, 'dateFilter')), + ); + } + + public function dateFilter($timestamp, $format = 'F j, Y H:i') + { + // do something different from the built-in date filter + } + } + + $twig = new Twig_Environment($loader); + $twig->addExtension(new MyCoreExtension()); + +Here, we have overloaded the built-in ``date`` filter with a custom one. + +If you do the same on the ``Twig_Environment`` itself, beware that it takes +precedence over any other registered extensions:: + + $twig = new Twig_Environment($loader); + $twig->addFilter(new Twig_SimpleFilter('date', function ($timestamp, $format = 'F j, Y H:i') { + // do something different from the built-in date filter + })); + // the date filter will come from the above registration, not + // from the registered extension below + $twig->addExtension(new MyCoreExtension()); + +.. caution:: + + Note that overloading the built-in Twig elements is not recommended as it + might be confusing. + +Testing an Extension +-------------------- + +Functional Tests +~~~~~~~~~~~~~~~~ + +You can create functional tests for extensions simply by creating the +following file structure in your test directory:: + + Fixtures/ + filters/ + foo.test + bar.test + functions/ + foo.test + bar.test + tags/ + foo.test + bar.test + IntegrationTest.php + +The ``IntegrationTest.php`` file should look like this:: + + class Project_Tests_IntegrationTest extends Twig_Test_IntegrationTestCase + { + public function getExtensions() + { + return array( + new Project_Twig_Extension1(), + new Project_Twig_Extension2(), + ); + } + + public function getFixturesDir() + { + return dirname(__FILE__).'/Fixtures/'; + } + } + +Fixtures examples can be found within the Twig repository +`tests/Twig/Fixtures`_ directory. + +Node Tests +~~~~~~~~~~ + +Testing the node visitors can be complex, so extend your test cases from +``Twig_Test_NodeTestCase``. Examples can be found in the Twig repository +`tests/Twig/Node`_ directory. + +.. _`rot13`: http://www.php.net/manual/en/function.str-rot13.php +.. _`tests/Twig/Fixtures`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Fixtures +.. _`tests/Twig/Node`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Node diff --git a/src/vendor/twig/twig/doc/advanced_legacy.rst b/src/vendor/twig/twig/doc/advanced_legacy.rst new file mode 100644 index 0000000..33e9f45 --- /dev/null +++ b/src/vendor/twig/twig/doc/advanced_legacy.rst @@ -0,0 +1,885 @@ +Extending Twig +============== + +.. caution:: + + This section describes how to extends Twig for versions **older than + 1.12**. If you are using a newer version, read the :doc:`newer` + chapter instead. + +Twig can be extended in many ways; you can add extra tags, filters, tests, +operators, global variables, and functions. You can even extend the parser +itself with node visitors. + +.. note:: + + The first section of this chapter describes how to extend Twig easily. If + you want to reuse your changes in different projects or if you want to + share them with others, you should then create an extension as described + in the following section. + +.. caution:: + + When extending Twig by calling methods on the Twig environment instance, + Twig won't be able to recompile your templates when the PHP code is + updated. To see your changes in real-time, either disable template caching + or package your code into an extension (see the next section of this + chapter). + +Before extending Twig, you must understand the differences between all the +different possible extension points and when to use them. + +First, remember that Twig has two main language constructs: + +* ``{{ }}``: used to print the result of an expression evaluation; + +* ``{% %}``: used to execute statements. + +To understand why Twig exposes so many extension points, let's see how to +implement a *Lorem ipsum* generator (it needs to know the number of words to +generate). + +You can use a ``lipsum`` *tag*: + +.. code-block:: jinja + + {% lipsum 40 %} + +That works, but using a tag for ``lipsum`` is not a good idea for at least +three main reasons: + +* ``lipsum`` is not a language construct; +* The tag outputs something; +* The tag is not flexible as you cannot use it in an expression: + + .. code-block:: jinja + + {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }} + +In fact, you rarely need to create tags; and that's good news because tags are +the most complex extension point of Twig. + +Now, let's use a ``lipsum`` *filter*: + +.. code-block:: jinja + + {{ 40|lipsum }} + +Again, it works, but it looks weird. A filter transforms the passed value to +something else but here we use the value to indicate the number of words to +generate (so, ``40`` is an argument of the filter, not the value we want to +transform). + +Next, let's use a ``lipsum`` *function*: + +.. code-block:: jinja + + {{ lipsum(40) }} + +Here we go. For this specific example, the creation of a function is the +extension point to use. And you can use it anywhere an expression is accepted: + +.. code-block:: jinja + + {{ 'some text' ~ ipsum(40) ~ 'some more text' }} + + {% set ipsum = ipsum(40) %} + +Last but not the least, you can also use a *global* object with a method able +to generate lorem ipsum text: + +.. code-block:: jinja + + {{ text.lipsum(40) }} + +As a rule of thumb, use functions for frequently used features and global +objects for everything else. + +Keep in mind the following when you want to extend Twig: + +========== ========================== ========== ========================= +What? Implementation difficulty? How often? When? +========== ========================== ========== ========================= +*macro* trivial frequent Content generation +*global* trivial frequent Helper object +*function* trivial frequent Content generation +*filter* trivial frequent Value transformation +*tag* complex rare DSL language construct +*test* trivial rare Boolean decision +*operator* trivial rare Values transformation +========== ========================== ========== ========================= + +Globals +------- + +A global variable is like any other template variable, except that it's +available in all templates and macros:: + + $twig = new Twig_Environment($loader); + $twig->addGlobal('text', new Text()); + +You can then use the ``text`` variable anywhere in a template: + +.. code-block:: jinja + + {{ text.lipsum(40) }} + +Filters +------- + +A filter is a regular PHP function or an object method that takes the left +side of the filter (before the pipe ``|``) as first argument and the extra +arguments passed to the filter (within parentheses ``()``) as extra arguments. + +Defining a filter is as easy as associating the filter name with a PHP +callable. For instance, let's say you have the following code in a template: + +.. code-block:: jinja + + {{ 'TWIG'|lower }} + +When compiling this template to PHP, Twig looks for the PHP callable +associated with the ``lower`` filter. The ``lower`` filter is a built-in Twig +filter, and it is simply mapped to the PHP ``strtolower()`` function. After +compilation, the generated PHP code is roughly equivalent to: + +.. code-block:: html+php + + + +As you can see, the ``'TWIG'`` string is passed as a first argument to the PHP +function. + +A filter can also take extra arguments like in the following example: + +.. code-block:: jinja + + {{ now|date('d/m/Y') }} + +In this case, the extra arguments are passed to the function after the main +argument, and the compiled code is equivalent to: + +.. code-block:: html+php + + + +Let's see how to create a new filter. + +In this section, we will create a ``rot13`` filter, which should return the +`rot13`_ transformation of a string. Here is an example of its usage and the +expected output: + +.. code-block:: jinja + + {{ "Twig"|rot13 }} + + {# should displays Gjvt #} + +Adding a filter is as simple as calling the ``addFilter()`` method on the +``Twig_Environment`` instance:: + + $twig = new Twig_Environment($loader); + $twig->addFilter('rot13', new Twig_Filter_Function('str_rot13')); + +The second argument of ``addFilter()`` is an instance of ``Twig_Filter``. +Here, we use ``Twig_Filter_Function`` as the filter is a PHP function. The +first argument passed to the ``Twig_Filter_Function`` constructor is the name +of the PHP function to call, here ``str_rot13``, a native PHP function. + +Let's say I now want to be able to add a prefix before the converted string: + +.. code-block:: jinja + + {{ "Twig"|rot13('prefix_') }} + + {# should displays prefix_Gjvt #} + +As the PHP ``str_rot13()`` function does not support this requirement, let's +create a new PHP function:: + + function project_compute_rot13($string, $prefix = '') + { + return $prefix.str_rot13($string); + } + +As you can see, the ``prefix`` argument of the filter is passed as an extra +argument to the ``project_compute_rot13()`` function. + +Adding this filter is as easy as before:: + + $twig->addFilter('rot13', new Twig_Filter_Function('project_compute_rot13')); + +For better encapsulation, a filter can also be defined as a static method of a +class. The ``Twig_Filter_Function`` class can also be used to register such +static methods as filters:: + + $twig->addFilter('rot13', new Twig_Filter_Function('SomeClass::rot13Filter')); + +.. tip:: + + In an extension, you can also define a filter as a static method of the + extension class. + +Environment aware Filters +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Twig_Filter`` classes take options as their last argument. For instance, +if you want access to the current environment instance in your filter, set the +``needs_environment`` option to ``true``:: + + $filter = new Twig_Filter_Function('str_rot13', array('needs_environment' => true)); + +Twig will then pass the current environment as the first argument to the +filter call:: + + function twig_compute_rot13(Twig_Environment $env, $string) + { + // get the current charset for instance + $charset = $env->getCharset(); + + return str_rot13($string); + } + +Automatic Escaping +~~~~~~~~~~~~~~~~~~ + +If automatic escaping is enabled, the output of the filter may be escaped +before printing. If your filter acts as an escaper (or explicitly outputs HTML +or JavaScript code), you will want the raw output to be printed. In such a +case, set the ``is_safe`` option:: + + $filter = new Twig_Filter_Function('nl2br', array('is_safe' => array('html'))); + +Some filters may need to work on input that is already escaped or safe, for +example when adding (safe) HTML tags to originally unsafe output. In such a +case, set the ``pre_escape`` option to escape the input data before it is run +through your filter:: + + $filter = new Twig_Filter_Function('somefilter', array('pre_escape' => 'html', 'is_safe' => array('html'))); + +Dynamic Filters +~~~~~~~~~~~~~~~ + +.. versionadded:: 1.5 + Dynamic filters support was added in Twig 1.5. + +A filter name containing the special ``*`` character is a dynamic filter as +the ``*`` can be any string:: + + $twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path')); + + function twig_path($name, $arguments) + { + // ... + } + +The following filters will be matched by the above defined dynamic filter: + +* ``product_path`` +* ``category_path`` + +A dynamic filter can define more than one dynamic parts:: + + $twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path')); + + function twig_path($name, $suffix, $arguments) + { + // ... + } + +The filter will receive all dynamic part values before the normal filters +arguments. For instance, a call to ``'foo'|a_path_b()`` will result in the +following PHP call: ``twig_path('a', 'b', 'foo')``. + +Functions +--------- + +A function is a regular PHP function or an object method that can be called from +templates. + +.. code-block:: jinja + + {{ constant("DATE_W3C") }} + +When compiling this template to PHP, Twig looks for the PHP callable +associated with the ``constant`` function. The ``constant`` function is a built-in Twig +function, and it is simply mapped to the PHP ``constant()`` function. After +compilation, the generated PHP code is roughly equivalent to: + +.. code-block:: html+php + + + +Adding a function is similar to adding a filter. This can be done by calling the +``addFunction()`` method on the ``Twig_Environment`` instance:: + + $twig = new Twig_Environment($loader); + $twig->addFunction('functionName', new Twig_Function_Function('someFunction')); + +You can also expose extension methods as functions in your templates:: + + // $this is an object that implements Twig_ExtensionInterface. + $twig = new Twig_Environment($loader); + $twig->addFunction('otherFunction', new Twig_Function_Method($this, 'someMethod')); + +Functions also support ``needs_environment`` and ``is_safe`` parameters. + +Dynamic Functions +~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.5 + Dynamic functions support was added in Twig 1.5. + +A function name containing the special ``*`` character is a dynamic function +as the ``*`` can be any string:: + + $twig->addFunction('*_path', new Twig_Function_Function('twig_path')); + + function twig_path($name, $arguments) + { + // ... + } + +The following functions will be matched by the above defined dynamic function: + +* ``product_path`` +* ``category_path`` + +A dynamic function can define more than one dynamic parts:: + + $twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path')); + + function twig_path($name, $suffix, $arguments) + { + // ... + } + +The function will receive all dynamic part values before the normal functions +arguments. For instance, a call to ``a_path_b('foo')`` will result in the +following PHP call: ``twig_path('a', 'b', 'foo')``. + +Tags +---- + +One of the most exciting feature of a template engine like Twig is the +possibility to define new language constructs. This is also the most complex +feature as you need to understand how Twig's internals work. + +Let's create a simple ``set`` tag that allows the definition of simple +variables from within a template. The tag can be used like follows: + +.. code-block:: jinja + + {% set name = "value" %} + + {{ name }} + + {# should output value #} + +.. note:: + + The ``set`` tag is part of the Core extension and as such is always + available. The built-in version is slightly more powerful and supports + multiple assignments by default (cf. the template designers chapter for + more information). + +Three steps are needed to define a new tag: + +* Defining a Token Parser class (responsible for parsing the template code); + +* Defining a Node class (responsible for converting the parsed code to PHP); + +* Registering the tag. + +Registering a new tag +~~~~~~~~~~~~~~~~~~~~~ + +Adding a tag is as simple as calling the ``addTokenParser`` method on the +``Twig_Environment`` instance:: + + $twig = new Twig_Environment($loader); + $twig->addTokenParser(new Project_Set_TokenParser()); + +Defining a Token Parser +~~~~~~~~~~~~~~~~~~~~~~~ + +Now, let's see the actual code of this class:: + + class Project_Set_TokenParser extends Twig_TokenParser + { + public function parse(Twig_Token $token) + { + $lineno = $token->getLine(); + $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue(); + $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, '='); + $value = $this->parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Project_Set_Node($name, $value, $lineno, $this->getTag()); + } + + public function getTag() + { + return 'set'; + } + } + +The ``getTag()`` method must return the tag we want to parse, here ``set``. + +The ``parse()`` method is invoked whenever the parser encounters a ``set`` +tag. It should return a ``Twig_Node`` instance that represents the node (the +``Project_Set_Node`` calls creating is explained in the next section). + +The parsing process is simplified thanks to a bunch of methods you can call +from the token stream (``$this->parser->getStream()``): + +* ``getCurrent()``: Gets the current token in the stream. + +* ``next()``: Moves to the next token in the stream, *but returns the old one*. + +* ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether + the current token is of a particular type or value (or both). The value may be an + array of several possible values. + +* ``expect($type[, $value[, $message]])``: If the current token isn't of the given + type/value a syntax error is thrown. Otherwise, if the type and value are correct, + the token is returned and the stream moves to the next token. + +* ``look()``: Looks a the next token without consuming it. + +Parsing expressions is done by calling the ``parseExpression()`` like we did for +the ``set`` tag. + +.. tip:: + + Reading the existing ``TokenParser`` classes is the best way to learn all + the nitty-gritty details of the parsing process. + +Defining a Node +~~~~~~~~~~~~~~~ + +The ``Project_Set_Node`` class itself is rather simple:: + + class Project_Set_Node extends Twig_Node + { + public function __construct($name, Twig_Node_Expression $value, $lineno, $tag = null) + { + parent::__construct(array('value' => $value), array('name' => $name), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('$context[\''.$this->getAttribute('name').'\'] = ') + ->subcompile($this->getNode('value')) + ->raw(";\n") + ; + } + } + +The compiler implements a fluid interface and provides methods that helps the +developer generate beautiful and readable PHP code: + +* ``subcompile()``: Compiles a node. + +* ``raw()``: Writes the given string as is. + +* ``write()``: Writes the given string by adding indentation at the beginning + of each line. + +* ``string()``: Writes a quoted string. + +* ``repr()``: Writes a PHP representation of a given value (see + ``Twig_Node_For`` for a usage example). + +* ``addDebugInfo()``: Adds the line of the original template file related to + the current node as a comment. + +* ``indent()``: Indents the generated code (see ``Twig_Node_Block`` for a + usage example). + +* ``outdent()``: Outdents the generated code (see ``Twig_Node_Block`` for a + usage example). + +.. _creating_extensions: + +Creating an Extension +--------------------- + +The main motivation for writing an extension is to move often used code into a +reusable class like adding support for internationalization. An extension can +define tags, filters, tests, operators, global variables, functions, and node +visitors. + +Creating an extension also makes for a better separation of code that is +executed at compilation time and code needed at runtime. As such, it makes +your code faster. + +Most of the time, it is useful to create a single extension for your project, +to host all the specific tags and filters you want to add to Twig. + +.. tip:: + + When packaging your code into an extension, Twig is smart enough to + recompile your templates whenever you make a change to it (when the + ``auto_reload`` is enabled). + +.. note:: + + Before writing your own extensions, have a look at the Twig official + extension repository: http://github.com/twigphp/Twig-extensions. + +An extension is a class that implements the following interface:: + + interface Twig_ExtensionInterface + { + /** + * Initializes the runtime environment. + * + * This is where you can load some file that contains filter functions for instance. + */ + function initRuntime(Twig_Environment $environment); + + /** + * Returns the token parser instances to add to the existing list. + * + * @return (Twig_TokenParserInterface|Twig_TokenParserBrokerInterface)[] + */ + function getTokenParsers(); + + /** + * Returns the node visitor instances to add to the existing list. + * + * @return Twig_NodeVisitorInterface[] + */ + function getNodeVisitors(); + + /** + * Returns a list of filters to add to the existing list. + * + * @return Twig_SimpleFilter[] + */ + function getFilters(); + + /** + * Returns a list of tests to add to the existing list. + * + * @return Twig_SimpleTest[] + */ + function getTests(); + + /** + * Returns a list of functions to add to the existing list. + * + * @return Twig_SimpleFunction[] + */ + function getFunctions(); + + /** + * Returns a list of operators to add to the existing list. + * + * @return array First array of unary operators, second array of binary operators + */ + function getOperators(); + + /** + * Returns a list of global variables to add to the existing list. + * + * @return array An array of global variables + */ + function getGlobals(); + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + function getName(); + } + +To keep your extension class clean and lean, it can inherit from the built-in +``Twig_Extension`` class instead of implementing the whole interface. That +way, you just need to implement the ``getName()`` method as the +``Twig_Extension`` provides empty implementations for all other methods. + +The ``getName()`` method must return a unique identifier for your extension. + +Now, with this information in mind, let's create the most basic extension +possible:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getName() + { + return 'project'; + } + } + +.. note:: + + Of course, this extension does nothing for now. We will customize it in + the next sections. + +Twig does not care where you save your extension on the filesystem, as all +extensions must be registered explicitly to be available in your templates. + +You can register an extension by using the ``addExtension()`` method on your +main ``Environment`` object:: + + $twig = new Twig_Environment($loader); + $twig->addExtension(new Project_Twig_Extension()); + +Of course, you need to first load the extension file by either using +``require_once()`` or by using an autoloader (see `spl_autoload_register()`_). + +.. tip:: + + The bundled extensions are great examples of how extensions work. + +Globals +~~~~~~~ + +Global variables can be registered in an extension via the ``getGlobals()`` +method:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getGlobals() + { + return array( + 'text' => new Text(), + ); + } + + // ... + } + +Functions +~~~~~~~~~ + +Functions can be registered in an extension via the ``getFunctions()`` +method:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getFunctions() + { + return array( + 'lipsum' => new Twig_Function_Function('generate_lipsum'), + ); + } + + // ... + } + +Filters +~~~~~~~ + +To add a filter to an extension, you need to override the ``getFilters()`` +method. This method must return an array of filters to add to the Twig +environment:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getFilters() + { + return array( + 'rot13' => new Twig_Filter_Function('str_rot13'), + ); + } + + // ... + } + +As you can see in the above code, the ``getFilters()`` method returns an array +where keys are the name of the filters (``rot13``) and the values the +definition of the filter (``new Twig_Filter_Function('str_rot13')``). + +As seen in the previous chapter, you can also define filters as static methods +on the extension class:: + +$twig->addFilter('rot13', new Twig_Filter_Function('Project_Twig_Extension::rot13Filter')); + +You can also use ``Twig_Filter_Method`` instead of ``Twig_Filter_Function`` +when defining a filter to use a method:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getFilters() + { + return array( + 'rot13' => new Twig_Filter_Method($this, 'rot13Filter'), + ); + } + + public function rot13Filter($string) + { + return str_rot13($string); + } + + // ... + } + +The first argument of the ``Twig_Filter_Method`` constructor is always +``$this``, the current extension object. The second one is the name of the +method to call. + +Using methods for filters is a great way to package your filter without +polluting the global namespace. This also gives the developer more flexibility +at the cost of a small overhead. + +Overriding default Filters +.......................... + +If some default core filters do not suit your needs, you can easily override +them by creating your own extension. Just use the same names as the one you +want to override:: + + class MyCoreExtension extends Twig_Extension + { + public function getFilters() + { + return array( + 'date' => new Twig_Filter_Method($this, 'dateFilter'), + // ... + ); + } + + public function dateFilter($timestamp, $format = 'F j, Y H:i') + { + return '...'.twig_date_format_filter($timestamp, $format); + } + + public function getName() + { + return 'project'; + } + } + +Here, we override the ``date`` filter with a custom one. Using this extension +is as simple as registering the ``MyCoreExtension`` extension by calling the +``addExtension()`` method on the environment instance:: + + $twig = new Twig_Environment($loader); + $twig->addExtension(new MyCoreExtension()); + +Tags +~~~~ + +Adding a tag in an extension can be done by overriding the +``getTokenParsers()`` method. This method must return an array of tags to add +to the Twig environment:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getTokenParsers() + { + return array(new Project_Set_TokenParser()); + } + + // ... + } + +In the above code, we have added a single new tag, defined by the +``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is +responsible for parsing the tag and compiling it to PHP. + +Operators +~~~~~~~~~ + +The ``getOperators()`` methods allows to add new operators. Here is how to add +``!``, ``||``, and ``&&`` operators:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getOperators() + { + return array( + array( + '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'), + ), + array( + '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + ), + ); + } + + // ... + } + +Tests +~~~~~ + +The ``getTests()`` methods allows to add new test functions:: + + class Project_Twig_Extension extends Twig_Extension + { + public function getTests() + { + return array( + 'even' => new Twig_Test_Function('twig_test_even'), + ); + } + + // ... + } + +Testing an Extension +-------------------- + +.. versionadded:: 1.10 + Support for functional tests was added in Twig 1.10. + +Functional Tests +~~~~~~~~~~~~~~~~ + +You can create functional tests for extensions simply by creating the +following file structure in your test directory:: + + Fixtures/ + filters/ + foo.test + bar.test + functions/ + foo.test + bar.test + tags/ + foo.test + bar.test + IntegrationTest.php + +The ``IntegrationTest.php`` file should look like this:: + + class Project_Tests_IntegrationTest extends Twig_Test_IntegrationTestCase + { + public function getExtensions() + { + return array( + new Project_Twig_Extension1(), + new Project_Twig_Extension2(), + ); + } + + public function getFixturesDir() + { + return dirname(__FILE__).'/Fixtures/'; + } + } + +Fixtures examples can be found within the Twig repository +`tests/Twig/Fixtures`_ directory. + +Node Tests +~~~~~~~~~~ + +Testing the node visitors can be complex, so extend your test cases from +``Twig_Test_NodeTestCase``. Examples can be found in the Twig repository +`tests/Twig/Node`_ directory. + +.. _`spl_autoload_register()`: http://www.php.net/spl_autoload_register +.. _`rot13`: http://www.php.net/manual/en/function.str-rot13.php +.. _`tests/Twig/Fixtures`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Fixtures +.. _`tests/Twig/Node`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Node diff --git a/src/vendor/twig/twig/doc/api.rst b/src/vendor/twig/twig/doc/api.rst new file mode 100644 index 0000000..8be2b8a --- /dev/null +++ b/src/vendor/twig/twig/doc/api.rst @@ -0,0 +1,590 @@ +Twig for Developers +=================== + +This chapter describes the API to Twig and not the template language. It will +be most useful as reference to those implementing the template interface to +the application and not those who are creating Twig templates. + +Basics +------ + +Twig uses a central object called the **environment** (of class +``Twig_Environment``). Instances of this class are used to store the +configuration and extensions, and are used to load templates from the file +system or other locations. + +Most applications will create one ``Twig_Environment`` object on application +initialization and use that to load templates. In some cases it's however +useful to have multiple environments side by side, if different configurations +are in use. + +The simplest way to configure Twig to load templates for your application +looks roughly like this:: + + require_once '/path/to/lib/Twig/Autoloader.php'; + Twig_Autoloader::register(); + + $loader = new Twig_Loader_Filesystem('/path/to/templates'); + $twig = new Twig_Environment($loader, array( + 'cache' => '/path/to/compilation_cache', + )); + +This will create a template environment with the default settings and a loader +that looks up the templates in the ``/path/to/templates/`` folder. Different +loaders are available and you can also write your own if you want to load +templates from a database or other resources. + +.. note:: + + Notice that the second argument of the environment is an array of options. + The ``cache`` option is a compilation cache directory, where Twig caches + the compiled templates to avoid the parsing phase for sub-sequent + requests. It is very different from the cache you might want to add for + the evaluated templates. For such a need, you can use any available PHP + cache library. + +Rendering Templates +------------------- + +To load a template from a Twig environment, call the ``load()`` method which +returns a ``Twig_TemplateWrapper`` instance:: + + $template = $twig->load('index.html'); + +.. note:: + + Before Twig 1.28, you should use ``loadTemplate()`` instead which returns a + ``Twig_Template`` instance. + +To render the template with some variables, call the ``render()`` method:: + + echo $template->render(array('the' => 'variables', 'go' => 'here')); + +.. note:: + + The ``display()`` method is a shortcut to output the template directly. + +You can also load and render the template in one fell swoop:: + + echo $twig->render('index.html', array('the' => 'variables', 'go' => 'here')); + +.. versionadded:: 1.28 + The possibility to render blocks from the API was added in Twig 1.28. + +If a template defines blocks, they can be rendered individually via the +``renderBlock()`` call:: + + echo $template->renderBlock('block_name', array('the' => 'variables', 'go' => 'here')); + +.. _environment_options: + +Environment Options +------------------- + +When creating a new ``Twig_Environment`` instance, you can pass an array of +options as the constructor second argument:: + + $twig = new Twig_Environment($loader, array('debug' => true)); + +The following options are available: + +* ``debug`` *boolean* + + When set to ``true``, the generated templates have a + ``__toString()`` method that you can use to display the generated nodes + (default to ``false``). + +* ``charset`` *string* (defaults to ``utf-8``) + + The charset used by the templates. + +* ``base_template_class`` *string* (defaults to ``Twig_Template``) + + The base template class to use for generated + templates. + +* ``cache`` *string* or ``false`` + + An absolute path where to store the compiled templates, or + ``false`` to disable caching (which is the default). + +* ``auto_reload`` *boolean* + + When developing with Twig, it's useful to recompile the + template whenever the source code changes. If you don't provide a value for + the ``auto_reload`` option, it will be determined automatically based on the + ``debug`` value. + +* ``strict_variables`` *boolean* + + If set to ``false``, Twig will silently ignore invalid + variables (variables and or attributes/methods that do not exist) and + replace them with a ``null`` value. When set to ``true``, Twig throws an + exception instead (default to ``false``). + +* ``autoescape`` *string* or *boolean* + + If set to ``true``, HTML auto-escaping will be enabled by + default for all templates (default to ``true``). + + As of Twig 1.8, you can set the escaping strategy to use (``html``, ``js``, + ``false`` to disable). + + As of Twig 1.9, you can set the escaping strategy to use (``css``, ``url``, + ``html_attr``, or a PHP callback that takes the template name and must + return the escaping strategy to use -- the callback cannot be a function name + to avoid collision with built-in escaping strategies). + + As of Twig 1.17, the ``filename`` escaping strategy (renamed to ``name`` as + of Twig 1.27) determines the escaping strategy to use for a template based on + the template filename extension (this strategy does not incur any overhead at + runtime as auto-escaping is done at compilation time.) + +* ``optimizations`` *integer* + + A flag that indicates which optimizations to apply + (default to ``-1`` -- all optimizations are enabled; set it to ``0`` to + disable). + +Loaders +------- + +Loaders are responsible for loading templates from a resource such as the file +system. + +Compilation Cache +~~~~~~~~~~~~~~~~~ + +All template loaders can cache the compiled templates on the filesystem for +future reuse. It speeds up Twig a lot as templates are only compiled once; and +the performance boost is even larger if you use a PHP accelerator such as APC. +See the ``cache`` and ``auto_reload`` options of ``Twig_Environment`` above +for more information. + +Built-in Loaders +~~~~~~~~~~~~~~~~ + +Here is a list of the built-in loaders Twig provides: + +``Twig_Loader_Filesystem`` +.......................... + +.. versionadded:: 1.10 + The ``prependPath()`` and support for namespaces were added in Twig 1.10. + +.. versionadded:: 1.27 + Relative paths support was added in Twig 1.27. + +``Twig_Loader_Filesystem`` loads templates from the file system. This loader +can find templates in folders on the file system and is the preferred way to +load them:: + + $loader = new Twig_Loader_Filesystem($templateDir); + +It can also look for templates in an array of directories:: + + $loader = new Twig_Loader_Filesystem(array($templateDir1, $templateDir2)); + +With such a configuration, Twig will first look for templates in +``$templateDir1`` and if they do not exist, it will fallback to look for them +in the ``$templateDir2``. + +You can add or prepend paths via the ``addPath()`` and ``prependPath()`` +methods:: + + $loader->addPath($templateDir3); + $loader->prependPath($templateDir4); + +The filesystem loader also supports namespaced templates. This allows to group +your templates under different namespaces which have their own template paths. + +When using the ``setPaths()``, ``addPath()``, and ``prependPath()`` methods, +specify the namespace as the second argument (when not specified, these +methods act on the "main" namespace):: + + $loader->addPath($templateDir, 'admin'); + +Namespaced templates can be accessed via the special +``@namespace_name/template_path`` notation:: + + $twig->render('@admin/index.html', array()); + +``Twig_Loader_Filesystem`` support absolute and relative paths. Using relative +paths is preferred as it makes the cache keys independent of the project root +directory (for instance, it allows warming the cache from a build server where +the directory might be different from the one used on production servers):: + + $loader = new Twig_Loader_Filesystem('templates', getcwd().'/..'); + +.. note:: + + When not passing the root path as a second argument, Twig uses ``getcwd()`` + for relative paths. + +``Twig_Loader_Array`` +..................... + +``Twig_Loader_Array`` loads a template from a PHP array. It's passed an array +of strings bound to template names:: + + $loader = new Twig_Loader_Array(array( + 'index.html' => 'Hello {{ name }}!', + )); + $twig = new Twig_Environment($loader); + + echo $twig->render('index.html', array('name' => 'Fabien')); + +This loader is very useful for unit testing. It can also be used for small +projects where storing all templates in a single PHP file might make sense. + +.. tip:: + + When using the ``Array`` or ``String`` loaders with a cache mechanism, you + should know that a new cache key is generated each time a template content + "changes" (the cache key being the source code of the template). If you + don't want to see your cache grows out of control, you need to take care + of clearing the old cache file by yourself. + +``Twig_Loader_Chain`` +..................... + +``Twig_Loader_Chain`` delegates the loading of templates to other loaders:: + + $loader1 = new Twig_Loader_Array(array( + 'base.html' => '{% block content %}{% endblock %}', + )); + $loader2 = new Twig_Loader_Array(array( + 'index.html' => '{% extends "base.html" %}{% block content %}Hello {{ name }}{% endblock %}', + 'base.html' => 'Will never be loaded', + )); + + $loader = new Twig_Loader_Chain(array($loader1, $loader2)); + + $twig = new Twig_Environment($loader); + +When looking for a template, Twig will try each loader in turn and it will +return as soon as the template is found. When rendering the ``index.html`` +template from the above example, Twig will load it with ``$loader2`` but the +``base.html`` template will be loaded from ``$loader1``. + +``Twig_Loader_Chain`` accepts any loader that implements +``Twig_LoaderInterface``. + +.. note:: + + You can also add loaders via the ``addLoader()`` method. + +Create your own Loader +~~~~~~~~~~~~~~~~~~~~~~ + +All loaders implement the ``Twig_LoaderInterface``:: + + interface Twig_LoaderInterface + { + /** + * Gets the source code of a template, given its name. + * + * @param string $name string The name of the template to load + * + * @return string The template source code + * + * @deprecated since 1.27 (to be removed in 2.0), implement Twig_SourceContextLoaderInterface + */ + function getSource($name); + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name string The name of the template to load + * + * @return string The cache key + */ + function getCacheKey($name); + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param timestamp $time The last modification time of the cached template + */ + function isFresh($name, $time); + } + +The ``isFresh()`` method must return ``true`` if the current cached template +is still fresh, given the last modification time, or ``false`` otherwise. + +.. note:: + + As of Twig 1.27, you should also implement + ``Twig_SourceContextLoaderInterface`` to avoid deprecation notices. + +.. tip:: + + As of Twig 1.11.0, you can also implement ``Twig_ExistsLoaderInterface`` + to make your loader faster when used with the chain loader. + +Using Extensions +---------------- + +Twig extensions are packages that add new features to Twig. Using an +extension is as simple as using the ``addExtension()`` method:: + + $twig->addExtension(new Twig_Extension_Sandbox()); + +Twig comes bundled with the following extensions: + +* *Twig_Extension_Core*: Defines all the core features of Twig. + +* *Twig_Extension_Escaper*: Adds automatic output-escaping and the possibility + to escape/unescape blocks of code. + +* *Twig_Extension_Sandbox*: Adds a sandbox mode to the default Twig + environment, making it safe to evaluate untrusted code. + +* *Twig_Extension_Profiler*: Enabled the built-in Twig profiler (as of Twig + 1.18). + +* *Twig_Extension_Optimizer*: Optimizes the node tree before compilation. + +The core, escaper, and optimizer extensions do not need to be added to the +Twig environment, as they are registered by default. + +Built-in Extensions +------------------- + +This section describes the features added by the built-in extensions. + +.. tip:: + + Read the chapter about extending Twig to learn how to create your own + extensions. + +Core Extension +~~~~~~~~~~~~~~ + +The ``core`` extension defines all the core features of Twig: + +* :doc:`Tags `; +* :doc:`Filters `; +* :doc:`Functions `; +* :doc:`Tests `. + +Escaper Extension +~~~~~~~~~~~~~~~~~ + +The ``escaper`` extension adds automatic output escaping to Twig. It defines a +tag, ``autoescape``, and a filter, ``raw``. + +When creating the escaper extension, you can switch on or off the global +output escaping strategy:: + + $escaper = new Twig_Extension_Escaper('html'); + $twig->addExtension($escaper); + +If set to ``html``, all variables in templates are escaped (using the ``html`` +escaping strategy), except those using the ``raw`` filter: + +.. code-block:: jinja + + {{ article.to_html|raw }} + +You can also change the escaping mode locally by using the ``autoescape`` tag +(see the :doc:`autoescape` doc for the syntax used before +Twig 1.8): + +.. code-block:: jinja + + {% autoescape 'html' %} + {{ var }} + {{ var|raw }} {# var won't be escaped #} + {{ var|escape }} {# var won't be double-escaped #} + {% endautoescape %} + +.. warning:: + + The ``autoescape`` tag has no effect on included files. + +The escaping rules are implemented as follows: + +* Literals (integers, booleans, arrays, ...) used in the template directly as + variables or filter arguments are never automatically escaped: + + .. code-block:: jinja + + {{ "Twig
" }} {# won't be escaped #} + + {% set text = "Twig
" %} + {{ text }} {# will be escaped #} + +* Expressions which the result is always a literal or a variable marked safe + are never automatically escaped: + + .. code-block:: jinja + + {{ foo ? "Twig
" : "
Twig" }} {# won't be escaped #} + + {% set text = "Twig
" %} + {{ foo ? text : "
Twig" }} {# will be escaped #} + + {% set text = "Twig
" %} + {{ foo ? text|raw : "
Twig" }} {# won't be escaped #} + + {% set text = "Twig
" %} + {{ foo ? text|escape : "
Twig" }} {# the result of the expression won't be escaped #} + +* Escaping is applied before printing, after any other filter is applied: + + .. code-block:: jinja + + {{ var|upper }} {# is equivalent to {{ var|upper|escape }} #} + +* The `raw` filter should only be used at the end of the filter chain: + + .. code-block:: jinja + + {{ var|raw|upper }} {# will be escaped #} + + {{ var|upper|raw }} {# won't be escaped #} + +* Automatic escaping is not applied if the last filter in the chain is marked + safe for the current context (e.g. ``html`` or ``js``). ``escape`` and + ``escape('html')`` are marked safe for HTML, ``escape('js')`` is marked + safe for JavaScript, ``raw`` is marked safe for everything. + + .. code-block:: jinja + + {% autoescape 'js' %} + {{ var|escape('html') }} {# will be escaped for HTML and JavaScript #} + {{ var }} {# will be escaped for JavaScript #} + {{ var|escape('js') }} {# won't be double-escaped #} + {% endautoescape %} + +.. note:: + + Note that autoescaping has some limitations as escaping is applied on + expressions after evaluation. For instance, when working with + concatenation, ``{{ foo|raw ~ bar }}`` won't give the expected result as + escaping is applied on the result of the concatenation, not on the + individual variables (so, the ``raw`` filter won't have any effect here). + +Sandbox Extension +~~~~~~~~~~~~~~~~~ + +The ``sandbox`` extension can be used to evaluate untrusted code. Access to +unsafe attributes and methods is prohibited. The sandbox security is managed +by a policy instance. By default, Twig comes with one policy class: +``Twig_Sandbox_SecurityPolicy``. This class allows you to white-list some +tags, filters, properties, and methods:: + + $tags = array('if'); + $filters = array('upper'); + $methods = array( + 'Article' => array('getTitle', 'getBody'), + ); + $properties = array( + 'Article' => array('title', 'body'), + ); + $functions = array('range'); + $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions); + +With the previous configuration, the security policy will only allow usage of +the ``if`` tag, and the ``upper`` filter. Moreover, the templates will only be +able to call the ``getTitle()`` and ``getBody()`` methods on ``Article`` +objects, and the ``title`` and ``body`` public properties. Everything else +won't be allowed and will generate a ``Twig_Sandbox_SecurityError`` exception. + +The policy object is the first argument of the sandbox constructor:: + + $sandbox = new Twig_Extension_Sandbox($policy); + $twig->addExtension($sandbox); + +By default, the sandbox mode is disabled and should be enabled when including +untrusted template code by using the ``sandbox`` tag: + +.. code-block:: jinja + + {% sandbox %} + {% include 'user.html' %} + {% endsandbox %} + +You can sandbox all templates by passing ``true`` as the second argument of +the extension constructor:: + + $sandbox = new Twig_Extension_Sandbox($policy, true); + +Profiler Extension +~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.18 + The Profile extension was added in Twig 1.18. + +The ``profiler`` extension enables a profiler for Twig templates; it should +only be used on your development machines as it adds some overhead:: + + $profile = new Twig_Profiler_Profile(); + $twig->addExtension(new Twig_Extension_Profiler($profile)); + + $dumper = new Twig_Profiler_Dumper_Text(); + echo $dumper->dump($profile); + +A profile contains information about time and memory consumption for template, +block, and macro executions. + +You can also dump the data in a `Blackfire.io `_ +compatible format:: + + $dumper = new Twig_Profiler_Dumper_Blackfire(); + file_put_contents('/path/to/profile.prof', $dumper->dump($profile)); + +Upload the profile to visualize it (create a `free account +`_ first): + +.. code-block:: sh + + blackfire --slot=7 upload /path/to/profile.prof + +Optimizer Extension +~~~~~~~~~~~~~~~~~~~ + +The ``optimizer`` extension optimizes the node tree before compilation:: + + $twig->addExtension(new Twig_Extension_Optimizer()); + +By default, all optimizations are turned on. You can select the ones you want +to enable by passing them to the constructor:: + + $optimizer = new Twig_Extension_Optimizer(Twig_NodeVisitor_Optimizer::OPTIMIZE_FOR); + + $twig->addExtension($optimizer); + +Twig supports the following optimizations: + +* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_ALL``, enables all optimizations + (this is the default value). +* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_NONE``, disables all optimizations. + This reduces the compilation time, but it can increase the execution time + and the consumed memory. +* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_FOR``, optimizes the ``for`` tag by + removing the ``loop`` variable creation whenever possible. +* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_RAW_FILTER``, removes the ``raw`` + filter whenever possible. +* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_VAR_ACCESS``, simplifies the creation + and access of variables in the compiled templates whenever possible. + +Exceptions +---------- + +Twig can throw exceptions: + +* ``Twig_Error``: The base exception for all errors. + +* ``Twig_Error_Syntax``: Thrown to tell the user that there is a problem with + the template syntax. + +* ``Twig_Error_Runtime``: Thrown when an error occurs at runtime (when a filter + does not exist for instance). + +* ``Twig_Error_Loader``: Thrown when an error occurs during template loading. + +* ``Twig_Sandbox_SecurityError``: Thrown when an unallowed tag, filter, or + method is called in a sandboxed template. diff --git a/src/vendor/twig/twig/doc/coding_standards.rst b/src/vendor/twig/twig/doc/coding_standards.rst new file mode 100644 index 0000000..bf8ea91 --- /dev/null +++ b/src/vendor/twig/twig/doc/coding_standards.rst @@ -0,0 +1,101 @@ +Coding Standards +================ + +When writing Twig templates, we recommend you to follow these official coding +standards: + +* Put one (and only one) space after the start of a delimiter (``{{``, ``{%``, + and ``{#``) and before the end of a delimiter (``}}``, ``%}``, and ``#}``): + + .. code-block:: jinja + + {{ foo }} + {# comment #} + {% if foo %}{% endif %} + + When using the whitespace control character, do not put any spaces between + it and the delimiter: + + .. code-block:: jinja + + {{- foo -}} + {#- comment -#} + {%- if foo -%}{%- endif -%} + +* Put one (and only one) space before and after the following operators: + comparison operators (``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``), math + operators (``+``, ``-``, ``/``, ``*``, ``%``, ``//``, ``**``), logic + operators (``not``, ``and``, ``or``), ``~``, ``is``, ``in``, and the ternary + operator (``?:``): + + .. code-block:: jinja + + {{ 1 + 2 }} + {{ foo ~ bar }} + {{ true ? true : false }} + +* Put one (and only one) space after the ``:`` sign in hashes and ``,`` in + arrays and hashes: + + .. code-block:: jinja + + {{ [1, 2, 3] }} + {{ {'foo': 'bar'} }} + +* Do not put any spaces after an opening parenthesis and before a closing + parenthesis in expressions: + + .. code-block:: jinja + + {{ 1 + (2 * 3) }} + +* Do not put any spaces before and after string delimiters: + + .. code-block:: jinja + + {{ 'foo' }} + {{ "foo" }} + +* Do not put any spaces before and after the following operators: ``|``, + ``.``, ``..``, ``[]``: + + .. code-block:: jinja + + {{ foo|upper|lower }} + {{ user.name }} + {{ user[name] }} + {% for i in 1..12 %}{% endfor %} + +* Do not put any spaces before and after the parenthesis used for filter and + function calls: + + .. code-block:: jinja + + {{ foo|default('foo') }} + {{ range(1..10) }} + +* Do not put any spaces before and after the opening and the closing of arrays + and hashes: + + .. code-block:: jinja + + {{ [1, 2, 3] }} + {{ {'foo': 'bar'} }} + +* Use lower cased and underscored variable names: + + .. code-block:: jinja + + {% set foo = 'foo' %} + {% set foo_bar = 'foo' %} + +* Indent your code inside tags (use the same indentation as the one used for + the target language of the rendered template): + + .. code-block:: jinja + + {% block foo %} + {% if true %} + true + {% endif %} + {% endblock %} diff --git a/src/vendor/twig/twig/doc/deprecated.rst b/src/vendor/twig/twig/doc/deprecated.rst new file mode 100644 index 0000000..3890c93 --- /dev/null +++ b/src/vendor/twig/twig/doc/deprecated.rst @@ -0,0 +1,214 @@ +Deprecated Features +=================== + +This document lists all deprecated features in Twig. Deprecated features are +kept for backward compatibility and removed in the next major release (a +feature that was deprecated in Twig 1.x is removed in Twig 2.0). + +Deprecation Notices +------------------- + +As of Twig 1.21, Twig generates deprecation notices when a template uses +deprecated features. See :ref:`deprecation-notices` for more information. + +Token Parsers +------------- + +* As of Twig 1.x, the token parser broker sub-system is deprecated. The + following class and interface will be removed in 2.0: + + * ``Twig_TokenParserBrokerInterface`` + * ``Twig_TokenParserBroker`` + +* As of Twig 1.27, ``Twig_Parser::getFilename()`` is deprecated. From a token + parser, use ``$this->parser->getStream()->getSourceContext()->getPath()`` instead. + +* As of Twig 1.27, ``Twig_Parser::getEnvironment()`` is deprecated. + +Extensions +---------- + +* As of Twig 1.x, the ability to remove an extension is deprecated and the + ``Twig_Environment::removeExtension()`` method will be removed in 2.0. + +* As of Twig 1.23, the ``Twig_ExtensionInterface::initRuntime()`` method is + deprecated. You have two options to avoid the deprecation notice: if you + implement this method to store the environment for your custom filters, + functions, or tests, use the ``needs_environment`` option instead; if you + have more complex needs, explicitly implement + ``Twig_Extension_InitRuntimeInterface`` (not recommended). + +* As of Twig 1.23, the ``Twig_ExtensionInterface::getGlobals()`` method is + deprecated. Implement ``Twig_Extension_GlobalsInterface`` to avoid + deprecation notices. + +* As of Twig 1.26, the ``Twig_ExtensionInterface::getName()`` method is + deprecated and it is not used internally anymore. + +PEAR +---- + +PEAR support has been discontinued in Twig 1.15.1, and no PEAR packages are +provided anymore. Use Composer instead. + +Filters +------- + +* As of Twig 1.x, use ``Twig_SimpleFilter`` to add a filter. The following + classes and interfaces will be removed in 2.0: + + * ``Twig_FilterInterface`` + * ``Twig_FilterCallableInterface`` + * ``Twig_Filter`` + * ``Twig_Filter_Function`` + * ``Twig_Filter_Method`` + * ``Twig_Filter_Node`` + +* As of Twig 2.x, the ``Twig_SimpleFilter`` class is deprecated and will be + removed in Twig 3.x (use ``Twig_Filter`` instead). In Twig 2.x, + ``Twig_SimpleFilter`` is just an alias for ``Twig_Filter``. + +Functions +--------- + +* As of Twig 1.x, use ``Twig_SimpleFunction`` to add a function. The following + classes and interfaces will be removed in 2.0: + + * ``Twig_FunctionInterface`` + * ``Twig_FunctionCallableInterface`` + * ``Twig_Function`` + * ``Twig_Function_Function`` + * ``Twig_Function_Method`` + * ``Twig_Function_Node`` + +* As of Twig 2.x, the ``Twig_SimpleFunction`` class is deprecated and will be + removed in Twig 3.x (use ``Twig_Function`` instead). In Twig 2.x, + ``Twig_SimpleFunction`` is just an alias for ``Twig_Function``. + +Tests +----- + +* As of Twig 1.x, use ``Twig_SimpleTest`` to add a test. The following classes + and interfaces will be removed in 2.0: + + * ``Twig_TestInterface`` + * ``Twig_TestCallableInterface`` + * ``Twig_Test`` + * ``Twig_Test_Function`` + * ``Twig_Test_Method`` + * ``Twig_Test_Node`` + +* As of Twig 2.x, the ``Twig_SimpleTest`` class is deprecated and will be + removed in Twig 3.x (use ``Twig_Test`` instead). In Twig 2.x, + ``Twig_SimpleTest`` is just an alias for ``Twig_Test``. + +* The ``sameas`` and ``divisibleby`` tests are deprecated in favor of ``same + as`` and ``divisible by`` respectively. + +Tags +---- + +* As of Twig 1.x, the ``raw`` tag is deprecated. You should use ``verbatim`` + instead. + +Nodes +----- + +* As of Twig 1.x, ``Node::toXml()`` is deprecated and will be removed in Twig + 2.0. + +* As of Twig 1.26, ``Node::$nodes`` should only contains ``Twig_Node`` + instances, storing a ``null`` value is deprecated and won't be possible in + Twig 2.x. + +* As of Twig 1.27, the ``filename`` attribute on ``Twig_Node_Module`` is + deprecated. Use ``getName()`` instead. + +* As of Twig 1.27, the ``Twig_Node::getFilename()/Twig_Node::getLine()`` + methods are deprecated, use + ``Twig_Node::getTemplateName()/Twig_Node::getTemplateLine()`` instead. + +Interfaces +---------- + +* As of Twig 2.x, the following interfaces are deprecated and empty (they will + be removed in Twig 3.0): + +* ``Twig_CompilerInterface`` (use ``Twig_Compiler`` instead) +* ``Twig_LexerInterface`` (use ``Twig_Lexer`` instead) +* ``Twig_NodeInterface`` (use ``Twig_Node`` instead) +* ``Twig_ParserInterface`` (use ``Twig_Parser`` instead) +* ``Twig_ExistsLoaderInterface`` (merged with ``Twig_LoaderInterface``) +* ``Twig_SourceContextLoaderInterface`` (merged with ``Twig_LoaderInterface``) +* ``Twig_TemplateInterface`` (use ``Twig_Template`` instead, and use + those constants Twig_Template::ANY_CALL, Twig_Template::ARRAY_CALL, + Twig_Template::METHOD_CALL) + +Compiler +-------- + +* As of Twig 1.26, the ``Twig_Compiler::getFilename()`` has been deprecated. + You should not use it anyway as its values is not reliable. + +* As of Twig 1.27, the ``Twig_Compiler::addIndentation()`` has been deprecated. + Use ``Twig_Compiler::write('')`` instead. + +Loaders +------- + +* As of Twig 1.x, ``Twig_Loader_String`` is deprecated and will be removed in + 2.0. You can render a string via ``Twig_Environment::createTemplate()``. + +* As of Twig 1.27, ``Twig_LoaderInterface::getSource()`` is deprecated. + Implement ``Twig_SourceContextLoaderInterface`` instead and use + ``getSourceContext()``. + +Node Visitors +------------- + +* Because of the removal of ``Twig_NodeInterface`` in 2.0, you need to extend + ``Twig_BaseNodeVisitor`` instead of implementing ``Twig_NodeVisitorInterface`` + directly to make your node visitors compatible with both Twig 1.x and 2.x. + +Globals +------- + +* As of Twig 2.x, the ability to register a global variable after the runtime + or the extensions have been initialized is not possible anymore (but + changing the value of an already registered global is possible). + +* As of Twig 1.x, using the ``_self`` global variable to get access to the + current ``Twig_Template`` instance is deprecated; most usages only need the + current template name, which will continue to work in Twig 2.0. In Twig 2.0, + ``_self`` returns the current template name instead of the current + ``Twig_Template`` instance. If you are using ``{{ _self.templateName }}``, + just replace it with ``{{ _self }}``. + +Miscellaneous +------------- + +* As of Twig 1.x, ``Twig_Environment::clearTemplateCache()``, + ``Twig_Environment::writeCacheFile()``, + ``Twig_Environment::clearCacheFiles()``, + ``Twig_Environment::getCacheFilename()``, + ``Twig_Environment::getTemplateClassPrefix()``, + ``Twig_Environment::getLexer()``, ``Twig_Environment::getParser()``, and + ``Twig_Environment::getCompiler()`` are deprecated and will be removed in 2.0. + +* As of Twig 1.x, ``Twig_Template::getEnvironment()`` and + ``Twig_TemplateInterface::getEnvironment()`` are deprecated and will be + removed in 2.0. + +* As of Twig 1.27, ``Twig_Error::getTemplateFile()`` and + ``Twig_Error::setTemplateFile()`` are deprecated. Use + ``Twig_Error::getTemplateName()`` and ``Twig_Error::setTemplateName()`` + instead. + +* As of Twig 1.27, ``Twig_Template::getSource()`` is deprecated. Use + ``Twig_Template::getSourceContext()`` instead. + +* As of Twig 1.27, ``Twig_Parser::addHandler()`` and + ``Twig_Parser::addNodeVisitor()`` are deprecated and will be removed in 2.0. + +* As of Twig 1.29, some classes are marked as being final via the `@final` + annotation. Those classes will be marked as final in 2.0. diff --git a/src/vendor/twig/twig/doc/filters/abs.rst b/src/vendor/twig/twig/doc/filters/abs.rst new file mode 100644 index 0000000..22fa59d --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/abs.rst @@ -0,0 +1,18 @@ +``abs`` +======= + +The ``abs`` filter returns the absolute value. + +.. code-block:: jinja + + {# number = -5 #} + + {{ number|abs }} + + {# outputs 5 #} + +.. note:: + + Internally, Twig uses the PHP `abs`_ function. + +.. _`abs`: http://php.net/abs diff --git a/src/vendor/twig/twig/doc/filters/batch.rst b/src/vendor/twig/twig/doc/filters/batch.rst new file mode 100644 index 0000000..f8b6fa9 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/batch.rst @@ -0,0 +1,51 @@ +``batch`` +========= + +.. versionadded:: 1.12.3 + The ``batch`` filter was added in Twig 1.12.3. + +The ``batch`` filter "batches" items by returning a list of lists with the +given number of items. A second parameter can be provided and used to fill in +missing items: + +.. code-block:: jinja + + {% set items = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] %} + + + {% for row in items|batch(3, 'No item') %} + + {% for column in row %} + + {% endfor %} + + {% endfor %} +
{{ column }}
+ +The above example will be rendered as: + +.. code-block:: jinja + + + + + + + + + + + + + + + + + +
abc
def
gNo itemNo item
+ +Arguments +--------- + +* ``size``: The size of the batch; fractional numbers will be rounded up +* ``fill``: Used to fill in missing items diff --git a/src/vendor/twig/twig/doc/filters/capitalize.rst b/src/vendor/twig/twig/doc/filters/capitalize.rst new file mode 100644 index 0000000..10546a1 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/capitalize.rst @@ -0,0 +1,11 @@ +``capitalize`` +============== + +The ``capitalize`` filter capitalizes a value. The first character will be +uppercase, all others lowercase: + +.. code-block:: jinja + + {{ 'my first car'|capitalize }} + + {# outputs 'My first car' #} diff --git a/src/vendor/twig/twig/doc/filters/convert_encoding.rst b/src/vendor/twig/twig/doc/filters/convert_encoding.rst new file mode 100644 index 0000000..f4ebe58 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/convert_encoding.rst @@ -0,0 +1,28 @@ +``convert_encoding`` +==================== + +.. versionadded:: 1.4 + The ``convert_encoding`` filter was added in Twig 1.4. + +The ``convert_encoding`` filter converts a string from one encoding to +another. The first argument is the expected output charset and the second one +is the input charset: + +.. code-block:: jinja + + {{ data|convert_encoding('UTF-8', 'iso-2022-jp') }} + +.. note:: + + This filter relies on the `iconv`_ or `mbstring`_ extension, so one of + them must be installed. In case both are installed, `mbstring`_ is used by + default (Twig before 1.8.1 uses `iconv`_ by default). + +Arguments +--------- + +* ``to``: The output charset +* ``from``: The input charset + +.. _`iconv`: http://php.net/iconv +.. _`mbstring`: http://php.net/mbstring diff --git a/src/vendor/twig/twig/doc/filters/date.rst b/src/vendor/twig/twig/doc/filters/date.rst new file mode 100644 index 0000000..99a17ab --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/date.rst @@ -0,0 +1,100 @@ +``date`` +======== + +.. versionadded:: 1.1 + The timezone support has been added in Twig 1.1. + +.. versionadded:: 1.5 + The default date format support has been added in Twig 1.5. + +.. versionadded:: 1.6.1 + The default timezone support has been added in Twig 1.6.1. + +.. versionadded:: 1.11.0 + The introduction of the false value for the timezone was introduced in Twig 1.11.0 + +The ``date`` filter formats a date to a given format: + +.. code-block:: jinja + + {{ post.published_at|date("m/d/Y") }} + +The format specifier is the same as supported by `date`_, +except when the filtered data is of type `DateInterval`_, when the format must conform to +`DateInterval::format`_ instead. + +The ``date`` filter accepts strings (it must be in a format supported by the +`strtotime`_ function), `DateTime`_ instances, or `DateInterval`_ instances. For +instance, to display the current date, filter the word "now": + +.. code-block:: jinja + + {{ "now"|date("m/d/Y") }} + +To escape words and characters in the date format use ``\\`` in front of each +character: + +.. code-block:: jinja + + {{ post.published_at|date("F jS \\a\\t g:ia") }} + +If the value passed to the ``date`` filter is ``null``, it will return the +current date by default. If an empty string is desired instead of the current +date, use a ternary operator: + +.. code-block:: jinja + + {{ post.published_at is empty ? "" : post.published_at|date("m/d/Y") }} + +If no format is provided, Twig will use the default one: ``F j, Y H:i``. This +default can be easily changed by calling the ``setDateFormat()`` method on the +``core`` extension instance. The first argument is the default format for +dates and the second one is the default format for date intervals: + +.. code-block:: php + + $twig = new Twig_Environment($loader); + $twig->getExtension('Twig_Extension_Core')->setDateFormat('d/m/Y', '%d days'); + + // before Twig 1.26 + $twig->getExtension('core')->setDateFormat('d/m/Y', '%d days'); + +Timezone +-------- + +By default, the date is displayed by applying the default timezone (the one +specified in php.ini or declared in Twig -- see below), but you can override +it by explicitly specifying a timezone: + +.. code-block:: jinja + + {{ post.published_at|date("m/d/Y", "Europe/Paris") }} + +If the date is already a DateTime object, and if you want to keep its current +timezone, pass ``false`` as the timezone value: + +.. code-block:: jinja + + {{ post.published_at|date("m/d/Y", false) }} + +The default timezone can also be set globally by calling ``setTimezone()``: + +.. code-block:: php + + $twig = new Twig_Environment($loader); + $twig->getExtension('Twig_Extension_Core')->setTimezone('Europe/Paris'); + + // before Twig 1.26 + $twig->getExtension('core')->setTimezone('Europe/Paris'); + +Arguments +--------- + +* ``format``: The date format +* ``timezone``: The date timezone + +.. _`strtotime`: http://www.php.net/strtotime +.. _`DateTime`: http://www.php.net/DateTime +.. _`DateInterval`: http://www.php.net/DateInterval +.. _`date`: http://www.php.net/date +.. _`DateInterval::format`: http://www.php.net/DateInterval.format diff --git a/src/vendor/twig/twig/doc/filters/date_modify.rst b/src/vendor/twig/twig/doc/filters/date_modify.rst new file mode 100644 index 0000000..add40b5 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/date_modify.rst @@ -0,0 +1,23 @@ +``date_modify`` +=============== + +.. versionadded:: 1.9.0 + The date_modify filter has been added in Twig 1.9.0. + +The ``date_modify`` filter modifies a date with a given modifier string: + +.. code-block:: jinja + + {{ post.published_at|date_modify("+1 day")|date("m/d/Y") }} + +The ``date_modify`` filter accepts strings (it must be in a format supported +by the `strtotime`_ function) or `DateTime`_ instances. You can easily combine +it with the :doc:`date` filter for formatting. + +Arguments +--------- + +* ``modifier``: The modifier + +.. _`strtotime`: http://www.php.net/strtotime +.. _`DateTime`: http://www.php.net/DateTime diff --git a/src/vendor/twig/twig/doc/filters/default.rst b/src/vendor/twig/twig/doc/filters/default.rst new file mode 100644 index 0000000..641ac6e --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/default.rst @@ -0,0 +1,33 @@ +``default`` +=========== + +The ``default`` filter returns the passed default value if the value is +undefined or empty, otherwise the value of the variable: + +.. code-block:: jinja + + {{ var|default('var is not defined') }} + + {{ var.foo|default('foo item on var is not defined') }} + + {{ var['foo']|default('foo item on var is not defined') }} + + {{ ''|default('passed var is empty') }} + +When using the ``default`` filter on an expression that uses variables in some +method calls, be sure to use the ``default`` filter whenever a variable can be +undefined: + +.. code-block:: jinja + + {{ var.method(foo|default('foo'))|default('foo') }} + +.. note:: + + Read the documentation for the :doc:`defined<../tests/defined>` and + :doc:`empty<../tests/empty>` tests to learn more about their semantics. + +Arguments +--------- + +* ``default``: The default value diff --git a/src/vendor/twig/twig/doc/filters/escape.rst b/src/vendor/twig/twig/doc/filters/escape.rst new file mode 100644 index 0000000..2149134 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/escape.rst @@ -0,0 +1,119 @@ +``escape`` +========== + +.. versionadded:: 1.9.0 + The ``css``, ``url``, and ``html_attr`` strategies were added in Twig + 1.9.0. + +.. versionadded:: 1.14.0 + The ability to define custom escapers was added in Twig 1.14.0. + +The ``escape`` filter escapes a string for safe insertion into the final +output. It supports different escaping strategies depending on the template +context. + +By default, it uses the HTML escaping strategy: + +.. code-block:: jinja + + {{ user.username|escape }} + +For convenience, the ``e`` filter is defined as an alias: + +.. code-block:: jinja + + {{ user.username|e }} + +The ``escape`` filter can also be used in other contexts than HTML thanks to +an optional argument which defines the escaping strategy to use: + +.. code-block:: jinja + + {{ user.username|e }} + {# is equivalent to #} + {{ user.username|e('html') }} + +And here is how to escape variables included in JavaScript code: + +.. code-block:: jinja + + {{ user.username|escape('js') }} + {{ user.username|e('js') }} + +The ``escape`` filter supports the following escaping strategies: + +* ``html``: escapes a string for the **HTML body** context. + +* ``js``: escapes a string for the **JavaScript context**. + +* ``css``: escapes a string for the **CSS context**. CSS escaping can be + applied to any string being inserted into CSS and escapes everything except + alphanumerics. + +* ``url``: escapes a string for the **URI or parameter contexts**. This should + not be used to escape an entire URI; only a subcomponent being inserted. + +* ``html_attr``: escapes a string for the **HTML attribute** context. + +.. note:: + + Internally, ``escape`` uses the PHP native `htmlspecialchars`_ function + for the HTML escaping strategy. + +.. caution:: + + When using automatic escaping, Twig tries to not double-escape a variable + when the automatic escaping strategy is the same as the one applied by the + escape filter; but that does not work when using a variable as the + escaping strategy: + + .. code-block:: jinja + + {% set strategy = 'html' %} + + {% autoescape 'html' %} + {{ var|escape('html') }} {# won't be double-escaped #} + {{ var|escape(strategy) }} {# will be double-escaped #} + {% endautoescape %} + + When using a variable as the escaping strategy, you should disable + automatic escaping: + + .. code-block:: jinja + + {% set strategy = 'html' %} + + {% autoescape 'html' %} + {{ var|escape(strategy)|raw }} {# won't be double-escaped #} + {% endautoescape %} + +Custom Escapers +--------------- + +You can define custom escapers by calling the ``setEscaper()`` method on the +``core`` extension instance. The first argument is the escaper name (to be +used in the ``escape`` call) and the second one must be a valid PHP callable: + +.. code-block:: php + + $twig = new Twig_Environment($loader); + $twig->getExtension('Twig_Extension_Core')->setEscaper('csv', 'csv_escaper'); + + // before Twig 1.26 + $twig->getExtension('core')->setEscaper('csv', 'csv_escaper'); + +When called by Twig, the callable receives the Twig environment instance, the +string to escape, and the charset. + +.. note:: + + Built-in escapers cannot be overridden mainly they should be considered as + the final implementation and also for better performance. + +Arguments +--------- + +* ``strategy``: The escaping strategy +* ``charset``: The string charset + +.. _`htmlspecialchars`: http://php.net/htmlspecialchars diff --git a/src/vendor/twig/twig/doc/filters/first.rst b/src/vendor/twig/twig/doc/filters/first.rst new file mode 100644 index 0000000..674c1f9 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/first.rst @@ -0,0 +1,25 @@ +``first`` +========= + +.. versionadded:: 1.12.2 + The ``first`` filter was added in Twig 1.12.2. + +The ``first`` filter returns the first "element" of a sequence, a mapping, or +a string: + +.. code-block:: jinja + + {{ [1, 2, 3, 4]|first }} + {# outputs 1 #} + + {{ { a: 1, b: 2, c: 3, d: 4 }|first }} + {# outputs 1 #} + + {{ '1234'|first }} + {# outputs 1 #} + +.. note:: + + It also works with objects implementing the `Traversable`_ interface. + +.. _`Traversable`: http://php.net/manual/en/class.traversable.php diff --git a/src/vendor/twig/twig/doc/filters/format.rst b/src/vendor/twig/twig/doc/filters/format.rst new file mode 100644 index 0000000..f8effd9 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/format.rst @@ -0,0 +1,16 @@ +``format`` +========== + +The ``format`` filter formats a given string by replacing the placeholders +(placeholders follows the `sprintf`_ notation): + +.. code-block:: jinja + + {{ "I like %s and %s."|format(foo, "bar") }} + + {# outputs I like foo and bar + if the foo parameter equals to the foo string. #} + +.. _`sprintf`: http://www.php.net/sprintf + +.. seealso:: :doc:`replace` diff --git a/src/vendor/twig/twig/doc/filters/index.rst b/src/vendor/twig/twig/doc/filters/index.rst new file mode 100644 index 0000000..8daa961 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/index.rst @@ -0,0 +1,37 @@ +Filters +======= + +.. toctree:: + :maxdepth: 1 + + abs + batch + capitalize + convert_encoding + date + date_modify + default + escape + first + format + join + json_encode + keys + last + length + lower + merge + nl2br + number_format + raw + replace + reverse + round + slice + sort + split + striptags + title + trim + upper + url_encode diff --git a/src/vendor/twig/twig/doc/filters/join.rst b/src/vendor/twig/twig/doc/filters/join.rst new file mode 100644 index 0000000..2fab945 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/join.rst @@ -0,0 +1,23 @@ +``join`` +======== + +The ``join`` filter returns a string which is the concatenation of the items +of a sequence: + +.. code-block:: jinja + + {{ [1, 2, 3]|join }} + {# returns 123 #} + +The separator between elements is an empty string per default, but you can +define it with the optional first parameter: + +.. code-block:: jinja + + {{ [1, 2, 3]|join('|') }} + {# outputs 1|2|3 #} + +Arguments +--------- + +* ``glue``: The separator diff --git a/src/vendor/twig/twig/doc/filters/json_encode.rst b/src/vendor/twig/twig/doc/filters/json_encode.rst new file mode 100644 index 0000000..a39bb47 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/json_encode.rst @@ -0,0 +1,21 @@ +``json_encode`` +=============== + +The ``json_encode`` filter returns the JSON representation of a value: + +.. code-block:: jinja + + {{ data|json_encode() }} + +.. note:: + + Internally, Twig uses the PHP `json_encode`_ function. + +Arguments +--------- + +* ``options``: A bitmask of `json_encode options`_ (``{{ + data|json_encode(constant('JSON_PRETTY_PRINT')) }}``) + +.. _`json_encode`: http://php.net/json_encode +.. _`json_encode options`: http://www.php.net/manual/en/json.constants.php diff --git a/src/vendor/twig/twig/doc/filters/keys.rst b/src/vendor/twig/twig/doc/filters/keys.rst new file mode 100644 index 0000000..e4f090c --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/keys.rst @@ -0,0 +1,11 @@ +``keys`` +======== + +The ``keys`` filter returns the keys of an array. It is useful when you want to +iterate over the keys of an array: + +.. code-block:: jinja + + {% for key in array|keys %} + ... + {% endfor %} diff --git a/src/vendor/twig/twig/doc/filters/last.rst b/src/vendor/twig/twig/doc/filters/last.rst new file mode 100644 index 0000000..345b657 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/last.rst @@ -0,0 +1,25 @@ +``last`` +======== + +.. versionadded:: 1.12.2 + The ``last`` filter was added in Twig 1.12.2. + +The ``last`` filter returns the last "element" of a sequence, a mapping, or +a string: + +.. code-block:: jinja + + {{ [1, 2, 3, 4]|last }} + {# outputs 4 #} + + {{ { a: 1, b: 2, c: 3, d: 4 }|last }} + {# outputs 4 #} + + {{ '1234'|last }} + {# outputs 4 #} + +.. note:: + + It also works with objects implementing the `Traversable`_ interface. + +.. _`Traversable`: http://php.net/manual/en/class.traversable.php diff --git a/src/vendor/twig/twig/doc/filters/length.rst b/src/vendor/twig/twig/doc/filters/length.rst new file mode 100644 index 0000000..1f783b3 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/length.rst @@ -0,0 +1,11 @@ +``length`` +========== + +The ``length`` filter returns the number of items of a sequence or mapping, or +the length of a string: + +.. code-block:: jinja + + {% if users|length > 10 %} + ... + {% endif %} diff --git a/src/vendor/twig/twig/doc/filters/lower.rst b/src/vendor/twig/twig/doc/filters/lower.rst new file mode 100644 index 0000000..ef9faa9 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/lower.rst @@ -0,0 +1,10 @@ +``lower`` +========= + +The ``lower`` filter converts a value to lowercase: + +.. code-block:: jinja + + {{ 'WELCOME'|lower }} + + {# outputs 'welcome' #} diff --git a/src/vendor/twig/twig/doc/filters/merge.rst b/src/vendor/twig/twig/doc/filters/merge.rst new file mode 100644 index 0000000..88780dd --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/merge.rst @@ -0,0 +1,48 @@ +``merge`` +========= + +The ``merge`` filter merges an array with another array: + +.. code-block:: jinja + + {% set values = [1, 2] %} + + {% set values = values|merge(['apple', 'orange']) %} + + {# values now contains [1, 2, 'apple', 'orange'] #} + +New values are added at the end of the existing ones. + +The ``merge`` filter also works on hashes: + +.. code-block:: jinja + + {% set items = { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'unknown' } %} + + {% set items = items|merge({ 'peugeot': 'car', 'renault': 'car' }) %} + + {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'renault': 'car' } #} + +For hashes, the merging process occurs on the keys: if the key does not +already exist, it is added but if the key already exists, its value is +overridden. + +.. tip:: + + If you want to ensure that some values are defined in an array (by given + default values), reverse the two elements in the call: + + .. code-block:: jinja + + {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %} + + {% set items = { 'apple': 'unknown' }|merge(items) %} + + {# items now contains { 'apple': 'fruit', 'orange': 'fruit' } #} + +.. note:: + + Internally, Twig uses the PHP `array_merge`_ function. It supports + Traversable objects by transforming those to arrays. + +.. _`array_merge`: http://php.net/array_merge diff --git a/src/vendor/twig/twig/doc/filters/nl2br.rst b/src/vendor/twig/twig/doc/filters/nl2br.rst new file mode 100644 index 0000000..5c923e1 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/nl2br.rst @@ -0,0 +1,22 @@ +``nl2br`` +========= + +.. versionadded:: 1.5 + The ``nl2br`` filter was added in Twig 1.5. + +The ``nl2br`` filter inserts HTML line breaks before all newlines in a string: + +.. code-block:: jinja + + {{ "I like Twig.\nYou will like it too."|nl2br }} + {# outputs + + I like Twig.
+ You will like it too. + + #} + +.. note:: + + The ``nl2br`` filter pre-escapes the input before applying the + transformation. diff --git a/src/vendor/twig/twig/doc/filters/number_format.rst b/src/vendor/twig/twig/doc/filters/number_format.rst new file mode 100644 index 0000000..f42ede1 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/number_format.rst @@ -0,0 +1,48 @@ +``number_format`` +================= + +.. versionadded:: 1.5 + The ``number_format`` filter was added in Twig 1.5 + +The ``number_format`` filter formats numbers. It is a wrapper around PHP's +`number_format`_ function: + +.. code-block:: jinja + + {{ 200.35|number_format }} + +You can control the number of decimal places, decimal point, and thousands +separator using the additional arguments: + +.. code-block:: jinja + + {{ 9800.333|number_format(2, '.', ',') }} + +If no formatting options are provided then Twig will use the default formatting +options of: + +* 0 decimal places. +* ``.`` as the decimal point. +* ``,`` as the thousands separator. + +These defaults can be easily changed through the core extension: + +.. code-block:: php + + $twig = new Twig_Environment($loader); + $twig->getExtension('Twig_Extension_Core')->setNumberFormat(3, '.', ','); + + // before Twig 1.26 + $twig->getExtension('core')->setNumberFormat(3, '.', ','); + +The defaults set for ``number_format`` can be over-ridden upon each call using the +additional parameters. + +Arguments +--------- + +* ``decimal``: The number of decimal points to display +* ``decimal_point``: The character(s) to use for the decimal point +* ``thousand_sep``: The character(s) to use for the thousands separator + +.. _`number_format`: http://php.net/number_format diff --git a/src/vendor/twig/twig/doc/filters/raw.rst b/src/vendor/twig/twig/doc/filters/raw.rst new file mode 100644 index 0000000..e5e5b12 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/raw.rst @@ -0,0 +1,36 @@ +``raw`` +======= + +The ``raw`` filter marks the value as being "safe", which means that in an +environment with automatic escaping enabled this variable will not be escaped +if ``raw`` is the last filter applied to it: + +.. code-block:: jinja + + {% autoescape %} + {{ var|raw }} {# var won't be escaped #} + {% endautoescape %} + +.. note:: + + Be careful when using the ``raw`` filter inside expressions: + + .. code-block:: jinja + + {% autoescape %} + {% set hello = 'Hello' %} + {% set hola = 'Hola' %} + + {{ false ? 'Hola' : hello|raw }} + does not render the same as + {{ false ? hola : hello|raw }} + but renders the same as + {{ (false ? hola : hello)|raw }} + {% endautoescape %} + + The first ternary statement is not escaped: ``hello`` is marked as being + safe and Twig does not escape static values (see + :doc:`escape<../tags/autoescape>`). In the second ternary statement, even + if ``hello`` is marked as safe, ``hola`` remains unsafe and so is the whole + expression. The third ternary statement is marked as safe and the result is + not escaped. diff --git a/src/vendor/twig/twig/doc/filters/replace.rst b/src/vendor/twig/twig/doc/filters/replace.rst new file mode 100644 index 0000000..1227957 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/replace.rst @@ -0,0 +1,19 @@ +``replace`` +=========== + +The ``replace`` filter formats a given string by replacing the placeholders +(placeholders are free-form): + +.. code-block:: jinja + + {{ "I like %this% and %that%."|replace({'%this%': foo, '%that%': "bar"}) }} + + {# outputs I like foo and bar + if the foo parameter equals to the foo string. #} + +Arguments +--------- + +* ``replace_pairs``: The placeholder values + +.. seealso:: :doc:`format` diff --git a/src/vendor/twig/twig/doc/filters/reverse.rst b/src/vendor/twig/twig/doc/filters/reverse.rst new file mode 100644 index 0000000..76fd2c1 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/reverse.rst @@ -0,0 +1,47 @@ +``reverse`` +=========== + +.. versionadded:: 1.6 + Support for strings has been added in Twig 1.6. + +The ``reverse`` filter reverses a sequence, a mapping, or a string: + +.. code-block:: jinja + + {% for user in users|reverse %} + ... + {% endfor %} + + {{ '1234'|reverse }} + + {# outputs 4321 #} + +.. tip:: + + For sequences and mappings, numeric keys are not preserved. To reverse + them as well, pass ``true`` as an argument to the ``reverse`` filter: + + .. code-block:: jinja + + {% for key, value in {1: "a", 2: "b", 3: "c"}|reverse %} + {{ key }}: {{ value }} + {%- endfor %} + + {# output: 0: c 1: b 2: a #} + + {% for key, value in {1: "a", 2: "b", 3: "c"}|reverse(true) %} + {{ key }}: {{ value }} + {%- endfor %} + + {# output: 3: c 2: b 1: a #} + +.. note:: + + It also works with objects implementing the `Traversable`_ interface. + +Arguments +--------- + +* ``preserve_keys``: Preserve keys when reversing a mapping or a sequence. + +.. _`Traversable`: http://php.net/Traversable diff --git a/src/vendor/twig/twig/doc/filters/round.rst b/src/vendor/twig/twig/doc/filters/round.rst new file mode 100644 index 0000000..2521cf1 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/round.rst @@ -0,0 +1,37 @@ +``round`` +========= + +.. versionadded:: 1.15.0 + The ``round`` filter was added in Twig 1.15.0. + +The ``round`` filter rounds a number to a given precision: + +.. code-block:: jinja + + {{ 42.55|round }} + {# outputs 43 #} + + {{ 42.55|round(1, 'floor') }} + {# outputs 42.5 #} + +The ``round`` filter takes two optional arguments; the first one specifies the +precision (default is ``0``) and the second the rounding method (default is +``common``): + +* ``common`` rounds either up or down (rounds the value up to precision decimal + places away from zero, when it is half way there -- making 1.5 into 2 and + -1.5 into -2); + +* ``ceil`` always rounds up; + +* ``floor`` always rounds down. + +.. note:: + + The ``//`` operator is equivalent to ``|round(0, 'floor')``. + +Arguments +--------- + +* ``precision``: The rounding precision +* ``method``: The rounding method diff --git a/src/vendor/twig/twig/doc/filters/slice.rst b/src/vendor/twig/twig/doc/filters/slice.rst new file mode 100644 index 0000000..70bf139 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/slice.rst @@ -0,0 +1,71 @@ +``slice`` +=========== + +.. versionadded:: 1.6 + The ``slice`` filter was added in Twig 1.6. + +The ``slice`` filter extracts a slice of a sequence, a mapping, or a string: + +.. code-block:: jinja + + {% for i in [1, 2, 3, 4, 5]|slice(1, 2) %} + {# will iterate over 2 and 3 #} + {% endfor %} + + {{ '12345'|slice(1, 2) }} + + {# outputs 23 #} + +You can use any valid expression for both the start and the length: + +.. code-block:: jinja + + {% for i in [1, 2, 3, 4, 5]|slice(start, length) %} + {# ... #} + {% endfor %} + +As syntactic sugar, you can also use the ``[]`` notation: + +.. code-block:: jinja + + {% for i in [1, 2, 3, 4, 5][start:length] %} + {# ... #} + {% endfor %} + + {{ '12345'[1:2] }} {# will display "23" #} + + {# you can omit the first argument -- which is the same as 0 #} + {{ '12345'[:2] }} {# will display "12" #} + + {# you can omit the last argument -- which will select everything till the end #} + {{ '12345'[2:] }} {# will display "345" #} + +The ``slice`` filter works as the `array_slice`_ PHP function for arrays and +`mb_substr`_ for strings with a fallback to `substr`_. + +If the start is non-negative, the sequence will start at that start in the +variable. If start is negative, the sequence will start that far from the end +of the variable. + +If length is given and is positive, then the sequence will have up to that +many elements in it. If the variable is shorter than the length, then only the +available variable elements will be present. If length is given and is +negative then the sequence will stop that many elements from the end of the +variable. If it is omitted, then the sequence will have everything from offset +up until the end of the variable. + +.. note:: + + It also works with objects implementing the `Traversable`_ interface. + +Arguments +--------- + +* ``start``: The start of the slice +* ``length``: The size of the slice +* ``preserve_keys``: Whether to preserve key or not (when the input is an array) + +.. _`Traversable`: http://php.net/manual/en/class.traversable.php +.. _`array_slice`: http://php.net/array_slice +.. _`mb_substr` : http://php.net/mb-substr +.. _`substr`: http://php.net/substr diff --git a/src/vendor/twig/twig/doc/filters/sort.rst b/src/vendor/twig/twig/doc/filters/sort.rst new file mode 100644 index 0000000..350207f --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/sort.rst @@ -0,0 +1,18 @@ +``sort`` +======== + +The ``sort`` filter sorts an array: + +.. code-block:: jinja + + {% for user in users|sort %} + ... + {% endfor %} + +.. note:: + + Internally, Twig uses the PHP `asort`_ function to maintain index + association. It supports Traversable objects by transforming + those to arrays. + +.. _`asort`: http://php.net/asort diff --git a/src/vendor/twig/twig/doc/filters/split.rst b/src/vendor/twig/twig/doc/filters/split.rst new file mode 100644 index 0000000..bbc6d79 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/split.rst @@ -0,0 +1,53 @@ +``split`` +========= + +.. versionadded:: 1.10.3 + The ``split`` filter was added in Twig 1.10.3. + +The ``split`` filter splits a string by the given delimiter and returns a list +of strings: + +.. code-block:: jinja + + {% set foo = "one,two,three"|split(',') %} + {# foo contains ['one', 'two', 'three'] #} + +You can also pass a ``limit`` argument: + + * If ``limit`` is positive, the returned array will contain a maximum of + limit elements with the last element containing the rest of string; + + * If ``limit`` is negative, all components except the last -limit are + returned; + + * If ``limit`` is zero, then this is treated as 1. + +.. code-block:: jinja + + {% set foo = "one,two,three,four,five"|split(',', 3) %} + {# foo contains ['one', 'two', 'three,four,five'] #} + +If the ``delimiter`` is an empty string, then value will be split by equal +chunks. Length is set by the ``limit`` argument (one character by default). + +.. code-block:: jinja + + {% set foo = "123"|split('') %} + {# foo contains ['1', '2', '3'] #} + + {% set bar = "aabbcc"|split('', 2) %} + {# bar contains ['aa', 'bb', 'cc'] #} + +.. note:: + + Internally, Twig uses the PHP `explode`_ or `str_split`_ (if delimiter is + empty) functions for string splitting. + +Arguments +--------- + +* ``delimiter``: The delimiter +* ``limit``: The limit argument + +.. _`explode`: http://php.net/explode +.. _`str_split`: http://php.net/str_split diff --git a/src/vendor/twig/twig/doc/filters/striptags.rst b/src/vendor/twig/twig/doc/filters/striptags.rst new file mode 100644 index 0000000..72c6f25 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/striptags.rst @@ -0,0 +1,15 @@ +``striptags`` +============= + +The ``striptags`` filter strips SGML/XML tags and replace adjacent whitespace +by one space: + +.. code-block:: jinja + + {{ some_html|striptags }} + +.. note:: + + Internally, Twig uses the PHP `strip_tags`_ function. + +.. _`strip_tags`: http://php.net/strip_tags diff --git a/src/vendor/twig/twig/doc/filters/title.rst b/src/vendor/twig/twig/doc/filters/title.rst new file mode 100644 index 0000000..c5a318e --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/title.rst @@ -0,0 +1,11 @@ +``title`` +========= + +The ``title`` filter returns a titlecased version of the value. Words will +start with uppercase letters, all remaining characters are lowercase: + +.. code-block:: jinja + + {{ 'my first car'|title }} + + {# outputs 'My First Car' #} diff --git a/src/vendor/twig/twig/doc/filters/trim.rst b/src/vendor/twig/twig/doc/filters/trim.rst new file mode 100644 index 0000000..4ddb208 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/trim.rst @@ -0,0 +1,29 @@ +``trim`` +======== + +.. versionadded:: 1.6.2 + The ``trim`` filter was added in Twig 1.6.2. + +The ``trim`` filter strips whitespace (or other characters) from the beginning +and end of a string: + +.. code-block:: jinja + + {{ ' I like Twig. '|trim }} + + {# outputs 'I like Twig.' #} + + {{ ' I like Twig.'|trim('.') }} + + {# outputs ' I like Twig' #} + +.. note:: + + Internally, Twig uses the PHP `trim`_ function. + +Arguments +--------- + +* ``character_mask``: The characters to strip + +.. _`trim`: http://php.net/trim diff --git a/src/vendor/twig/twig/doc/filters/upper.rst b/src/vendor/twig/twig/doc/filters/upper.rst new file mode 100644 index 0000000..561cebe --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/upper.rst @@ -0,0 +1,10 @@ +``upper`` +========= + +The ``upper`` filter converts a value to uppercase: + +.. code-block:: jinja + + {{ 'welcome'|upper }} + + {# outputs 'WELCOME' #} diff --git a/src/vendor/twig/twig/doc/filters/url_encode.rst b/src/vendor/twig/twig/doc/filters/url_encode.rst new file mode 100644 index 0000000..5944e59 --- /dev/null +++ b/src/vendor/twig/twig/doc/filters/url_encode.rst @@ -0,0 +1,34 @@ +``url_encode`` +============== + +.. versionadded:: 1.12.3 + Support for encoding an array as query string was added in Twig 1.12.3. + +.. versionadded:: 1.16.0 + The ``raw`` argument was removed in Twig 1.16.0. Twig now always encodes + according to RFC 3986. + +The ``url_encode`` filter percent encodes a given string as URL segment +or an array as query string: + +.. code-block:: jinja + + {{ "path-seg*ment"|url_encode }} + {# outputs "path-seg%2Ament" #} + + {{ "string with spaces"|url_encode }} + {# outputs "string%20with%20spaces" #} + + {{ {'param': 'value', 'foo': 'bar'}|url_encode }} + {# outputs "param=value&foo=bar" #} + +.. note:: + + Internally, Twig uses the PHP `urlencode`_ (or `rawurlencode`_ if you pass + ``true`` as the first parameter) or the `http_build_query`_ function. Note + that as of Twig 1.16.0, ``urlencode`` **always** uses ``rawurlencode`` (the + ``raw`` argument was removed.) + +.. _`urlencode`: http://php.net/urlencode +.. _`rawurlencode`: http://php.net/rawurlencode +.. _`http_build_query`: http://php.net/http_build_query diff --git a/src/vendor/twig/twig/doc/functions/attribute.rst b/src/vendor/twig/twig/doc/functions/attribute.rst new file mode 100644 index 0000000..ceba96b --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/attribute.rst @@ -0,0 +1,26 @@ +``attribute`` +============= + +.. versionadded:: 1.2 + The ``attribute`` function was added in Twig 1.2. + +The ``attribute`` function can be used to access a "dynamic" attribute of a +variable: + +.. code-block:: jinja + + {{ attribute(object, method) }} + {{ attribute(object, method, arguments) }} + {{ attribute(array, item) }} + +In addition, the ``defined`` test can check for the existence of a dynamic +attribute: + +.. code-block:: jinja + + {{ attribute(object, method) is defined ? 'Method exists' : 'Method does not exist' }} + +.. note:: + + The resolution algorithm is the same as the one used for the ``.`` + notation, except that the item can be any valid expression. diff --git a/src/vendor/twig/twig/doc/functions/block.rst b/src/vendor/twig/twig/doc/functions/block.rst new file mode 100644 index 0000000..f5d683c --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/block.rst @@ -0,0 +1,41 @@ +``block`` +========= + +.. versionadded: 1.28 + Using ``block`` with the ``defined`` test was added in Twig 1.28. + +.. versionadded: 1.28 + Support for the template argument was added in Twig 1.28. + +When a template uses inheritance and if you want to print a block multiple +times, use the ``block`` function: + +.. code-block:: jinja + + {% block title %}{% endblock %} + +

{{ block('title') }}

+ + {% block body %}{% endblock %} + +The ``block`` function can also be used to display one block of another +template: + +.. code-block:: jinja + + {{ block("title", "common_blocks.twig") }} + +Use the ``defined`` test to check if a block exists in the context of the +current template: + +.. code-block:: jinja + + {% if block("footer") is defined %} + ... + {% endif %} + + {% if block("footer", "common_blocks.twig") is defined %} + ... + {% endif %} + +.. seealso:: :doc:`extends<../tags/extends>`, :doc:`parent<../functions/parent>` diff --git a/src/vendor/twig/twig/doc/functions/constant.rst b/src/vendor/twig/twig/doc/functions/constant.rst new file mode 100644 index 0000000..97aa5c8 --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/constant.rst @@ -0,0 +1,29 @@ +``constant`` +============ + +.. versionadded: 1.12.1 + constant now accepts object instances as the second argument. + +.. versionadded: 1.28 + Using ``constant`` with the ``defined`` test was added in Twig 1.28. + +``constant`` returns the constant value for a given string: + +.. code-block:: jinja + + {{ some_date|date(constant('DATE_W3C')) }} + {{ constant('Namespace\\Classname::CONSTANT_NAME') }} + +As of 1.12.1 you can read constants from object instances as well: + +.. code-block:: jinja + + {{ constant('RSS', date) }} + +Use the ``defined`` test to check if a constant is defined: + +.. code-block:: jinja + + {% if constant('SOME_CONST') is defined %} + ... + {% endif %} diff --git a/src/vendor/twig/twig/doc/functions/cycle.rst b/src/vendor/twig/twig/doc/functions/cycle.rst new file mode 100644 index 0000000..e343493 --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/cycle.rst @@ -0,0 +1,28 @@ +``cycle`` +========= + +The ``cycle`` function cycles on an array of values: + +.. code-block:: jinja + + {% set start_year = date() | date('Y') %} + {% set end_year = start_year + 5 %} + + {% for year in start_year..end_year %} + {{ cycle(['odd', 'even'], loop.index0) }} + {% endfor %} + +The array can contain any number of values: + +.. code-block:: jinja + + {% set fruits = ['apple', 'orange', 'citrus'] %} + + {% for i in 0..10 %} + {{ cycle(fruits, i) }} + {% endfor %} + +Arguments +--------- + +* ``position``: The cycle position diff --git a/src/vendor/twig/twig/doc/functions/date.rst b/src/vendor/twig/twig/doc/functions/date.rst new file mode 100644 index 0000000..158dd6a --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/date.rst @@ -0,0 +1,55 @@ +``date`` +======== + +.. versionadded:: 1.6 + The date function has been added in Twig 1.6. + +.. versionadded:: 1.6.1 + The default timezone support has been added in Twig 1.6.1. + +Converts an argument to a date to allow date comparison: + +.. code-block:: jinja + + {% if date(user.created_at) < date('-2days') %} + {# do something #} + {% endif %} + +The argument must be in one of PHP’s supported `date and time formats`_. + +You can pass a timezone as the second argument: + +.. code-block:: jinja + + {% if date(user.created_at) < date('-2days', 'Europe/Paris') %} + {# do something #} + {% endif %} + +If no argument is passed, the function returns the current date: + +.. code-block:: jinja + + {% if date(user.created_at) < date() %} + {# always! #} + {% endif %} + +.. note:: + + You can set the default timezone globally by calling ``setTimezone()`` on + the ``core`` extension instance: + + .. code-block:: php + + $twig = new Twig_Environment($loader); + $twig->getExtension('Twig_Extension_Core')->setTimezone('Europe/Paris'); + + // before Twig 1.26 + $twig->getExtension('core')->setTimezone('Europe/Paris'); + +Arguments +--------- + +* ``date``: The date +* ``timezone``: The timezone + +.. _`date and time formats`: http://php.net/manual/en/datetime.formats.php diff --git a/src/vendor/twig/twig/doc/functions/dump.rst b/src/vendor/twig/twig/doc/functions/dump.rst new file mode 100644 index 0000000..a231f08 --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/dump.rst @@ -0,0 +1,69 @@ +``dump`` +======== + +.. versionadded:: 1.5 + The ``dump`` function was added in Twig 1.5. + +The ``dump`` function dumps information about a template variable. This is +mostly useful to debug a template that does not behave as expected by +introspecting its variables: + +.. code-block:: jinja + + {{ dump(user) }} + +.. note:: + + The ``dump`` function is not available by default. You must add the + ``Twig_Extension_Debug`` extension explicitly when creating your Twig + environment:: + + $twig = new Twig_Environment($loader, array( + 'debug' => true, + // ... + )); + $twig->addExtension(new Twig_Extension_Debug()); + + Even when enabled, the ``dump`` function won't display anything if the + ``debug`` option on the environment is not enabled (to avoid leaking debug + information on a production server). + +In an HTML context, wrap the output with a ``pre`` tag to make it easier to +read: + +.. code-block:: jinja + +
+        {{ dump(user) }}
+    
+ +.. tip:: + + Using a ``pre`` tag is not needed when `XDebug`_ is enabled and + ``html_errors`` is ``on``; as a bonus, the output is also nicer with + XDebug enabled. + +You can debug several variables by passing them as additional arguments: + +.. code-block:: jinja + + {{ dump(user, categories) }} + +If you don't pass any value, all variables from the current context are +dumped: + +.. code-block:: jinja + + {{ dump() }} + +.. note:: + + Internally, Twig uses the PHP `var_dump`_ function. + +Arguments +--------- + +* ``context``: The context to dump + +.. _`XDebug`: http://xdebug.org/docs/display +.. _`var_dump`: http://php.net/var_dump diff --git a/src/vendor/twig/twig/doc/functions/include.rst b/src/vendor/twig/twig/doc/functions/include.rst new file mode 100644 index 0000000..2f88ed7 --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/include.rst @@ -0,0 +1,84 @@ +``include`` +=========== + +.. versionadded:: 1.12 + The ``include`` function was added in Twig 1.12. + +The ``include`` function returns the rendered content of a template: + +.. code-block:: jinja + + {{ include('template.html') }} + {{ include(some_var) }} + +Included templates have access to the variables of the active context. + +If you are using the filesystem loader, the templates are looked for in the +paths defined by it. + +The context is passed by default to the template but you can also pass +additional variables: + +.. code-block:: jinja + + {# template.html will have access to the variables from the current context and the additional ones provided #} + {{ include('template.html', {foo: 'bar'}) }} + +You can disable access to the context by setting ``with_context`` to +``false``: + +.. code-block:: jinja + + {# only the foo variable will be accessible #} + {{ include('template.html', {foo: 'bar'}, with_context = false) }} + +.. code-block:: jinja + + {# no variables will be accessible #} + {{ include('template.html', with_context = false) }} + +And if the expression evaluates to a ``Twig_Template`` or a +``Twig_TemplateWrapper`` instance, Twig will use it directly:: + + // {{ include(template) }} + + // deprecated as of Twig 1.28 + $template = $twig->loadTemplate('some_template.twig'); + + // as of Twig 1.28 + $template = $twig->load('some_template.twig'); + + $twig->display('template.twig', array('template' => $template)); + +When you set the ``ignore_missing`` flag, Twig will return an empty string if +the template does not exist: + +.. code-block:: jinja + + {{ include('sidebar.html', ignore_missing = true) }} + +You can also provide a list of templates that are checked for existence before +inclusion. The first template that exists will be rendered: + +.. code-block:: jinja + + {{ include(['page_detailed.html', 'page.html']) }} + +If ``ignore_missing`` is set, it will fall back to rendering nothing if none +of the templates exist, otherwise it will throw an exception. + +When including a template created by an end user, you should consider +sandboxing it: + +.. code-block:: jinja + + {{ include('page.html', sandboxed = true) }} + +Arguments +--------- + +* ``template``: The template to render +* ``variables``: The variables to pass to the template +* ``with_context``: Whether to pass the current context variables or not +* ``ignore_missing``: Whether to ignore missing templates or not +* ``sandboxed``: Whether to sandbox the template or not diff --git a/src/vendor/twig/twig/doc/functions/index.rst b/src/vendor/twig/twig/doc/functions/index.rst new file mode 100644 index 0000000..07214a7 --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/index.rst @@ -0,0 +1,20 @@ +Functions +========= + +.. toctree:: + :maxdepth: 1 + + attribute + block + constant + cycle + date + dump + include + max + min + parent + random + range + source + template_from_string diff --git a/src/vendor/twig/twig/doc/functions/max.rst b/src/vendor/twig/twig/doc/functions/max.rst new file mode 100644 index 0000000..6f3cfc5 --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/max.rst @@ -0,0 +1,20 @@ +``max`` +======= + +.. versionadded:: 1.15 + The ``max`` function was added in Twig 1.15. + +``max`` returns the biggest value of a sequence or a set of values: + +.. code-block:: jinja + + {{ max(1, 3, 2) }} + {{ max([1, 3, 2]) }} + +When called with a mapping, max ignores keys and only compares values: + +.. code-block:: jinja + + {{ max({2: "e", 1: "a", 3: "b", 5: "d", 4: "c"}) }} + {# returns "e" #} + diff --git a/src/vendor/twig/twig/doc/functions/min.rst b/src/vendor/twig/twig/doc/functions/min.rst new file mode 100644 index 0000000..7b6a65e --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/min.rst @@ -0,0 +1,20 @@ +``min`` +======= + +.. versionadded:: 1.15 + The ``min`` function was added in Twig 1.15. + +``min`` returns the lowest value of a sequence or a set of values: + +.. code-block:: jinja + + {{ min(1, 3, 2) }} + {{ min([1, 3, 2]) }} + +When called with a mapping, min ignores keys and only compares values: + +.. code-block:: jinja + + {{ min({2: "e", 3: "a", 1: "b", 5: "d", 4: "c"}) }} + {# returns "a" #} + diff --git a/src/vendor/twig/twig/doc/functions/parent.rst b/src/vendor/twig/twig/doc/functions/parent.rst new file mode 100644 index 0000000..f5bd200 --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/parent.rst @@ -0,0 +1,20 @@ +``parent`` +========== + +When a template uses inheritance, it's possible to render the contents of the +parent block when overriding a block by using the ``parent`` function: + +.. code-block:: jinja + + {% extends "base.html" %} + + {% block sidebar %} +

Table Of Contents

+ ... + {{ parent() }} + {% endblock %} + +The ``parent()`` call will return the content of the ``sidebar`` block as +defined in the ``base.html`` template. + +.. seealso:: :doc:`extends<../tags/extends>`, :doc:`block<../functions/block>`, :doc:`block<../tags/block>` diff --git a/src/vendor/twig/twig/doc/functions/random.rst b/src/vendor/twig/twig/doc/functions/random.rst new file mode 100644 index 0000000..168e74f --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/random.rst @@ -0,0 +1,29 @@ +``random`` +========== + +.. versionadded:: 1.5 + The ``random`` function was added in Twig 1.5. + +.. versionadded:: 1.6 + String and integer handling was added in Twig 1.6. + +The ``random`` function returns a random value depending on the supplied +parameter type: + +* a random item from a sequence; +* a random character from a string; +* a random integer between 0 and the integer parameter (inclusive). + +.. code-block:: jinja + + {{ random(['apple', 'orange', 'citrus']) }} {# example output: orange #} + {{ random('ABC') }} {# example output: C #} + {{ random() }} {# example output: 15386094 (works as the native PHP mt_rand function) #} + {{ random(5) }} {# example output: 3 #} + +Arguments +--------- + +* ``values``: The values + +.. _`mt_rand`: http://php.net/mt_rand diff --git a/src/vendor/twig/twig/doc/functions/range.rst b/src/vendor/twig/twig/doc/functions/range.rst new file mode 100644 index 0000000..b7cd011 --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/range.rst @@ -0,0 +1,45 @@ +``range`` +========= + +Returns a list containing an arithmetic progression of integers: + +.. code-block:: jinja + + {% for i in range(0, 3) %} + {{ i }}, + {% endfor %} + + {# outputs 0, 1, 2, 3, #} + +When step is given (as the third parameter), it specifies the increment (or +decrement): + +.. code-block:: jinja + + {% for i in range(0, 6, 2) %} + {{ i }}, + {% endfor %} + + {# outputs 0, 2, 4, 6, #} + +The Twig built-in ``..`` operator is just syntactic sugar for the ``range`` +function (with a step of 1): + +.. code-block:: jinja + + {% for i in 0..3 %} + {{ i }}, + {% endfor %} + +.. tip:: + + The ``range`` function works as the native PHP `range`_ function. + +Arguments +--------- + +* ``low``: The first value of the sequence. +* ``high``: The highest possible value of the sequence. +* ``step``: The increment between elements of the sequence. + +.. _`range`: http://php.net/range diff --git a/src/vendor/twig/twig/doc/functions/source.rst b/src/vendor/twig/twig/doc/functions/source.rst new file mode 100644 index 0000000..3c921b1 --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/source.rst @@ -0,0 +1,32 @@ +``source`` +========== + +.. versionadded:: 1.15 + The ``source`` function was added in Twig 1.15. + +.. versionadded:: 1.18.3 + The ``ignore_missing`` flag was added in Twig 1.18.3. + +The ``source`` function returns the content of a template without rendering it: + +.. code-block:: jinja + + {{ source('template.html') }} + {{ source(some_var) }} + +When you set the ``ignore_missing`` flag, Twig will return an empty string if +the template does not exist: + +.. code-block:: jinja + + {{ source('template.html', ignore_missing = true) }} + +The function uses the same template loaders as the ones used to include +templates. So, if you are using the filesystem loader, the templates are looked +for in the paths defined by it. + +Arguments +--------- + +* ``name``: The name of the template to read +* ``ignore_missing``: Whether to ignore missing templates or not diff --git a/src/vendor/twig/twig/doc/functions/template_from_string.rst b/src/vendor/twig/twig/doc/functions/template_from_string.rst new file mode 100644 index 0000000..ce6a60d --- /dev/null +++ b/src/vendor/twig/twig/doc/functions/template_from_string.rst @@ -0,0 +1,32 @@ +``template_from_string`` +======================== + +.. versionadded:: 1.11 + The ``template_from_string`` function was added in Twig 1.11. + +The ``template_from_string`` function loads a template from a string: + +.. code-block:: jinja + + {{ include(template_from_string("Hello {{ name }}")) }} + {{ include(template_from_string(page.template)) }} + +.. note:: + + The ``template_from_string`` function is not available by default. You + must add the ``Twig_Extension_StringLoader`` extension explicitly when + creating your Twig environment:: + + $twig = new Twig_Environment(...); + $twig->addExtension(new Twig_Extension_StringLoader()); + +.. note:: + + Even if you will probably always use the ``template_from_string`` function + with the ``include`` function, you can use it with any tag or function that + takes a template as an argument (like the ``embed`` or ``extends`` tags). + +Arguments +--------- + +* ``template``: The template diff --git a/src/vendor/twig/twig/doc/index.rst b/src/vendor/twig/twig/doc/index.rst new file mode 100644 index 0000000..358bd73 --- /dev/null +++ b/src/vendor/twig/twig/doc/index.rst @@ -0,0 +1,19 @@ +Twig +==== + +.. toctree:: + :maxdepth: 2 + + intro + installation + templates + api + advanced + internals + deprecated + recipes + coding_standards + tags/index + filters/index + functions/index + tests/index diff --git a/src/vendor/twig/twig/doc/installation.rst b/src/vendor/twig/twig/doc/installation.rst new file mode 100644 index 0000000..afdcf16 --- /dev/null +++ b/src/vendor/twig/twig/doc/installation.rst @@ -0,0 +1,116 @@ +Installation +============ + +You have multiple ways to install Twig. + +Installing the Twig PHP package +------------------------------- + +Installing via Composer (recommended) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Install `Composer`_ and run the following command to get the latest version: + +.. code-block:: bash + + composer require twig/twig:~1.0 + +Installing from the tarball release +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Download the most recent tarball from the `download page`_ +2. Verify the integrity of the tarball http://fabien.potencier.org/article/73/signing-project-releases +3. Unpack the tarball +4. Move the files somewhere in your project + +Installing the development version +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + git clone git://github.com/twigphp/Twig.git + +Installing the PEAR package +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. note:: + + Using PEAR for installing Twig is deprecated and Twig 1.15.1 was the last + version published on the PEAR channel; use Composer instead. + +.. code-block:: bash + + pear channel-discover pear.twig-project.org + pear install twig/Twig + +Installing the C extension +-------------------------- + +.. versionadded:: 1.4 + The C extension was added in Twig 1.4. + +.. note:: + + The C extension is **optional** but it brings some nice performance + improvements. Note that the extension is not a replacement for the PHP + code; it only implements a small part of the PHP code to improve the + performance at runtime; you must still install the regular PHP code. + +Twig comes with a C extension that enhances the performance of the Twig +runtime engine; install it like any other PHP extensions: + +.. code-block:: bash + + cd ext/twig + phpize + ./configure + make + make install + +.. note:: + + You can also install the C extension via PEAR (note that this method is + deprecated and newer versions of Twig are not available on the PEAR + channel): + + .. code-block:: bash + + pear channel-discover pear.twig-project.org + pear install twig/CTwig + +For Windows: + +1. Setup the build environment following the `PHP documentation`_ +2. Put Twig's C extension source code into ``C:\php-sdk\phpdev\vcXX\x86\php-source-directory\ext\twig`` +3. Use the ``configure --disable-all --enable-cli --enable-twig=shared`` command instead of step 14 +4. ``nmake`` +5. Copy the ``C:\php-sdk\phpdev\vcXX\x86\php-source-directory\Release_TS\php_twig.dll`` file to your PHP setup. + +.. tip:: + + For Windows ZendServer, ZTS is not enabled as mentioned in `Zend Server + FAQ`_. + + You have to use ``configure --disable-all --disable-zts --enable-cli + --enable-twig=shared`` to be able to build the twig C extension for + ZendServer. + + The built DLL will be available in + ``C:\\php-sdk\\phpdev\\vcXX\\x86\\php-source-directory\\Release`` + +Finally, enable the extension in your ``php.ini`` configuration file: + +.. code-block:: ini + + extension=twig.so #For Unix systems + extension=php_twig.dll #For Windows systems + +And from now on, Twig will automatically compile your templates to take +advantage of the C extension. Note that this extension does not replace the +PHP code but only provides an optimized version of the +``Twig_Template::getAttribute()`` method. + +.. _`download page`: https://github.com/twigphp/Twig/tags +.. _`Composer`: https://getcomposer.org/download/ +.. _`PHP documentation`: https://wiki.php.net/internals/windows/stepbystepbuild +.. _`Zend Server FAQ`: http://www.zend.com/en/products/server/faq#faqD6 diff --git a/src/vendor/twig/twig/doc/internals.rst b/src/vendor/twig/twig/doc/internals.rst new file mode 100644 index 0000000..f38089f --- /dev/null +++ b/src/vendor/twig/twig/doc/internals.rst @@ -0,0 +1,142 @@ +Twig Internals +============== + +Twig is very extensible and you can easily hack it. Keep in mind that you +should probably try to create an extension before hacking the core, as most +features and enhancements can be handled with extensions. This chapter is also +useful for people who want to understand how Twig works under the hood. + +How does Twig work? +------------------- + +The rendering of a Twig template can be summarized into four key steps: + +* **Load** the template: If the template is already compiled, load it and go + to the *evaluation* step, otherwise: + + * First, the **lexer** tokenizes the template source code into small pieces + for easier processing; + * Then, the **parser** converts the token stream into a meaningful tree + of nodes (the Abstract Syntax Tree); + * Eventually, the *compiler* transforms the AST into PHP code. + +* **Evaluate** the template: It basically means calling the ``display()`` + method of the compiled template and passing it the context. + +The Lexer +--------- + +The lexer tokenizes a template source code into a token stream (each token is +an instance of ``Twig_Token``, and the stream is an instance of +``Twig_TokenStream``). The default lexer recognizes 13 different token types: + +* ``Twig_Token::BLOCK_START_TYPE``, ``Twig_Token::BLOCK_END_TYPE``: Delimiters for blocks (``{% %}``) +* ``Twig_Token::VAR_START_TYPE``, ``Twig_Token::VAR_END_TYPE``: Delimiters for variables (``{{ }}``) +* ``Twig_Token::TEXT_TYPE``: A text outside an expression; +* ``Twig_Token::NAME_TYPE``: A name in an expression; +* ``Twig_Token::NUMBER_TYPE``: A number in an expression; +* ``Twig_Token::STRING_TYPE``: A string in an expression; +* ``Twig_Token::OPERATOR_TYPE``: An operator; +* ``Twig_Token::PUNCTUATION_TYPE``: A punctuation sign; +* ``Twig_Token::INTERPOLATION_START_TYPE``, ``Twig_Token::INTERPOLATION_END_TYPE`` (as of Twig 1.5): Delimiters for string interpolation; +* ``Twig_Token::EOF_TYPE``: Ends of template. + +You can manually convert a source code into a token stream by calling the +``tokenize()`` method of an environment:: + + $stream = $twig->tokenize(new Twig_Source($source, $identifier)); + +.. versionadded:: 1.27 + ``Twig_Source`` was introduced in version 1.27, pass the source and the + identifier directly on previous versions. + +As the stream has a ``__toString()`` method, you can have a textual +representation of it by echoing the object:: + + echo $stream."\n"; + +Here is the output for the ``Hello {{ name }}`` template: + +.. code-block:: text + + TEXT_TYPE(Hello ) + VAR_START_TYPE() + NAME_TYPE(name) + VAR_END_TYPE() + EOF_TYPE() + +.. note:: + + The default lexer (``Twig_Lexer``) can be changed by calling + the ``setLexer()`` method:: + + $twig->setLexer($lexer); + +The Parser +---------- + +The parser converts the token stream into an AST (Abstract Syntax Tree), or a +node tree (an instance of ``Twig_Node_Module``). The core extension defines +the basic nodes like: ``for``, ``if``, ... and the expression nodes. + +You can manually convert a token stream into a node tree by calling the +``parse()`` method of an environment:: + + $nodes = $twig->parse($stream); + +Echoing the node object gives you a nice representation of the tree:: + + echo $nodes."\n"; + +Here is the output for the ``Hello {{ name }}`` template: + +.. code-block:: text + + Twig_Node_Module( + Twig_Node_Text(Hello ) + Twig_Node_Print( + Twig_Node_Expression_Name(name) + ) + ) + +.. note:: + + The default parser (``Twig_TokenParser``) can be changed by calling the + ``setParser()`` method:: + + $twig->setParser($parser); + +The Compiler +------------ + +The last step is done by the compiler. It takes a node tree as an input and +generates PHP code usable for runtime execution of the template. + +You can manually compile a node tree to PHP code with the ``compile()`` method +of an environment:: + + $php = $twig->compile($nodes); + +The generated template for a ``Hello {{ name }}`` template reads as follows +(the actual output can differ depending on the version of Twig you are +using):: + + /* Hello {{ name }} */ + class __TwigTemplate_1121b6f109fe93ebe8c6e22e3712bceb extends Twig_Template + { + protected function doDisplay(array $context, array $blocks = array()) + { + // line 1 + echo "Hello "; + echo twig_escape_filter($this->env, isset($context["name"]) ? $context["name"] : null), "html", null, true); + } + + // some more code + } + +.. note:: + + The default compiler (``Twig_Compiler``) can be changed by calling the + ``setCompiler()`` method:: + + $twig->setCompiler($compiler); diff --git a/src/vendor/twig/twig/doc/intro.rst b/src/vendor/twig/twig/doc/intro.rst new file mode 100644 index 0000000..3522869 --- /dev/null +++ b/src/vendor/twig/twig/doc/intro.rst @@ -0,0 +1,85 @@ +Introduction +============ + +This is the documentation for Twig, the flexible, fast, and secure template +engine for PHP. + +If you have any exposure to other text-based template languages, such as +Smarty, Django, or Jinja, you should feel right at home with Twig. It's both +designer and developer friendly by sticking to PHP's principles and adding +functionality useful for templating environments. + +The key-features are... + +* *Fast*: Twig compiles templates down to plain optimized PHP code. The + overhead compared to regular PHP code was reduced to the very minimum. + +* *Secure*: Twig has a sandbox mode to evaluate untrusted template code. This + allows Twig to be used as a template language for applications where users + may modify the template design. + +* *Flexible*: Twig is powered by a flexible lexer and parser. This allows the + developer to define their own custom tags and filters, and to create their own DSL. + +Twig is used by many Open-Source projects like Symfony, Drupal8, eZPublish, +phpBB, Piwik, OroCRM; and many frameworks have support for it as well like +Slim, Yii, Laravel, Codeigniter and Kohana — just to name a few. + +Prerequisites +------------- + +Twig needs at least **PHP 5.2.7** to run. + +Installation +------------ + +The recommended way to install Twig is via Composer: + +.. code-block:: bash + + composer require "twig/twig:~1.0" + +.. note:: + + To learn more about the other installation methods, read the + :doc:`installation` chapter; it also explains how to install + the Twig C extension. + +Basic API Usage +--------------- + +This section gives you a brief introduction to the PHP API for Twig. + +.. code-block:: php + + require_once '/path/to/vendor/autoload.php'; + + $loader = new Twig_Loader_Array(array( + 'index' => 'Hello {{ name }}!', + )); + $twig = new Twig_Environment($loader); + + echo $twig->render('index', array('name' => 'Fabien')); + +Twig uses a loader (``Twig_Loader_Array``) to locate templates, and an +environment (``Twig_Environment``) to store the configuration. + +The ``render()`` method loads the template passed as a first argument and +renders it with the variables passed as a second argument. + +As templates are generally stored on the filesystem, Twig also comes with a +filesystem loader:: + + $loader = new Twig_Loader_Filesystem('/path/to/templates'); + $twig = new Twig_Environment($loader, array( + 'cache' => '/path/to/compilation_cache', + )); + + echo $twig->render('index.html', array('name' => 'Fabien')); + +.. tip:: + + If you are not using Composer, use the Twig built-in autoloader:: + + require_once '/path/to/lib/Twig/Autoloader.php'; + Twig_Autoloader::register(); diff --git a/src/vendor/twig/twig/doc/recipes.rst b/src/vendor/twig/twig/doc/recipes.rst new file mode 100644 index 0000000..9523fff --- /dev/null +++ b/src/vendor/twig/twig/doc/recipes.rst @@ -0,0 +1,568 @@ +Recipes +======= + +.. _deprecation-notices: + +Displaying Deprecation Notices +------------------------------ + +.. versionadded:: 1.21 + This works as of Twig 1.21. + +Deprecated features generate deprecation notices (via a call to the +``trigger_error()`` PHP function). By default, they are silenced and never +displayed nor logged. + +To easily remove all deprecated feature usages from your templates, write and +run a script along the lines of the following:: + + require_once __DIR__.'/vendor/autoload.php'; + + $twig = create_your_twig_env(); + + $deprecations = new Twig_Util_DeprecationCollector($twig); + + print_r($deprecations->collectDir(__DIR__.'/templates')); + +The ``collectDir()`` method compiles all templates found in a directory, +catches deprecation notices, and return them. + +.. tip:: + + If your templates are not stored on the filesystem, use the ``collect()`` + method instead which takes an ``Iterator``; the iterator must return + template names as keys and template contents as values (as done by + ``Twig_Util_TemplateDirIterator``). + +However, this code won't find all deprecations (like using deprecated some Twig +classes). To catch all notices, register a custom error handler like the one +below:: + + $deprecations = array(); + set_error_handler(function ($type, $msg) use (&$deprecations) { + if (E_USER_DEPRECATED === $type) { + $deprecations[] = $msg; + } + }); + + // run your application + + print_r($deprecations); + +Note that most deprecation notices are triggered during **compilation**, so +they won't be generated when templates are already cached. + +.. tip:: + + If you want to manage the deprecation notices from your PHPUnit tests, have + a look at the `symfony/phpunit-bridge + `_ package, which eases the + process a lot. + +Making a Layout conditional +--------------------------- + +Working with Ajax means that the same content is sometimes displayed as is, +and sometimes decorated with a layout. As Twig layout template names can be +any valid expression, you can pass a variable that evaluates to ``true`` when +the request is made via Ajax and choose the layout accordingly: + +.. code-block:: jinja + + {% extends request.ajax ? "base_ajax.html" : "base.html" %} + + {% block content %} + This is the content to be displayed. + {% endblock %} + +Making an Include dynamic +------------------------- + +When including a template, its name does not need to be a string. For +instance, the name can depend on the value of a variable: + +.. code-block:: jinja + + {% include var ~ '_foo.html' %} + +If ``var`` evaluates to ``index``, the ``index_foo.html`` template will be +rendered. + +As a matter of fact, the template name can be any valid expression, such as +the following: + +.. code-block:: jinja + + {% include var|default('index') ~ '_foo.html' %} + +Overriding a Template that also extends itself +---------------------------------------------- + +A template can be customized in two different ways: + +* *Inheritance*: A template *extends* a parent template and overrides some + blocks; + +* *Replacement*: If you use the filesystem loader, Twig loads the first + template it finds in a list of configured directories; a template found in a + directory *replaces* another one from a directory further in the list. + +But how do you combine both: *replace* a template that also extends itself +(aka a template in a directory further in the list)? + +Let's say that your templates are loaded from both ``.../templates/mysite`` +and ``.../templates/default`` in this order. The ``page.twig`` template, +stored in ``.../templates/default`` reads as follows: + +.. code-block:: jinja + + {# page.twig #} + {% extends "layout.twig" %} + + {% block content %} + {% endblock %} + +You can replace this template by putting a file with the same name in +``.../templates/mysite``. And if you want to extend the original template, you +might be tempted to write the following: + +.. code-block:: jinja + + {# page.twig in .../templates/mysite #} + {% extends "page.twig" %} {# from .../templates/default #} + +Of course, this will not work as Twig will always load the template from +``.../templates/mysite``. + +It turns out it is possible to get this to work, by adding a directory right +at the end of your template directories, which is the parent of all of the +other directories: ``.../templates`` in our case. This has the effect of +making every template file within our system uniquely addressable. Most of the +time you will use the "normal" paths, but in the special case of wanting to +extend a template with an overriding version of itself we can reference its +parent's full, unambiguous template path in the extends tag: + +.. code-block:: jinja + + {# page.twig in .../templates/mysite #} + {% extends "default/page.twig" %} {# from .../templates #} + +.. note:: + + This recipe was inspired by the following Django wiki page: + http://code.djangoproject.com/wiki/ExtendingTemplates + +Customizing the Syntax +---------------------- + +Twig allows some syntax customization for the block delimiters. It's not +recommended to use this feature as templates will be tied with your custom +syntax. But for specific projects, it can make sense to change the defaults. + +To change the block delimiters, you need to create your own lexer object:: + + $twig = new Twig_Environment(); + + $lexer = new Twig_Lexer($twig, array( + 'tag_comment' => array('{#', '#}'), + 'tag_block' => array('{%', '%}'), + 'tag_variable' => array('{{', '}}'), + 'interpolation' => array('#{', '}'), + )); + $twig->setLexer($lexer); + +Here are some configuration example that simulates some other template engines +syntax:: + + // Ruby erb syntax + $lexer = new Twig_Lexer($twig, array( + 'tag_comment' => array('<%#', '%>'), + 'tag_block' => array('<%', '%>'), + 'tag_variable' => array('<%=', '%>'), + )); + + // SGML Comment Syntax + $lexer = new Twig_Lexer($twig, array( + 'tag_comment' => array(''), + 'tag_block' => array(''), + 'tag_variable' => array('${', '}'), + )); + + // Smarty like + $lexer = new Twig_Lexer($twig, array( + 'tag_comment' => array('{*', '*}'), + 'tag_block' => array('{', '}'), + 'tag_variable' => array('{$', '}'), + )); + +Using dynamic Object Properties +------------------------------- + +When Twig encounters a variable like ``article.title``, it tries to find a +``title`` public property in the ``article`` object. + +It also works if the property does not exist but is rather defined dynamically +thanks to the magic ``__get()`` method; you just need to also implement the +``__isset()`` magic method like shown in the following snippet of code:: + + class Article + { + public function __get($name) + { + if ('title' == $name) { + return 'The title'; + } + + // throw some kind of error + } + + public function __isset($name) + { + if ('title' == $name) { + return true; + } + + return false; + } + } + +Accessing the parent Context in Nested Loops +-------------------------------------------- + +Sometimes, when using nested loops, you need to access the parent context. The +parent context is always accessible via the ``loop.parent`` variable. For +instance, if you have the following template data:: + + $data = array( + 'topics' => array( + 'topic1' => array('Message 1 of topic 1', 'Message 2 of topic 1'), + 'topic2' => array('Message 1 of topic 2', 'Message 2 of topic 2'), + ), + ); + +And the following template to display all messages in all topics: + +.. code-block:: jinja + + {% for topic, messages in topics %} + * {{ loop.index }}: {{ topic }} + {% for message in messages %} + - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }} + {% endfor %} + {% endfor %} + +The output will be similar to: + +.. code-block:: text + + * 1: topic1 + - 1.1: The message 1 of topic 1 + - 1.2: The message 2 of topic 1 + * 2: topic2 + - 2.1: The message 1 of topic 2 + - 2.2: The message 2 of topic 2 + +In the inner loop, the ``loop.parent`` variable is used to access the outer +context. So, the index of the current ``topic`` defined in the outer for loop +is accessible via the ``loop.parent.loop.index`` variable. + +Defining undefined Functions and Filters on the Fly +--------------------------------------------------- + +When a function (or a filter) is not defined, Twig defaults to throw a +``Twig_Error_Syntax`` exception. However, it can also call a `callback`_ (any +valid PHP callable) which should return a function (or a filter). + +For filters, register callbacks with ``registerUndefinedFilterCallback()``. +For functions, use ``registerUndefinedFunctionCallback()``:: + + // auto-register all native PHP functions as Twig functions + // don't try this at home as it's not secure at all! + $twig->registerUndefinedFunctionCallback(function ($name) { + if (function_exists($name)) { + return new Twig_SimpleFunction($name, $name); + } + + return false; + }); + +If the callable is not able to return a valid function (or filter), it must +return ``false``. + +If you register more than one callback, Twig will call them in turn until one +does not return ``false``. + +.. tip:: + + As the resolution of functions and filters is done during compilation, + there is no overhead when registering these callbacks. + +Validating the Template Syntax +------------------------------ + +When template code is provided by a third-party (through a web interface for +instance), it might be interesting to validate the template syntax before +saving it. If the template code is stored in a `$template` variable, here is +how you can do it:: + + try { + $twig->parse($twig->tokenize(new Twig_Source($template))); + + // the $template is valid + } catch (Twig_Error_Syntax $e) { + // $template contains one or more syntax errors + } + +If you iterate over a set of files, you can pass the filename to the +``tokenize()`` method to get the filename in the exception message:: + + foreach ($files as $file) { + try { + $twig->parse($twig->tokenize(new Twig_Source($template, $file->getFilename(), $file))); + + // the $template is valid + } catch (Twig_Error_Syntax $e) { + // $template contains one or more syntax errors + } + } + +.. versionadded:: 1.27 + ``Twig_Source`` was introduced in version 1.27, pass the source and the + identifier directly on previous versions. + +.. note:: + + This method won't catch any sandbox policy violations because the policy + is enforced during template rendering (as Twig needs the context for some + checks like allowed methods on objects). + +Refreshing modified Templates when OPcache or APC is enabled +------------------------------------------------------------ + +When using OPcache with ``opcache.validate_timestamps`` set to ``0`` or APC +with ``apc.stat`` set to ``0`` and Twig cache enabled, clearing the template +cache won't update the cache. + +To get around this, force Twig to invalidate the bytecode cache:: + + $twig = new Twig_Environment($loader, array( + 'cache' => new Twig_Cache_Filesystem('/some/cache/path', Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION), + // ... + )); + +.. note:: + + Before Twig 1.22, you should extend ``Twig_Environment`` instead:: + + class OpCacheAwareTwigEnvironment extends Twig_Environment + { + protected function writeCacheFile($file, $content) + { + parent::writeCacheFile($file, $content); + + // Compile cached file into bytecode cache + if (function_exists('opcache_invalidate')) { + opcache_invalidate($file, true); + } elseif (function_exists('apc_compile_file')) { + apc_compile_file($file); + } + } + } + +Reusing a stateful Node Visitor +------------------------------- + +When attaching a visitor to a ``Twig_Environment`` instance, Twig uses it to +visit *all* templates it compiles. If you need to keep some state information +around, you probably want to reset it when visiting a new template. + +This can be easily achieved with the following code:: + + protected $someTemplateState = array(); + + public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Module) { + // reset the state as we are entering a new template + $this->someTemplateState = array(); + } + + // ... + + return $node; + } + +Using a Database to store Templates +----------------------------------- + +If you are developing a CMS, templates are usually stored in a database. This +recipe gives you a simple PDO template loader you can use as a starting point +for your own. + +First, let's create a temporary in-memory SQLite3 database to work with:: + + $dbh = new PDO('sqlite::memory:'); + $dbh->exec('CREATE TABLE templates (name STRING, source STRING, last_modified INTEGER)'); + $base = '{% block content %}{% endblock %}'; + $index = ' + {% extends "base.twig" %} + {% block content %}Hello {{ name }}{% endblock %} + '; + $now = time(); + $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('base.twig', '$base', $now)"); + $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('index.twig', '$index', $now)"); + +We have created a simple ``templates`` table that hosts two templates: +``base.twig`` and ``index.twig``. + +Now, let's define a loader able to use this database:: + + class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface + { + protected $dbh; + + public function __construct(PDO $dbh) + { + $this->dbh = $dbh; + } + + public function getSource($name) + { + if (false === $source = $this->getValue('source', $name)) { + throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name)); + } + + return $source; + } + + // Twig_SourceContextLoaderInterface as of Twig 1.27 + public function getSourceContext($name) + { + if (false === $source = $this->getValue('source', $name)) { + throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name)); + } + + return new Twig_Source($source, $name); + } + + // Twig_ExistsLoaderInterface as of Twig 1.11 + public function exists($name) + { + return $name === $this->getValue('name', $name); + } + + public function getCacheKey($name) + { + return $name; + } + + public function isFresh($name, $time) + { + if (false === $lastModified = $this->getValue('last_modified', $name)) { + return false; + } + + return $lastModified <= $time; + } + + protected function getValue($column, $name) + { + $sth = $this->dbh->prepare('SELECT '.$column.' FROM templates WHERE name = :name'); + $sth->execute(array(':name' => (string) $name)); + + return $sth->fetchColumn(); + } + } + +Finally, here is an example on how you can use it:: + + $loader = new DatabaseTwigLoader($dbh); + $twig = new Twig_Environment($loader); + + echo $twig->render('index.twig', array('name' => 'Fabien')); + +Using different Template Sources +-------------------------------- + +This recipe is the continuation of the previous one. Even if you store the +contributed templates in a database, you might want to keep the original/base +templates on the filesystem. When templates can be loaded from different +sources, you need to use the ``Twig_Loader_Chain`` loader. + +As you can see in the previous recipe, we reference the template in the exact +same way as we would have done it with a regular filesystem loader. This is +the key to be able to mix and match templates coming from the database, the +filesystem, or any other loader for that matter: the template name should be a +logical name, and not the path from the filesystem:: + + $loader1 = new DatabaseTwigLoader($dbh); + $loader2 = new Twig_Loader_Array(array( + 'base.twig' => '{% block content %}{% endblock %}', + )); + $loader = new Twig_Loader_Chain(array($loader1, $loader2)); + + $twig = new Twig_Environment($loader); + + echo $twig->render('index.twig', array('name' => 'Fabien')); + +Now that the ``base.twig`` templates is defined in an array loader, you can +remove it from the database, and everything else will still work as before. + +Loading a Template from a String +-------------------------------- + +From a template, you can easily load a template stored in a string via the +``template_from_string`` function (available as of Twig 1.11 via the +``Twig_Extension_StringLoader`` extension): + +.. code-block:: jinja + + {{ include(template_from_string("Hello {{ name }}")) }} + +From PHP, it's also possible to load a template stored in a string via +``Twig_Environment::createTemplate()`` (available as of Twig 1.18):: + + $template = $twig->createTemplate('hello {{ name }}'); + echo $template->render(array('name' => 'Fabien')); + +.. note:: + + Never use the ``Twig_Loader_String`` loader, which has severe limitations. + +Using Twig and AngularJS in the same Templates +---------------------------------------------- + +Mixing different template syntaxes in the same file is not a recommended +practice as both AngularJS and Twig use the same delimiters in their syntax: +``{{`` and ``}}``. + +Still, if you want to use AngularJS and Twig in the same template, there are +two ways to make it work depending on the amount of AngularJS you need to +include in your templates: + +* Escaping the AngularJS delimiters by wrapping AngularJS sections with the + ``{% verbatim %}`` tag or by escaping each delimiter via ``{{ '{{' }}`` and + ``{{ '}}' }}``; + +* Changing the delimiters of one of the template engines (depending on which + engine you introduced last): + + * For AngularJS, change the interpolation tags using the + ``interpolateProvider`` service, for instance at the module initialization + time: + + ..code-block:: javascript + + angular.module('myApp', []).config(function($interpolateProvider) { + $interpolateProvider.startSymbol('{[').endSymbol(']}'); + }); + + * For Twig, change the delimiters via the ``tag_variable`` Lexer option: + + ..code-block:: php + + $env->setLexer(new Twig_Lexer($env, array( + 'tag_variable' => array('{[', ']}'), + ))); + +.. _callback: http://www.php.net/manual/en/function.is-callable.php diff --git a/src/vendor/twig/twig/doc/tags/autoescape.rst b/src/vendor/twig/twig/doc/tags/autoescape.rst new file mode 100644 index 0000000..8aa8c75 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/autoescape.rst @@ -0,0 +1,81 @@ +``autoescape`` +============== + +Whether automatic escaping is enabled or not, you can mark a section of a +template to be escaped or not by using the ``autoescape`` tag: + +.. code-block:: jinja + + {% autoescape %} + Everything will be automatically escaped in this block + using the HTML strategy + {% endautoescape %} + + {% autoescape 'html' %} + Everything will be automatically escaped in this block + using the HTML strategy + {% endautoescape %} + + {% autoescape 'js' %} + Everything will be automatically escaped in this block + using the js escaping strategy + {% endautoescape %} + + {% autoescape false %} + Everything will be outputted as is in this block + {% endautoescape %} + +.. note:: + + Before Twig 1.8, the syntax was different: + + .. code-block:: jinja + + {% autoescape true %} + Everything will be automatically escaped in this block + using the HTML strategy + {% endautoescape %} + + {% autoescape false %} + Everything will be outputted as is in this block + {% endautoescape %} + + {% autoescape true js %} + Everything will be automatically escaped in this block + using the js escaping strategy + {% endautoescape %} + +When automatic escaping is enabled everything is escaped by default except for +values explicitly marked as safe. Those can be marked in the template by using +the :doc:`raw<../filters/raw>` filter: + +.. code-block:: jinja + + {% autoescape %} + {{ safe_value|raw }} + {% endautoescape %} + +Functions returning template data (like :doc:`macros` and +:doc:`parent<../functions/parent>`) always return safe markup. + +.. note:: + + Twig is smart enough to not escape an already escaped value by the + :doc:`escape<../filters/escape>` filter. + +.. note:: + + Twig does not escape static expressions: + + .. code-block:: jinja + + {% set hello = "Hello" %} + {{ hello }} + {{ "world" }} + + Will be rendered "Hello **world**". + +.. note:: + + The chapter :doc:`Twig for Developers<../api>` gives more information + about when and how automatic escaping is applied. diff --git a/src/vendor/twig/twig/doc/tags/block.rst b/src/vendor/twig/twig/doc/tags/block.rst new file mode 100644 index 0000000..e380482 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/block.rst @@ -0,0 +1,11 @@ +``block`` +========= + +Blocks are used for inheritance and act as placeholders and replacements at +the same time. They are documented in detail in the documentation for the +:doc:`extends<../tags/extends>` tag. + +Block names should consist of alphanumeric characters, and underscores. Dashes +are not permitted. + +.. seealso:: :doc:`block<../functions/block>`, :doc:`parent<../functions/parent>`, :doc:`use<../tags/use>`, :doc:`extends<../tags/extends>` diff --git a/src/vendor/twig/twig/doc/tags/do.rst b/src/vendor/twig/twig/doc/tags/do.rst new file mode 100644 index 0000000..1c344e3 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/do.rst @@ -0,0 +1,12 @@ +``do`` +====== + +.. versionadded:: 1.5 + The ``do`` tag was added in Twig 1.5. + +The ``do`` tag works exactly like the regular variable expression (``{{ ... +}}``) just that it doesn't print anything: + +.. code-block:: jinja + + {% do 1 + 2 %} diff --git a/src/vendor/twig/twig/doc/tags/embed.rst b/src/vendor/twig/twig/doc/tags/embed.rst new file mode 100644 index 0000000..66fc21b --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/embed.rst @@ -0,0 +1,178 @@ +``embed`` +========= + +.. versionadded:: 1.8 + The ``embed`` tag was added in Twig 1.8. + +The ``embed`` tag combines the behaviour of :doc:`include` and +:doc:`extends`. +It allows you to include another template's contents, just like ``include`` +does. But it also allows you to override any block defined inside the +included template, like when extending a template. + +Think of an embedded template as a "micro layout skeleton". + +.. code-block:: jinja + + {% embed "teasers_skeleton.twig" %} + {# These blocks are defined in "teasers_skeleton.twig" #} + {# and we override them right here: #} + {% block left_teaser %} + Some content for the left teaser box + {% endblock %} + {% block right_teaser %} + Some content for the right teaser box + {% endblock %} + {% endembed %} + +The ``embed`` tag takes the idea of template inheritance to the level of +content fragments. While template inheritance allows for "document skeletons", +which are filled with life by child templates, the ``embed`` tag allows you to +create "skeletons" for smaller units of content and re-use and fill them +anywhere you like. + +Since the use case may not be obvious, let's look at a simplified example. +Imagine a base template shared by multiple HTML pages, defining a single block +named "content": + +.. code-block:: text + + ┌─── page layout ─────────────────────┐ + │ │ + │ ┌── block "content" ──┐ │ + │ │ │ │ + │ │ │ │ + │ │ (child template to │ │ + │ │ put content here) │ │ + │ │ │ │ + │ │ │ │ + │ └─────────────────────┘ │ + │ │ + └─────────────────────────────────────┘ + +Some pages ("foo" and "bar") share the same content structure - +two vertically stacked boxes: + +.. code-block:: text + + ┌─── page layout ─────────────────────┐ + │ │ + │ ┌── block "content" ──┐ │ + │ │ ┌─ block "top" ───┐ │ │ + │ │ │ │ │ │ + │ │ └─────────────────┘ │ │ + │ │ ┌─ block "bottom" ┐ │ │ + │ │ │ │ │ │ + │ │ └─────────────────┘ │ │ + │ └─────────────────────┘ │ + │ │ + └─────────────────────────────────────┘ + +While other pages ("boom" and "baz") share a different content structure - +two boxes side by side: + +.. code-block:: text + + ┌─── page layout ─────────────────────┐ + │ │ + │ ┌── block "content" ──┐ │ + │ │ │ │ + │ │ ┌ block ┐ ┌ block ┐ │ │ + │ │ │"left" │ │"right"│ │ │ + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ + │ │ └───────┘ └───────┘ │ │ + │ └─────────────────────┘ │ + │ │ + └─────────────────────────────────────┘ + +Without the ``embed`` tag, you have two ways to design your templates: + + * Create two "intermediate" base templates that extend the master layout + template: one with vertically stacked boxes to be used by the "foo" and + "bar" pages and another one with side-by-side boxes for the "boom" and + "baz" pages. + + * Embed the markup for the top/bottom and left/right boxes into each page + template directly. + +These two solutions do not scale well because they each have a major drawback: + + * The first solution may indeed work for this simplified example. But imagine + we add a sidebar, which may again contain different, recurring structures + of content. Now we would need to create intermediate base templates for + all occurring combinations of content structure and sidebar structure... + and so on. + + * The second solution involves duplication of common code with all its negative + consequences: any change involves finding and editing all affected copies + of the structure, correctness has to be verified for each copy, copies may + go out of sync by careless modifications etc. + +In such a situation, the ``embed`` tag comes in handy. The common layout +code can live in a single base template, and the two different content structures, +let's call them "micro layouts" go into separate templates which are embedded +as necessary: + +Page template ``foo.twig``: + +.. code-block:: jinja + + {% extends "layout_skeleton.twig" %} + + {% block content %} + {% embed "vertical_boxes_skeleton.twig" %} + {% block top %} + Some content for the top box + {% endblock %} + + {% block bottom %} + Some content for the bottom box + {% endblock %} + {% endembed %} + {% endblock %} + +And here is the code for ``vertical_boxes_skeleton.twig``: + +.. code-block:: html+jinja + +
+ {% block top %} + Top box default content + {% endblock %} +
+ +
+ {% block bottom %} + Bottom box default content + {% endblock %} +
+ +The goal of the ``vertical_boxes_skeleton.twig`` template being to factor +out the HTML markup for the boxes. + +The ``embed`` tag takes the exact same arguments as the ``include`` tag: + +.. code-block:: jinja + + {% embed "base" with {'foo': 'bar'} %} + ... + {% endembed %} + + {% embed "base" with {'foo': 'bar'} only %} + ... + {% endembed %} + + {% embed "base" ignore missing %} + ... + {% endembed %} + +.. warning:: + + As embedded templates do not have "names", auto-escaping strategies based + on the template name won't work as expected if you change the context (for + instance, if you embed a CSS/JavaScript template into an HTML one). In that + case, explicitly set the default auto-escaping strategy with the + ``autoescape`` tag. + +.. seealso:: :doc:`include<../tags/include>` diff --git a/src/vendor/twig/twig/doc/tags/extends.rst b/src/vendor/twig/twig/doc/tags/extends.rst new file mode 100644 index 0000000..df7bdcb --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/extends.rst @@ -0,0 +1,272 @@ +``extends`` +=========== + +The ``extends`` tag can be used to extend a template from another one. + +.. note:: + + Like PHP, Twig does not support multiple inheritance. So you can only have + one extends tag called per rendering. However, Twig supports horizontal + :doc:`reuse`. + +Let's define a base template, ``base.html``, which defines a simple HTML +skeleton document: + +.. code-block:: html+jinja + + + + + {% block head %} + + {% block title %}{% endblock %} - My Webpage + {% endblock %} + + +
{% block content %}{% endblock %}
+ + + + +In this example, the :doc:`block` tags define four blocks that child +templates can fill in. + +All the ``block`` tag does is to tell the template engine that a child +template may override those portions of the template. + +Child Template +-------------- + +A child template might look like this: + +.. code-block:: jinja + + {% extends "base.html" %} + + {% block title %}Index{% endblock %} + {% block head %} + {{ parent() }} + + {% endblock %} + {% block content %} +

Index

+

+ Welcome on my awesome homepage. +

+ {% endblock %} + +The ``extends`` tag is the key here. It tells the template engine that this +template "extends" another template. When the template system evaluates this +template, first it locates the parent. The extends tag should be the first tag +in the template. + +Note that since the child template doesn't define the ``footer`` block, the +value from the parent template is used instead. + +You can't define multiple ``block`` tags with the same name in the same +template. This limitation exists because a block tag works in "both" +directions. That is, a block tag doesn't just provide a hole to fill - it also +defines the content that fills the hole in the *parent*. If there were two +similarly-named ``block`` tags in a template, that template's parent wouldn't +know which one of the blocks' content to use. + +If you want to print a block multiple times you can however use the +``block`` function: + +.. code-block:: jinja + + {% block title %}{% endblock %} +

{{ block('title') }}

+ {% block body %}{% endblock %} + +Parent Blocks +------------- + +It's possible to render the contents of the parent block by using the +:doc:`parent<../functions/parent>` function. This gives back the results of +the parent block: + +.. code-block:: jinja + + {% block sidebar %} +

Table Of Contents

+ ... + {{ parent() }} + {% endblock %} + +Named Block End-Tags +-------------------- + +Twig allows you to put the name of the block after the end tag for better +readability: + +.. code-block:: jinja + + {% block sidebar %} + {% block inner_sidebar %} + ... + {% endblock inner_sidebar %} + {% endblock sidebar %} + +Of course, the name after the ``endblock`` word must match the block name. + +Block Nesting and Scope +----------------------- + +Blocks can be nested for more complex layouts. Per default, blocks have access +to variables from outer scopes: + +.. code-block:: jinja + + {% for item in seq %} +
  • {% block loop_item %}{{ item }}{% endblock %}
  • + {% endfor %} + +Block Shortcuts +--------------- + +For blocks with little content, it's possible to use a shortcut syntax. The +following constructs do the same thing: + +.. code-block:: jinja + + {% block title %} + {{ page_title|title }} + {% endblock %} + +.. code-block:: jinja + + {% block title page_title|title %} + +Dynamic Inheritance +------------------- + +Twig supports dynamic inheritance by using a variable as the base template: + +.. code-block:: jinja + + {% extends some_var %} + +If the variable evaluates to a ``Twig_Template`` or a ``Twig_TemplateWraper`` +instance, Twig will use it as the parent template:: + + // {% extends layout %} + + // deprecated as of Twig 1.28 + $layout = $twig->loadTemplate('some_layout_template.twig'); + + // as of Twig 1.28 + $layout = $twig->load('some_layout_template.twig'); + + $twig->display('template.twig', array('layout' => $layout)); + +.. versionadded:: 1.2 + The possibility to pass an array of templates has been added in Twig 1.2. + +You can also provide a list of templates that are checked for existence. The +first template that exists will be used as a parent: + +.. code-block:: jinja + + {% extends ['layout.html', 'base_layout.html'] %} + +Conditional Inheritance +----------------------- + +As the template name for the parent can be any valid Twig expression, it's +possible to make the inheritance mechanism conditional: + +.. code-block:: jinja + + {% extends standalone ? "minimum.html" : "base.html" %} + +In this example, the template will extend the "minimum.html" layout template +if the ``standalone`` variable evaluates to ``true``, and "base.html" +otherwise. + +How do blocks work? +------------------- + +A block provides a way to change how a certain part of a template is rendered +but it does not interfere in any way with the logic around it. + +Let's take the following example to illustrate how a block works and more +importantly, how it does not work: + +.. code-block:: jinja + + {# base.twig #} + + {% for post in posts %} + {% block post %} +

    {{ post.title }}

    +

    {{ post.body }}

    + {% endblock %} + {% endfor %} + +If you render this template, the result would be exactly the same with or +without the ``block`` tag. The ``block`` inside the ``for`` loop is just a way +to make it overridable by a child template: + +.. code-block:: jinja + + {# child.twig #} + + {% extends "base.twig" %} + + {% block post %} +
    +
    {{ post.title }}
    +
    {{ post.text }}
    +
    + {% endblock %} + +Now, when rendering the child template, the loop is going to use the block +defined in the child template instead of the one defined in the base one; the +executed template is then equivalent to the following one: + +.. code-block:: jinja + + {% for post in posts %} +
    +
    {{ post.title }}
    +
    {{ post.text }}
    +
    + {% endfor %} + +Let's take another example: a block included within an ``if`` statement: + +.. code-block:: jinja + + {% if posts is empty %} + {% block head %} + {{ parent() }} + + + {% endblock head %} + {% endif %} + +Contrary to what you might think, this template does not define a block +conditionally; it just makes overridable by a child template the output of +what will be rendered when the condition is ``true``. + +If you want the output to be displayed conditionally, use the following +instead: + +.. code-block:: jinja + + {% block head %} + {{ parent() }} + + {% if posts is empty %} + + {% endif %} + {% endblock head %} + +.. seealso:: :doc:`block<../functions/block>`, :doc:`block<../tags/block>`, :doc:`parent<../functions/parent>`, :doc:`use<../tags/use>` diff --git a/src/vendor/twig/twig/doc/tags/filter.rst b/src/vendor/twig/twig/doc/tags/filter.rst new file mode 100644 index 0000000..82ca5c6 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/filter.rst @@ -0,0 +1,21 @@ +``filter`` +========== + +Filter sections allow you to apply regular Twig filters on a block of template +data. Just wrap the code in the special ``filter`` section: + +.. code-block:: jinja + + {% filter upper %} + This text becomes uppercase + {% endfilter %} + +You can also chain filters: + +.. code-block:: jinja + + {% filter lower|escape %} + SOME TEXT + {% endfilter %} + + {# outputs "<strong>some text</strong>" #} diff --git a/src/vendor/twig/twig/doc/tags/flush.rst b/src/vendor/twig/twig/doc/tags/flush.rst new file mode 100644 index 0000000..55ef593 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/flush.rst @@ -0,0 +1,17 @@ +``flush`` +========= + +.. versionadded:: 1.5 + The flush tag was added in Twig 1.5. + +The ``flush`` tag tells Twig to flush the output buffer: + +.. code-block:: jinja + + {% flush %} + +.. note:: + + Internally, Twig uses the PHP `flush`_ function. + +.. _`flush`: http://php.net/flush diff --git a/src/vendor/twig/twig/doc/tags/for.rst b/src/vendor/twig/twig/doc/tags/for.rst new file mode 100644 index 0000000..0673b55 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/for.rst @@ -0,0 +1,172 @@ +``for`` +======= + +Loop over each item in a sequence. For example, to display a list of users +provided in a variable called ``users``: + +.. code-block:: jinja + +

    Members

    +
      + {% for user in users %} +
    • {{ user.username|e }}
    • + {% endfor %} +
    + +.. note:: + + A sequence can be either an array or an object implementing the + ``Traversable`` interface. + +If you do need to iterate over a sequence of numbers, you can use the ``..`` +operator: + +.. code-block:: jinja + + {% for i in 0..10 %} + * {{ i }} + {% endfor %} + +The above snippet of code would print all numbers from 0 to 10. + +It can be also useful with letters: + +.. code-block:: jinja + + {% for letter in 'a'..'z' %} + * {{ letter }} + {% endfor %} + +The ``..`` operator can take any expression at both sides: + +.. code-block:: jinja + + {% for letter in 'a'|upper..'z'|upper %} + * {{ letter }} + {% endfor %} + +.. tip: + + If you need a step different from 1, you can use the ``range`` function + instead. + +The `loop` variable +------------------- + +Inside of a ``for`` loop block you can access some special variables: + +===================== ============================================================= +Variable Description +===================== ============================================================= +``loop.index`` The current iteration of the loop. (1 indexed) +``loop.index0`` The current iteration of the loop. (0 indexed) +``loop.revindex`` The number of iterations from the end of the loop (1 indexed) +``loop.revindex0`` The number of iterations from the end of the loop (0 indexed) +``loop.first`` True if first iteration +``loop.last`` True if last iteration +``loop.length`` The number of items in the sequence +``loop.parent`` The parent context +===================== ============================================================= + +.. code-block:: jinja + + {% for user in users %} + {{ loop.index }} - {{ user.username }} + {% endfor %} + +.. note:: + + The ``loop.length``, ``loop.revindex``, ``loop.revindex0``, and + ``loop.last`` variables are only available for PHP arrays, or objects that + implement the ``Countable`` interface. They are also not available when + looping with a condition. + +.. versionadded:: 1.2 + The ``if`` modifier support has been added in Twig 1.2. + +Adding a condition +------------------ + +Unlike in PHP, it's not possible to ``break`` or ``continue`` in a loop. You +can however filter the sequence during iteration which allows you to skip +items. The following example skips all the users which are not active: + +.. code-block:: jinja + +
      + {% for user in users if user.active %} +
    • {{ user.username|e }}
    • + {% endfor %} +
    + +The advantage is that the special loop variable will count correctly thus not +counting the users not iterated over. Keep in mind that properties like +``loop.last`` will not be defined when using loop conditions. + +.. note:: + + Using the ``loop`` variable within the condition is not recommended as it + will probably not be doing what you expect it to. For instance, adding a + condition like ``loop.index > 4`` won't work as the index is only + incremented when the condition is true (so the condition will never + match). + +The `else` Clause +----------------- + +If no iteration took place because the sequence was empty, you can render a +replacement block by using ``else``: + +.. code-block:: jinja + +
      + {% for user in users %} +
    • {{ user.username|e }}
    • + {% else %} +
    • no user found
    • + {% endfor %} +
    + +Iterating over Keys +------------------- + +By default, a loop iterates over the values of the sequence. You can iterate +on keys by using the ``keys`` filter: + +.. code-block:: jinja + +

    Members

    +
      + {% for key in users|keys %} +
    • {{ key }}
    • + {% endfor %} +
    + +Iterating over Keys and Values +------------------------------ + +You can also access both keys and values: + +.. code-block:: jinja + +

    Members

    +
      + {% for key, user in users %} +
    • {{ key }}: {{ user.username|e }}
    • + {% endfor %} +
    + +Iterating over a Subset +----------------------- + +You might want to iterate over a subset of values. This can be achieved using +the :doc:`slice <../filters/slice>` filter: + +.. code-block:: jinja + +

    Top Ten Members

    +
      + {% for user in users|slice(0, 10) %} +
    • {{ user.username|e }}
    • + {% endfor %} +
    diff --git a/src/vendor/twig/twig/doc/tags/from.rst b/src/vendor/twig/twig/doc/tags/from.rst new file mode 100644 index 0000000..39334fd --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/from.rst @@ -0,0 +1,8 @@ +``from`` +======== + +The ``from`` tag imports :doc:`macro<../tags/macro>` names into the current +namespace. The tag is documented in detail in the documentation for the +:doc:`import<../tags/import>` tag. + +.. seealso:: :doc:`macro<../tags/macro>`, :doc:`import<../tags/import>` diff --git a/src/vendor/twig/twig/doc/tags/if.rst b/src/vendor/twig/twig/doc/tags/if.rst new file mode 100644 index 0000000..12edf98 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/if.rst @@ -0,0 +1,76 @@ +``if`` +====== + +The ``if`` statement in Twig is comparable with the if statements of PHP. + +In the simplest form you can use it to test if an expression evaluates to +``true``: + +.. code-block:: jinja + + {% if online == false %} +

    Our website is in maintenance mode. Please, come back later.

    + {% endif %} + +You can also test if an array is not empty: + +.. code-block:: jinja + + {% if users %} +
      + {% for user in users %} +
    • {{ user.username|e }}
    • + {% endfor %} +
    + {% endif %} + +.. note:: + + If you want to test if the variable is defined, use ``if users is + defined`` instead. + +You can also use ``not`` to check for values that evaluate to ``false``: + +.. code-block:: jinja + + {% if not user.subscribed %} +

    You are not subscribed to our mailing list.

    + {% endif %} + +For multiple conditions, ``and`` and ``or`` can be used: + +.. code-block:: jinja + + {% if temperature > 18 and temperature < 27 %} +

    It's a nice day for a walk in the park.

    + {% endif %} + +For multiple branches ``elseif`` and ``else`` can be used like in PHP. You can +use more complex ``expressions`` there too: + +.. code-block:: jinja + + {% if kenny.sick %} + Kenny is sick. + {% elseif kenny.dead %} + You killed Kenny! You bastard!!! + {% else %} + Kenny looks okay --- so far + {% endif %} + +.. note:: + + The rules to determine if an expression is ``true`` or ``false`` are the + same as in PHP; here are the edge cases rules: + + ====================== ==================== + Value Boolean evaluation + ====================== ==================== + empty string false + numeric zero false + whitespace-only string true + empty array false + null false + non-empty array true + object true + ====================== ==================== diff --git a/src/vendor/twig/twig/doc/tags/import.rst b/src/vendor/twig/twig/doc/tags/import.rst new file mode 100644 index 0000000..21a1e19 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/import.rst @@ -0,0 +1,57 @@ +``import`` +========== + +Twig supports putting often used code into :doc:`macros<../tags/macro>`. These +macros can go into different templates and get imported from there. + +There are two ways to import templates. You can import the complete template +into a variable or request specific macros from it. + +Imagine we have a helper module that renders forms (called ``forms.html``): + +.. code-block:: jinja + + {% macro input(name, value, type, size) %} + + {% endmacro %} + + {% macro textarea(name, value, rows, cols) %} + + {% endmacro %} + +The easiest and most flexible is importing the whole module into a variable. +That way you can access the attributes: + +.. code-block:: jinja + + {% import 'forms.html' as forms %} + +
    +
    Username
    +
    {{ forms.input('username') }}
    +
    Password
    +
    {{ forms.input('password', null, 'password') }}
    +
    +

    {{ forms.textarea('comment') }}

    + +Alternatively you can import names from the template into the current +namespace: + +.. code-block:: jinja + + {% from 'forms.html' import input as input_field, textarea %} + +
    +
    Username
    +
    {{ input_field('username') }}
    +
    Password
    +
    {{ input_field('password', '', 'password') }}
    +
    +

    {{ textarea('comment') }}

    + +.. tip:: + + To import macros from the current file, use the special ``_self`` variable + for the source. + +.. seealso:: :doc:`macro<../tags/macro>`, :doc:`from<../tags/from>` diff --git a/src/vendor/twig/twig/doc/tags/include.rst b/src/vendor/twig/twig/doc/tags/include.rst new file mode 100644 index 0000000..24ff24d --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/include.rst @@ -0,0 +1,90 @@ +``include`` +=========== + +The ``include`` statement includes a template and returns the rendered content +of that file into the current namespace: + +.. code-block:: jinja + + {% include 'header.html' %} + Body + {% include 'footer.html' %} + +Included templates have access to the variables of the active context. + +If you are using the filesystem loader, the templates are looked for in the +paths defined by it. + +You can add additional variables by passing them after the ``with`` keyword: + +.. code-block:: jinja + + {# template.html will have access to the variables from the current context and the additional ones provided #} + {% include 'template.html' with {'foo': 'bar'} %} + + {% set vars = {'foo': 'bar'} %} + {% include 'template.html' with vars %} + +You can disable access to the context by appending the ``only`` keyword: + +.. code-block:: jinja + + {# only the foo variable will be accessible #} + {% include 'template.html' with {'foo': 'bar'} only %} + +.. code-block:: jinja + + {# no variables will be accessible #} + {% include 'template.html' only %} + +.. tip:: + + When including a template created by an end user, you should consider + sandboxing it. More information in the :doc:`Twig for Developers<../api>` + chapter and in the :doc:`sandbox<../tags/sandbox>` tag documentation. + +The template name can be any valid Twig expression: + +.. code-block:: jinja + + {% include some_var %} + {% include ajax ? 'ajax.html' : 'not_ajax.html' %} + +And if the expression evaluates to a ``Twig_Template`` or a +``Twig_TemplateWrapper`` instance, Twig will use it directly:: + + // {% include template %} + + // deprecated as of Twig 1.28 + $template = $twig->loadTemplate('some_template.twig'); + + // as of Twig 1.28 + $template = $twig->load('some_template.twig'); + + $twig->display('template.twig', array('template' => $template)); + +.. versionadded:: 1.2 + The ``ignore missing`` feature has been added in Twig 1.2. + +You can mark an include with ``ignore missing`` in which case Twig will ignore +the statement if the template to be included does not exist. It has to be +placed just after the template name. Here some valid examples: + +.. code-block:: jinja + + {% include 'sidebar.html' ignore missing %} + {% include 'sidebar.html' ignore missing with {'foo': 'bar'} %} + {% include 'sidebar.html' ignore missing only %} + +.. versionadded:: 1.2 + The possibility to pass an array of templates has been added in Twig 1.2. + +You can also provide a list of templates that are checked for existence before +inclusion. The first template that exists will be included: + +.. code-block:: jinja + + {% include ['page_detailed.html', 'page.html'] %} + +If ``ignore missing`` is given, it will fall back to rendering nothing if none +of the templates exist, otherwise it will throw an exception. diff --git a/src/vendor/twig/twig/doc/tags/index.rst b/src/vendor/twig/twig/doc/tags/index.rst new file mode 100644 index 0000000..dbe2459 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/index.rst @@ -0,0 +1,25 @@ +Tags +==== + +.. toctree:: + :maxdepth: 1 + + autoescape + block + do + embed + extends + filter + flush + for + from + if + import + include + macro + sandbox + set + spaceless + use + verbatim + with diff --git a/src/vendor/twig/twig/doc/tags/macro.rst b/src/vendor/twig/twig/doc/tags/macro.rst new file mode 100644 index 0000000..a8aa76f --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/macro.rst @@ -0,0 +1,103 @@ +``macro`` +========= + +Macros are comparable with functions in regular programming languages. They +are useful to put often used HTML idioms into reusable elements to not repeat +yourself. + +Here is a small example of a macro that renders a form element: + +.. code-block:: jinja + + {% macro input(name, value, type, size) %} + + {% endmacro %} + +Macros differ from native PHP functions in a few ways: + +* Default argument values are defined by using the ``default`` filter in the + macro body; + +* Arguments of a macro are always optional. + +* If extra positional arguments are passed to a macro, they end up in the + special ``varargs`` variable as a list of values. + +But as with PHP functions, macros don't have access to the current template +variables. + +.. tip:: + + You can pass the whole context as an argument by using the special + ``_context`` variable. + +Import +------ + +Macros can be defined in any template, and need to be "imported" before being +used (see the documentation for the :doc:`import<../tags/import>` tag for more +information): + +.. code-block:: jinja + + {% import "forms.html" as forms %} + +The above ``import`` call imports the "forms.html" file (which can contain only +macros, or a template and some macros), and import the functions as items of +the ``forms`` variable. + +The macro can then be called at will: + +.. code-block:: jinja + +

    {{ forms.input('username') }}

    +

    {{ forms.input('password', null, 'password') }}

    + +If macros are defined and used in the same template, you can use the +special ``_self`` variable to import them: + +.. code-block:: jinja + + {% import _self as forms %} + +

    {{ forms.input('username') }}

    + +.. warning:: + + When you define a macro in the template where you are going to use it, you + might be tempted to call the macro directly via ``_self.input()`` instead + of importing it; even if seems to work, this is just a side-effect of the + current implementation and it won't work anymore in Twig 2.x. + +When you want to use a macro in another macro from the same file, you need to +import it locally: + +.. code-block:: jinja + + {% macro input(name, value, type, size) %} + + {% endmacro %} + + {% macro wrapped_input(name, value, type, size) %} + {% import _self as forms %} + +
    + {{ forms.input(name, value, type, size) }} +
    + {% endmacro %} + +Named Macro End-Tags +-------------------- + +Twig allows you to put the name of the macro after the end tag for better +readability: + +.. code-block:: jinja + + {% macro input() %} + ... + {% endmacro input %} + +Of course, the name after the ``endmacro`` word must match the macro name. + +.. seealso:: :doc:`from<../tags/from>`, :doc:`import<../tags/import>` diff --git a/src/vendor/twig/twig/doc/tags/sandbox.rst b/src/vendor/twig/twig/doc/tags/sandbox.rst new file mode 100644 index 0000000..e186726 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/sandbox.rst @@ -0,0 +1,30 @@ +``sandbox`` +=========== + +The ``sandbox`` tag can be used to enable the sandboxing mode for an included +template, when sandboxing is not enabled globally for the Twig environment: + +.. code-block:: jinja + + {% sandbox %} + {% include 'user.html' %} + {% endsandbox %} + +.. warning:: + + The ``sandbox`` tag is only available when the sandbox extension is + enabled (see the :doc:`Twig for Developers<../api>` chapter). + +.. note:: + + The ``sandbox`` tag can only be used to sandbox an include tag and it + cannot be used to sandbox a section of a template. The following example + won't work: + + .. code-block:: jinja + + {% sandbox %} + {% for i in 1..2 %} + {{ i }} + {% endfor %} + {% endsandbox %} diff --git a/src/vendor/twig/twig/doc/tags/set.rst b/src/vendor/twig/twig/doc/tags/set.rst new file mode 100644 index 0000000..3eba239 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/set.rst @@ -0,0 +1,78 @@ +``set`` +======= + +Inside code blocks you can also assign values to variables. Assignments use +the ``set`` tag and can have multiple targets. + +Here is how you can assign the ``bar`` value to the ``foo`` variable: + +.. code-block:: jinja + + {% set foo = 'bar' %} + +After the ``set`` call, the ``foo`` variable is available in the template like +any other ones: + +.. code-block:: jinja + + {# displays bar #} + {{ foo }} + +The assigned value can be any valid :ref:`Twig expressions +`: + +.. code-block:: jinja + + {% set foo = [1, 2] %} + {% set foo = {'foo': 'bar'} %} + {% set foo = 'foo' ~ 'bar' %} + +Several variables can be assigned in one block: + +.. code-block:: jinja + + {% set foo, bar = 'foo', 'bar' %} + + {# is equivalent to #} + + {% set foo = 'foo' %} + {% set bar = 'bar' %} + +The ``set`` tag can also be used to 'capture' chunks of text: + +.. code-block:: jinja + + {% set foo %} + + {% endset %} + +.. caution:: + + If you enable automatic output escaping, Twig will only consider the + content to be safe when capturing chunks of text. + +.. note:: + + Note that loops are scoped in Twig; therefore a variable declared inside a + ``for`` loop is not accessible outside the loop itself: + + .. code-block:: jinja + + {% for item in list %} + {% set foo = item %} + {% endfor %} + + {# foo is NOT available #} + + If you want to access the variable, just declare it before the loop: + + .. code-block:: jinja + + {% set foo = "" %} + {% for item in list %} + {% set foo = item %} + {% endfor %} + + {# foo is available #} diff --git a/src/vendor/twig/twig/doc/tags/spaceless.rst b/src/vendor/twig/twig/doc/tags/spaceless.rst new file mode 100644 index 0000000..b39cb27 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/spaceless.rst @@ -0,0 +1,37 @@ +``spaceless`` +============= + +Use the ``spaceless`` tag to remove whitespace *between HTML tags*, not +whitespace within HTML tags or whitespace in plain text: + +.. code-block:: jinja + + {% spaceless %} +
    + foo +
    + {% endspaceless %} + + {# output will be
    foo
    #} + +This tag is not meant to "optimize" the size of the generated HTML content but +merely to avoid extra whitespace between HTML tags to avoid browser rendering +quirks under some circumstances. + +.. tip:: + + If you want to optimize the size of the generated HTML content, gzip + compress the output instead. + +.. tip:: + + If you want to create a tag that actually removes all extra whitespace in + an HTML string, be warned that this is not as easy as it seems to be + (think of ``textarea`` or ``pre`` tags for instance). Using a third-party + library like Tidy is probably a better idea. + +.. tip:: + + For more information on whitespace control, read the + :ref:`dedicated section ` of the documentation and learn how + you can also use the whitespace control modifier on your tags. diff --git a/src/vendor/twig/twig/doc/tags/use.rst b/src/vendor/twig/twig/doc/tags/use.rst new file mode 100644 index 0000000..a6fdefb --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/use.rst @@ -0,0 +1,124 @@ +``use`` +======= + +.. versionadded:: 1.1 + Horizontal reuse was added in Twig 1.1. + +.. note:: + + Horizontal reuse is an advanced Twig feature that is hardly ever needed in + regular templates. It is mainly used by projects that need to make + template blocks reusable without using inheritance. + +Template inheritance is one of the most powerful features of Twig but it is +limited to single inheritance; a template can only extend one other template. +This limitation makes template inheritance simple to understand and easy to +debug: + +.. code-block:: jinja + + {% extends "base.html" %} + + {% block title %}{% endblock %} + {% block content %}{% endblock %} + +Horizontal reuse is a way to achieve the same goal as multiple inheritance, +but without the associated complexity: + +.. code-block:: jinja + + {% extends "base.html" %} + + {% use "blocks.html" %} + + {% block title %}{% endblock %} + {% block content %}{% endblock %} + +The ``use`` statement tells Twig to import the blocks defined in +``blocks.html`` into the current template (it's like macros, but for blocks): + +.. code-block:: jinja + + {# blocks.html #} + + {% block sidebar %}{% endblock %} + +In this example, the ``use`` statement imports the ``sidebar`` block into the +main template. The code is mostly equivalent to the following one (the +imported blocks are not outputted automatically): + +.. code-block:: jinja + + {% extends "base.html" %} + + {% block sidebar %}{% endblock %} + {% block title %}{% endblock %} + {% block content %}{% endblock %} + +.. note:: + + The ``use`` tag only imports a template if it does not extend another + template, if it does not define macros, and if the body is empty. But it + can *use* other templates. + +.. note:: + + Because ``use`` statements are resolved independently of the context + passed to the template, the template reference cannot be an expression. + +The main template can also override any imported block. If the template +already defines the ``sidebar`` block, then the one defined in ``blocks.html`` +is ignored. To avoid name conflicts, you can rename imported blocks: + +.. code-block:: jinja + + {% extends "base.html" %} + + {% use "blocks.html" with sidebar as base_sidebar, title as base_title %} + + {% block sidebar %}{% endblock %} + {% block title %}{% endblock %} + {% block content %}{% endblock %} + +.. versionadded:: 1.3 + The ``parent()`` support was added in Twig 1.3. + +The ``parent()`` function automatically determines the correct inheritance +tree, so it can be used when overriding a block defined in an imported +template: + +.. code-block:: jinja + + {% extends "base.html" %} + + {% use "blocks.html" %} + + {% block sidebar %} + {{ parent() }} + {% endblock %} + + {% block title %}{% endblock %} + {% block content %}{% endblock %} + +In this example, ``parent()`` will correctly call the ``sidebar`` block from +the ``blocks.html`` template. + +.. tip:: + + In Twig 1.2, renaming allows you to simulate inheritance by calling the + "parent" block: + + .. code-block:: jinja + + {% extends "base.html" %} + + {% use "blocks.html" with sidebar as parent_sidebar %} + + {% block sidebar %} + {{ block('parent_sidebar') }} + {% endblock %} + +.. note:: + + You can use as many ``use`` statements as you want in any given template. + If two imported templates define the same block, the latest one wins. diff --git a/src/vendor/twig/twig/doc/tags/verbatim.rst b/src/vendor/twig/twig/doc/tags/verbatim.rst new file mode 100644 index 0000000..fe61ca1 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/verbatim.rst @@ -0,0 +1,24 @@ +``verbatim`` +============ + +.. versionadded:: 1.12 + The ``verbatim`` tag was added in Twig 1.12 (it was named ``raw`` before). + +The ``verbatim`` tag marks sections as being raw text that should not be +parsed. For example to put Twig syntax as example into a template you can use +this snippet: + +.. code-block:: jinja + + {% verbatim %} +
      + {% for item in seq %} +
    • {{ item }}
    • + {% endfor %} +
    + {% endverbatim %} + +.. note:: + + The ``verbatim`` tag works in the exact same way as the old ``raw`` tag, + but was renamed to avoid confusion with the ``raw`` filter. \ No newline at end of file diff --git a/src/vendor/twig/twig/doc/tags/with.rst b/src/vendor/twig/twig/doc/tags/with.rst new file mode 100644 index 0000000..815b069 --- /dev/null +++ b/src/vendor/twig/twig/doc/tags/with.rst @@ -0,0 +1,44 @@ +``with`` +======== + +.. versionadded:: 1.28 + The ``with`` tag was added in Twig 1.28. + +Use the ``with`` tag to create a new inner scope. Variables set within this +scope are not visible outside of the scope: + +.. code-block:: jinja + + {% with %} + {% set foo = 42 %} + {{ foo }} foo is 42 here + {% endwith %} + foo is not visible here any longer + +Instead of defining variables at the beginning of the scope, you can pass a +hash of variables you want to define in the ``with`` tag; the previous example +is equivalent to the following one: + +.. code-block:: jinja + + {% with { foo: 42 } %} + {{ foo }} foo is 42 here + {% endwith %} + foo is not visible here any longer + + {# it works with any expression that resolves to a hash #} + {% set vars = { foo: 42 } %} + {% with vars %} + ... + {% endwith %} + +By default, the inner scope has access to the outer scope context; you can +disable this behavior by appending the ``only`` keyword: + +.. code-block:: jinja + + {% set bar = 'bar' %} + {% with { foo: 42 } only %} + {# only foo is defined #} + {# bar is not defined #} + {% endwith %} diff --git a/src/vendor/twig/twig/doc/templates.rst b/src/vendor/twig/twig/doc/templates.rst new file mode 100644 index 0000000..8826c56 --- /dev/null +++ b/src/vendor/twig/twig/doc/templates.rst @@ -0,0 +1,908 @@ +Twig for Template Designers +=========================== + +This document describes the syntax and semantics of the template engine and +will be most useful as reference to those creating Twig templates. + +Synopsis +-------- + +A template is simply a text file. It can generate any text-based format (HTML, +XML, CSV, LaTeX, etc.). It doesn't have a specific extension, ``.html`` or +``.xml`` are just fine. + +A template contains **variables** or **expressions**, which get replaced with +values when the template is evaluated, and **tags**, which control the logic +of the template. + +Below is a minimal template that illustrates a few basics. We will cover further +details later on: + +.. code-block:: html+jinja + + + + + My Webpage + + + + +

    My Webpage

    + {{ a_variable }} + + + +There are two kinds of delimiters: ``{% ... %}`` and ``{{ ... }}``. The first +one is used to execute statements such as for-loops, the latter prints the +result of an expression to the template. + +IDEs Integration +---------------- + +Many IDEs support syntax highlighting and auto-completion for Twig: + +* *Textmate* via the `Twig bundle`_ +* *Vim* via the `Jinja syntax plugin`_ or the `vim-twig plugin`_ +* *Netbeans* via the `Twig syntax plugin`_ (until 7.1, native as of 7.2) +* *PhpStorm* (native as of 2.1) +* *Eclipse* via the `Twig plugin`_ +* *Sublime Text* via the `Twig bundle`_ +* *GtkSourceView* via the `Twig language definition`_ (used by gedit and other projects) +* *Coda* and *SubEthaEdit* via the `Twig syntax mode`_ +* *Coda 2* via the `other Twig syntax mode`_ +* *Komodo* and *Komodo Edit* via the Twig highlight/syntax check mode +* *Notepad++* via the `Notepad++ Twig Highlighter`_ +* *Emacs* via `web-mode.el`_ +* *Atom* via the `PHP-twig for atom`_ +* *Visual Studio Code* via the `Twig pack`_ + +Also, `TwigFiddle`_ is an online service that allows you to execute Twig templates +from a browser; it supports all versions of Twig. + +Variables +--------- + +The application passes variables to the templates for manipulation in the +template. Variables may have attributes or elements you can access, +too. The visual representation of a variable depends heavily on the application providing +it. + +You can use a dot (``.``) to access attributes of a variable (methods or +properties of a PHP object, or items of a PHP array), or the so-called +"subscript" syntax (``[]``): + +.. code-block:: jinja + + {{ foo.bar }} + {{ foo['bar'] }} + +When the attribute contains special characters (like ``-`` that would be +interpreted as the minus operator), use the ``attribute`` function instead to +access the variable attribute: + +.. code-block:: jinja + + {# equivalent to the non-working foo.data-foo #} + {{ attribute(foo, 'data-foo') }} + +.. note:: + + It's important to know that the curly braces are *not* part of the + variable but the print statement. When accessing variables inside tags, + don't put the braces around them. + +If a variable or attribute does not exist, you will receive a ``null`` value +when the ``strict_variables`` option is set to ``false``; alternatively, if ``strict_variables`` +is set, Twig will throw an error (see :ref:`environment options`). + +.. sidebar:: Implementation + + For convenience's sake ``foo.bar`` does the following things on the PHP + layer: + + * check if ``foo`` is an array and ``bar`` a valid element; + * if not, and if ``foo`` is an object, check that ``bar`` is a valid property; + * if not, and if ``foo`` is an object, check that ``bar`` is a valid method + (even if ``bar`` is the constructor - use ``__construct()`` instead); + * if not, and if ``foo`` is an object, check that ``getBar`` is a valid method; + * if not, and if ``foo`` is an object, check that ``isBar`` is a valid method; + * if not, return a ``null`` value. + + ``foo['bar']`` on the other hand only works with PHP arrays: + + * check if ``foo`` is an array and ``bar`` a valid element; + * if not, return a ``null`` value. + +.. note:: + + If you want to access a dynamic attribute of a variable, use the + :doc:`attribute` function instead. + +Global Variables +~~~~~~~~~~~~~~~~ + +The following variables are always available in templates: + +* ``_self``: references the current template; +* ``_context``: references the current context; +* ``_charset``: references the current charset. + +Setting Variables +~~~~~~~~~~~~~~~~~ + +You can assign values to variables inside code blocks. Assignments use the +:doc:`set` tag: + +.. code-block:: jinja + + {% set foo = 'foo' %} + {% set foo = [1, 2] %} + {% set foo = {'foo': 'bar'} %} + +Filters +------- + +Variables can be modified by **filters**. Filters are separated from the +variable by a pipe symbol (``|``) and may have optional arguments in +parentheses. Multiple filters can be chained. The output of one filter is +applied to the next. + +The following example removes all HTML tags from the ``name`` and title-cases +it: + +.. code-block:: jinja + + {{ name|striptags|title }} + +Filters that accept arguments have parentheses around the arguments. This +example will join a list by commas: + +.. code-block:: jinja + + {{ list|join(', ') }} + +To apply a filter on a section of code, wrap it in the +:doc:`filter` tag: + +.. code-block:: jinja + + {% filter upper %} + This text becomes uppercase + {% endfilter %} + +Go to the :doc:`filters` page to learn more about built-in +filters. + +Functions +--------- + +Functions can be called to generate content. Functions are called by their +name followed by parentheses (``()``) and may have arguments. + +For instance, the ``range`` function returns a list containing an arithmetic +progression of integers: + +.. code-block:: jinja + + {% for i in range(0, 3) %} + {{ i }}, + {% endfor %} + +Go to the :doc:`functions` page to learn more about the +built-in functions. + +Named Arguments +--------------- + +.. versionadded:: 1.12 + Support for named arguments was added in Twig 1.12. + +.. code-block:: jinja + + {% for i in range(low=1, high=10, step=2) %} + {{ i }}, + {% endfor %} + +Using named arguments makes your templates more explicit about the meaning of +the values you pass as arguments: + +.. code-block:: jinja + + {{ data|convert_encoding('UTF-8', 'iso-2022-jp') }} + + {# versus #} + + {{ data|convert_encoding(from='iso-2022-jp', to='UTF-8') }} + +Named arguments also allow you to skip some arguments for which you don't want +to change the default value: + +.. code-block:: jinja + + {# the first argument is the date format, which defaults to the global date format if null is passed #} + {{ "now"|date(null, "Europe/Paris") }} + + {# or skip the format value by using a named argument for the time zone #} + {{ "now"|date(timezone="Europe/Paris") }} + +You can also use both positional and named arguments in one call, in which +case positional arguments must always come before named arguments: + +.. code-block:: jinja + + {{ "now"|date('d/m/Y H:i', timezone="Europe/Paris") }} + +.. tip:: + + Each function and filter documentation page has a section where the names + of all arguments are listed when supported. + +Control Structure +----------------- + +A control structure refers to all those things that control the flow of a +program - conditionals (i.e. ``if``/``elseif``/``else``), ``for``-loops, as +well as things like blocks. Control structures appear inside ``{% ... %}`` +blocks. + +For example, to display a list of users provided in a variable called +``users``, use the :doc:`for` tag: + +.. code-block:: jinja + +

    Members

    +
      + {% for user in users %} +
    • {{ user.username|e }}
    • + {% endfor %} +
    + +The :doc:`if` tag can be used to test an expression: + +.. code-block:: jinja + + {% if users|length > 0 %} +
      + {% for user in users %} +
    • {{ user.username|e }}
    • + {% endfor %} +
    + {% endif %} + +Go to the :doc:`tags` page to learn more about the built-in tags. + +Comments +-------- + +To comment-out part of a line in a template, use the comment syntax ``{# ... +#}``. This is useful for debugging or to add information for other template +designers or yourself: + +.. code-block:: jinja + + {# note: disabled template because we no longer use this + {% for user in users %} + ... + {% endfor %} + #} + +Including other Templates +------------------------- + +The :doc:`include` function is useful to include a template +and return the rendered content of that template into the current one: + +.. code-block:: jinja + + {{ include('sidebar.html') }} + +By default, included templates have access to the same context as the template +which includes them. This means that any variable defined in the main template +will be available in the included template too: + +.. code-block:: jinja + + {% for box in boxes %} + {{ include('render_box.html') }} + {% endfor %} + +The included template ``render_box.html`` is able to access the ``box`` variable. + +The filename of the template depends on the template loader. For instance, the +``Twig_Loader_Filesystem`` allows you to access other templates by giving the +filename. You can access templates in subdirectories with a slash: + +.. code-block:: jinja + + {{ include('sections/articles/sidebar.html') }} + +This behavior depends on the application embedding Twig. + +Template Inheritance +-------------------- + +The most powerful part of Twig is template inheritance. Template inheritance +allows you to build a base "skeleton" template that contains all the common +elements of your site and defines **blocks** that child templates can +override. + +Sounds complicated but it is very basic. It's easier to understand it by +starting with an example. + +Let's define a base template, ``base.html``, which defines a simple HTML +skeleton document that you might use for a simple two-column page: + +.. code-block:: html+jinja + + + + + {% block head %} + + {% block title %}{% endblock %} - My Webpage + {% endblock %} + + +
    {% block content %}{% endblock %}
    + + + + +In this example, the :doc:`block` tags define four blocks that +child templates can fill in. All the ``block`` tag does is to tell the +template engine that a child template may override those portions of the +template. + +A child template might look like this: + +.. code-block:: jinja + + {% extends "base.html" %} + + {% block title %}Index{% endblock %} + {% block head %} + {{ parent() }} + + {% endblock %} + {% block content %} +

    Index

    +

    + Welcome to my awesome homepage. +

    + {% endblock %} + +The :doc:`extends` tag is the key here. It tells the template +engine that this template "extends" another template. When the template system +evaluates this template, first it locates the parent. The extends tag should +be the first tag in the template. + +Note that since the child template doesn't define the ``footer`` block, the +value from the parent template is used instead. + +It's possible to render the contents of the parent block by using the +:doc:`parent` function. This gives back the results of the +parent block: + +.. code-block:: jinja + + {% block sidebar %} +

    Table Of Contents

    + ... + {{ parent() }} + {% endblock %} + +.. tip:: + + The documentation page for the :doc:`extends` tag describes + more advanced features like block nesting, scope, dynamic inheritance, and + conditional inheritance. + +.. note:: + + Twig also supports multiple inheritance with the so called horizontal reuse + with the help of the :doc:`use` tag. This is an advanced feature + hardly ever needed in regular templates. + +HTML Escaping +------------- + +When generating HTML from templates, there's always a risk that a variable +will include characters that affect the resulting HTML. There are two +approaches: manually escaping each variable or automatically escaping +everything by default. + +Twig supports both, automatic escaping is enabled by default. + +The automatic escaping strategy can be configured via the +:ref:`autoescape` option and defaults to ``html``. + +Working with Manual Escaping +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If manual escaping is enabled, it is **your** responsibility to escape +variables if needed. What to escape? Any variable you don't trust. + +Escaping works by piping the variable through the +:doc:`escape` or ``e`` filter: + +.. code-block:: jinja + + {{ user.username|e }} + +By default, the ``escape`` filter uses the ``html`` strategy, but depending on +the escaping context, you might want to explicitly use any other available +strategies: + +.. code-block:: jinja + + {{ user.username|e('js') }} + {{ user.username|e('css') }} + {{ user.username|e('url') }} + {{ user.username|e('html_attr') }} + +Working with Automatic Escaping +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Whether automatic escaping is enabled or not, you can mark a section of a +template to be escaped or not by using the :doc:`autoescape` +tag: + +.. code-block:: jinja + + {% autoescape %} + Everything will be automatically escaped in this block (using the HTML strategy) + {% endautoescape %} + +By default, auto-escaping uses the ``html`` escaping strategy. If you output +variables in other contexts, you need to explicitly escape them with the +appropriate escaping strategy: + +.. code-block:: jinja + + {% autoescape 'js' %} + Everything will be automatically escaped in this block (using the JS strategy) + {% endautoescape %} + +Escaping +-------- + +It is sometimes desirable or even necessary to have Twig ignore parts it would +otherwise handle as variables or blocks. For example if the default syntax is +used and you want to use ``{{`` as raw string in the template and not start a +variable you have to use a trick. + +The easiest way is to output the variable delimiter (``{{``) by using a variable +expression: + +.. code-block:: jinja + + {{ '{{' }} + +For bigger sections it makes sense to mark a block +:doc:`verbatim`. + +Macros +------ + +.. versionadded:: 1.12 + Support for default argument values was added in Twig 1.12. + +Macros are comparable with functions in regular programming languages. They +are useful to reuse often used HTML fragments to not repeat yourself. + +A macro is defined via the :doc:`macro` tag. Here is a small example +(subsequently called ``forms.html``) of a macro that renders a form element: + +.. code-block:: jinja + + {% macro input(name, value, type, size) %} + + {% endmacro %} + +Macros can be defined in any template, and need to be "imported" via the +:doc:`import` tag before being used: + +.. code-block:: jinja + + {% import "forms.html" as forms %} + +

    {{ forms.input('username') }}

    + +Alternatively, you can import individual macro names from a template into the +current namespace via the :doc:`from` tag and optionally alias them: + +.. code-block:: jinja + + {% from 'forms.html' import input as input_field %} + +
    +
    Username
    +
    {{ input_field('username') }}
    +
    Password
    +
    {{ input_field('password', '', 'password') }}
    +
    + +A default value can also be defined for macro arguments when not provided in a +macro call: + +.. code-block:: jinja + + {% macro input(name, value = "", type = "text", size = 20) %} + + {% endmacro %} + +If extra positional arguments are passed to a macro call, they end up in the +special ``varargs`` variable as a list of values. + +.. _twig-expressions: + +Expressions +----------- + +Twig allows expressions everywhere. These work very similar to regular PHP and +even if you're not working with PHP you should feel comfortable with it. + +.. note:: + + The operator precedence is as follows, with the lowest-precedence + operators listed first: ``b-and``, ``b-xor``, ``b-or``, ``or``, ``and``, + ``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``, ``in``, ``matches``, + ``starts with``, ``ends with``, ``..``, ``+``, ``-``, ``~``, ``*``, ``/``, + ``//``, ``%``, ``is``, ``**``, ``|``, ``[]``, and ``.``: + + .. code-block:: jinja + + {% set greeting = 'Hello ' %} + {% set name = 'Fabien' %} + + {{ greeting ~ name|lower }} {# Hello fabien #} + + {# use parenthesis to change precedence #} + {{ (greeting ~ name)|lower }} {# hello fabien #} + +Literals +~~~~~~~~ + +.. versionadded:: 1.5 + Support for hash keys as names and expressions was added in Twig 1.5. + +The simplest form of expressions are literals. Literals are representations +for PHP types such as strings, numbers, and arrays. The following literals +exist: + +* ``"Hello World"``: Everything between two double or single quotes is a + string. They are useful whenever you need a string in the template (for + example as arguments to function calls, filters or just to extend or include + a template). A string can contain a delimiter if it is preceded by a + backslash (``\``) -- like in ``'It\'s good'``. If the string contains a + backslash (e.g. ``'c:\Program Files'``) escape it by doubling it + (e.g. ``'c:\\Program Files'``). + +* ``42`` / ``42.23``: Integers and floating point numbers are created by just + writing the number down. If a dot is present the number is a float, + otherwise an integer. + +* ``["foo", "bar"]``: Arrays are defined by a sequence of expressions + separated by a comma (``,``) and wrapped with squared brackets (``[]``). + +* ``{"foo": "bar"}``: Hashes are defined by a list of keys and values + separated by a comma (``,``) and wrapped with curly braces (``{}``): + + .. code-block:: jinja + + {# keys as string #} + { 'foo': 'foo', 'bar': 'bar' } + + {# keys as names (equivalent to the previous hash) -- as of Twig 1.5 #} + { foo: 'foo', bar: 'bar' } + + {# keys as integer #} + { 2: 'foo', 4: 'bar' } + + {# keys as expressions (the expression must be enclosed into parentheses) -- as of Twig 1.5 #} + { (1 + 1): 'foo', (a ~ 'b'): 'bar' } + +* ``true`` / ``false``: ``true`` represents the true value, ``false`` + represents the false value. + +* ``null``: ``null`` represents no specific value. This is the value returned + when a variable does not exist. ``none`` is an alias for ``null``. + +Arrays and hashes can be nested: + +.. code-block:: jinja + + {% set foo = [1, {"foo": "bar"}] %} + +.. tip:: + + Using double-quoted or single-quoted strings has no impact on performance + but string interpolation is only supported in double-quoted strings. + +Math +~~~~ + +Twig allows you to calculate with values. This is rarely useful in templates +but exists for completeness' sake. The following operators are supported: + +* ``+``: Adds two objects together (the operands are casted to numbers). ``{{ + 1 + 1 }}`` is ``2``. + +* ``-``: Subtracts the second number from the first one. ``{{ 3 - 2 }}`` is + ``1``. + +* ``/``: Divides two numbers. The returned value will be a floating point + number. ``{{ 1 / 2 }}`` is ``{{ 0.5 }}``. + +* ``%``: Calculates the remainder of an integer division. ``{{ 11 % 7 }}`` is + ``4``. + +* ``//``: Divides two numbers and returns the floored integer result. ``{{ 20 + // 7 }}`` is ``2``, ``{{ -20 // 7 }}`` is ``-3`` (this is just syntactic + sugar for the :doc:`round` filter). + +* ``*``: Multiplies the left operand with the right one. ``{{ 2 * 2 }}`` would + return ``4``. + +* ``**``: Raises the left operand to the power of the right operand. ``{{ 2 ** + 3 }}`` would return ``8``. + +Logic +~~~~~ + +You can combine multiple expressions with the following operators: + +* ``and``: Returns true if the left and the right operands are both true. + +* ``or``: Returns true if the left or the right operand is true. + +* ``not``: Negates a statement. + +* ``(expr)``: Groups an expression. + +.. note:: + + Twig also support bitwise operators (``b-and``, ``b-xor``, and ``b-or``). + +.. note:: + + Operators are case sensitive. + +Comparisons +~~~~~~~~~~~ + +The following comparison operators are supported in any expression: ``==``, +``!=``, ``<``, ``>``, ``>=``, and ``<=``. + +You can also check if a string ``starts with`` or ``ends with`` another +string: + +.. code-block:: jinja + + {% if 'Fabien' starts with 'F' %} + {% endif %} + + {% if 'Fabien' ends with 'n' %} + {% endif %} + +.. note:: + + For complex string comparisons, the ``matches`` operator allows you to use + `regular expressions`_: + + .. code-block:: jinja + + {% if phone matches '/^[\\d\\.]+$/' %} + {% endif %} + +Containment Operator +~~~~~~~~~~~~~~~~~~~~ + +The ``in`` operator performs containment test. + +It returns ``true`` if the left operand is contained in the right: + +.. code-block:: jinja + + {# returns true #} + + {{ 1 in [1, 2, 3] }} + + {{ 'cd' in 'abcde' }} + +.. tip:: + + You can use this filter to perform a containment test on strings, arrays, + or objects implementing the ``Traversable`` interface. + +To perform a negative test, use the ``not in`` operator: + +.. code-block:: jinja + + {% if 1 not in [1, 2, 3] %} + + {# is equivalent to #} + {% if not (1 in [1, 2, 3]) %} + +Test Operator +~~~~~~~~~~~~~ + +The ``is`` operator performs tests. Tests can be used to test a variable against +a common expression. The right operand is name of the test: + +.. code-block:: jinja + + {# find out if a variable is odd #} + + {{ name is odd }} + +Tests can accept arguments too: + +.. code-block:: jinja + + {% if post.status is constant('Post::PUBLISHED') %} + +Tests can be negated by using the ``is not`` operator: + +.. code-block:: jinja + + {% if post.status is not constant('Post::PUBLISHED') %} + + {# is equivalent to #} + {% if not (post.status is constant('Post::PUBLISHED')) %} + +Go to the :doc:`tests` page to learn more about the built-in +tests. + +Other Operators +~~~~~~~~~~~~~~~ + +.. versionadded:: 1.12.0 + Support for the extended ternary operator was added in Twig 1.12.0. + +The following operators don't fit into any of the other categories: + +* ``|``: Applies a filter. + +* ``..``: Creates a sequence based on the operand before and after the operator + (this is just syntactic sugar for the :doc:`range` function): + + .. code-block:: jinja + + {{ 1..5 }} + + {# equivalent to #} + {{ range(1, 5) }} + + Note that you must use parentheses when combining it with the filter operator + due to the :ref:`operator precedence rules `: + + .. code-block:: jinja + + (1..5)|join(', ') + +* ``~``: Converts all operands into strings and concatenates them. ``{{ "Hello + " ~ name ~ "!" }}`` would return (assuming ``name`` is ``'John'``) ``Hello + John!``. + +* ``.``, ``[]``: Gets an attribute of an object. + +* ``?:``: The ternary operator: + + .. code-block:: jinja + + {{ foo ? 'yes' : 'no' }} + + {# as of Twig 1.12.0 #} + {{ foo ?: 'no' }} is the same as {{ foo ? foo : 'no' }} + {{ foo ? 'yes' }} is the same as {{ foo ? 'yes' : '' }} + +* ``??``: The null-coalescing operator: + + .. code-block:: jinja + + {# returns the value of foo if it is defined and not null, 'no' otherwise #} + {{ foo ?? 'no' }} + +String Interpolation +~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.5 + String interpolation was added in Twig 1.5. + +String interpolation (``#{expression}``) allows any valid expression to appear +within a *double-quoted string*. The result of evaluating that expression is +inserted into the string: + +.. code-block:: jinja + + {{ "foo #{bar} baz" }} + {{ "foo #{1 + 2} baz" }} + +.. _templates-whitespace-control: + +Whitespace Control +------------------ + +.. versionadded:: 1.1 + Tag level whitespace control was added in Twig 1.1. + +The first newline after a template tag is removed automatically (like in PHP.) +Whitespace is not further modified by the template engine, so each whitespace +(spaces, tabs, newlines etc.) is returned unchanged. + +Use the ``spaceless`` tag to remove whitespace *between HTML tags*: + +.. code-block:: jinja + + {% spaceless %} +
    + foo bar +
    + {% endspaceless %} + + {# output will be
    foo bar
    #} + +In addition to the spaceless tag you can also control whitespace on a per tag +level. By using the whitespace control modifier on your tags, you can trim +leading and or trailing whitespace: + +.. code-block:: jinja + + {% set value = 'no spaces' %} + {#- No leading/trailing whitespace -#} + {%- if true -%} + {{- value -}} + {%- endif -%} + + {# output 'no spaces' #} + +The above sample shows the default whitespace control modifier, and how you can +use it to remove whitespace around tags. Trimming space will consume all whitespace +for that side of the tag. It is possible to use whitespace trimming on one side +of a tag: + +.. code-block:: jinja + + {% set value = 'no spaces' %} +
  • {{- value }}
  • + + {# outputs '
  • no spaces
  • ' #} + +Extensions +---------- + +Twig can be easily extended. + +If you are looking for new tags, filters, or functions, have a look at the Twig official +`extension repository`_. + +If you want to create your own, read the :ref:`Creating an +Extension` chapter. + +.. _`Twig bundle`: https://github.com/Anomareh/PHP-Twig.tmbundle +.. _`Jinja syntax plugin`: http://jinja.pocoo.org/docs/integration/#vim +.. _`vim-twig plugin`: https://github.com/evidens/vim-twig +.. _`Twig syntax plugin`: http://plugins.netbeans.org/plugin/37069/php-twig +.. _`Twig plugin`: https://github.com/pulse00/Twig-Eclipse-Plugin +.. _`Twig language definition`: https://github.com/gabrielcorpse/gedit-twig-template-language +.. _`extension repository`: http://github.com/twigphp/Twig-extensions +.. _`Twig syntax mode`: https://github.com/bobthecow/Twig-HTML.mode +.. _`other Twig syntax mode`: https://github.com/muxx/Twig-HTML.mode +.. _`Notepad++ Twig Highlighter`: https://github.com/Banane9/notepadplusplus-twig +.. _`web-mode.el`: http://web-mode.org/ +.. _`regular expressions`: http://php.net/manual/en/pcre.pattern.php +.. _`PHP-twig for atom`: https://github.com/reesef/php-twig +.. _`TwigFiddle`: http://twigfiddle.com/ +.. _`Twig pack`: https://marketplace.visualstudio.com/items?itemName=bajdzis.vscode-twig-pack diff --git a/src/vendor/twig/twig/doc/tests/constant.rst b/src/vendor/twig/twig/doc/tests/constant.rst new file mode 100644 index 0000000..8d0724a --- /dev/null +++ b/src/vendor/twig/twig/doc/tests/constant.rst @@ -0,0 +1,22 @@ +``constant`` +============ + +.. versionadded: 1.13.1 + constant now accepts object instances as the second argument. + +``constant`` checks if a variable has the exact same value as a constant. You +can use either global constants or class constants: + +.. code-block:: jinja + + {% if post.status is constant('Post::PUBLISHED') %} + the status attribute is exactly the same as Post::PUBLISHED + {% endif %} + +You can test constants from object instances as well: + +.. code-block:: jinja + + {% if post.status is constant('PUBLISHED', post) %} + the status attribute is exactly the same as Post::PUBLISHED + {% endif %} diff --git a/src/vendor/twig/twig/doc/tests/defined.rst b/src/vendor/twig/twig/doc/tests/defined.rst new file mode 100644 index 0000000..702ce72 --- /dev/null +++ b/src/vendor/twig/twig/doc/tests/defined.rst @@ -0,0 +1,30 @@ +``defined`` +=========== + +``defined`` checks if a variable is defined in the current context. This is very +useful if you use the ``strict_variables`` option: + +.. code-block:: jinja + + {# defined works with variable names #} + {% if foo is defined %} + ... + {% endif %} + + {# and attributes on variables names #} + {% if foo.bar is defined %} + ... + {% endif %} + + {% if foo['bar'] is defined %} + ... + {% endif %} + +When using the ``defined`` test on an expression that uses variables in some +method calls, be sure that they are all defined first: + +.. code-block:: jinja + + {% if var is defined and foo.method(var) is defined %} + ... + {% endif %} diff --git a/src/vendor/twig/twig/doc/tests/divisibleby.rst b/src/vendor/twig/twig/doc/tests/divisibleby.rst new file mode 100644 index 0000000..6c693b2 --- /dev/null +++ b/src/vendor/twig/twig/doc/tests/divisibleby.rst @@ -0,0 +1,14 @@ +``divisible by`` +================ + +.. versionadded:: 1.14.2 + The ``divisible by`` test was added in Twig 1.14.2 as an alias for + ``divisibleby``. + +``divisible by`` checks if a variable is divisible by a number: + +.. code-block:: jinja + + {% if loop.index is divisible by(3) %} + ... + {% endif %} diff --git a/src/vendor/twig/twig/doc/tests/empty.rst b/src/vendor/twig/twig/doc/tests/empty.rst new file mode 100644 index 0000000..c214d50 --- /dev/null +++ b/src/vendor/twig/twig/doc/tests/empty.rst @@ -0,0 +1,11 @@ +``empty`` +========= + +``empty`` checks if a variable is an empty string, an empty array, an empty +hash, exactly ``false``, or exactly ``null``: + +.. code-block:: jinja + + {% if foo is empty %} + ... + {% endif %} diff --git a/src/vendor/twig/twig/doc/tests/even.rst b/src/vendor/twig/twig/doc/tests/even.rst new file mode 100644 index 0000000..6ab5cc3 --- /dev/null +++ b/src/vendor/twig/twig/doc/tests/even.rst @@ -0,0 +1,10 @@ +``even`` +======== + +``even`` returns ``true`` if the given number is even: + +.. code-block:: jinja + + {{ var is even }} + +.. seealso:: :doc:`odd<../tests/odd>` diff --git a/src/vendor/twig/twig/doc/tests/index.rst b/src/vendor/twig/twig/doc/tests/index.rst new file mode 100644 index 0000000..c63208e --- /dev/null +++ b/src/vendor/twig/twig/doc/tests/index.rst @@ -0,0 +1,15 @@ +Tests +===== + +.. toctree:: + :maxdepth: 1 + + constant + defined + divisibleby + empty + even + iterable + null + odd + sameas diff --git a/src/vendor/twig/twig/doc/tests/iterable.rst b/src/vendor/twig/twig/doc/tests/iterable.rst new file mode 100644 index 0000000..89a172f --- /dev/null +++ b/src/vendor/twig/twig/doc/tests/iterable.rst @@ -0,0 +1,19 @@ +``iterable`` +============ + +.. versionadded:: 1.7 + The iterable test was added in Twig 1.7. + +``iterable`` checks if a variable is an array or a traversable object: + +.. code-block:: jinja + + {# evaluates to true if the foo variable is iterable #} + {% if users is iterable %} + {% for user in users %} + Hello {{ user }}! + {% endfor %} + {% else %} + {# users is probably a string #} + Hello {{ users }}! + {% endif %} diff --git a/src/vendor/twig/twig/doc/tests/null.rst b/src/vendor/twig/twig/doc/tests/null.rst new file mode 100644 index 0000000..44eec62 --- /dev/null +++ b/src/vendor/twig/twig/doc/tests/null.rst @@ -0,0 +1,12 @@ +``null`` +======== + +``null`` returns ``true`` if the variable is ``null``: + +.. code-block:: jinja + + {{ var is null }} + +.. note:: + + ``none`` is an alias for ``null``. diff --git a/src/vendor/twig/twig/doc/tests/odd.rst b/src/vendor/twig/twig/doc/tests/odd.rst new file mode 100644 index 0000000..9eece77 --- /dev/null +++ b/src/vendor/twig/twig/doc/tests/odd.rst @@ -0,0 +1,10 @@ +``odd`` +======= + +``odd`` returns ``true`` if the given number is odd: + +.. code-block:: jinja + + {{ var is odd }} + +.. seealso:: :doc:`even<../tests/even>` diff --git a/src/vendor/twig/twig/doc/tests/sameas.rst b/src/vendor/twig/twig/doc/tests/sameas.rst new file mode 100644 index 0000000..16f904d --- /dev/null +++ b/src/vendor/twig/twig/doc/tests/sameas.rst @@ -0,0 +1,14 @@ +``same as`` +=========== + +.. versionadded:: 1.14.2 + The ``same as`` test was added in Twig 1.14.2 as an alias for ``sameas``. + +``same as`` checks if a variable is the same as another variable. +This is the equivalent to ``===`` in PHP: + +.. code-block:: jinja + + {% if foo.attribute is same as(false) %} + the foo attribute really is the 'false' PHP value + {% endif %} diff --git a/src/vendor/twig/twig/ext/twig/.gitignore b/src/vendor/twig/twig/ext/twig/.gitignore new file mode 100644 index 0000000..56ea76c --- /dev/null +++ b/src/vendor/twig/twig/ext/twig/.gitignore @@ -0,0 +1,30 @@ +*.sw* +.deps +Makefile +Makefile.fragments +Makefile.global +Makefile.objects +acinclude.m4 +aclocal.m4 +build/ +config.cache +config.guess +config.h +config.h.in +config.log +config.nice +config.status +config.sub +configure +configure.in +install-sh +libtool +ltmain.sh +missing +mkinstalldirs +run-tests.php +twig.loT +.libs/ +modules/ +twig.la +twig.lo diff --git a/src/vendor/twig/twig/ext/twig/config.m4 b/src/vendor/twig/twig/ext/twig/config.m4 new file mode 100644 index 0000000..83486be --- /dev/null +++ b/src/vendor/twig/twig/ext/twig/config.m4 @@ -0,0 +1,8 @@ +dnl config.m4 for extension twig + +PHP_ARG_ENABLE(twig, whether to enable twig support, +[ --enable-twig Enable twig support]) + +if test "$PHP_TWIG" != "no"; then + PHP_NEW_EXTENSION(twig, twig.c, $ext_shared) +fi diff --git a/src/vendor/twig/twig/ext/twig/config.w32 b/src/vendor/twig/twig/ext/twig/config.w32 new file mode 100644 index 0000000..cb287b9 --- /dev/null +++ b/src/vendor/twig/twig/ext/twig/config.w32 @@ -0,0 +1,8 @@ +// vim:ft=javascript + +ARG_ENABLE("twig", "Twig support", "no"); + +if (PHP_TWIG != "no") { + AC_DEFINE('HAVE_TWIG', 1); + EXTENSION('twig', 'twig.c'); +} diff --git a/src/vendor/twig/twig/ext/twig/php_twig.h b/src/vendor/twig/twig/ext/twig/php_twig.h new file mode 100644 index 0000000..0500b3c --- /dev/null +++ b/src/vendor/twig/twig/ext/twig/php_twig.h @@ -0,0 +1,35 @@ +/* + +----------------------------------------------------------------------+ + | Twig Extension | + +----------------------------------------------------------------------+ + | Copyright (c) 2011 Derick Rethans | + +----------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met (BSD-3-Clause). | + +----------------------------------------------------------------------+ + | Author: Derick Rethans | + +----------------------------------------------------------------------+ + */ + +#ifndef PHP_TWIG_H +#define PHP_TWIG_H + +#define PHP_TWIG_VERSION "1.29.0" + +#include "php.h" + +extern zend_module_entry twig_module_entry; +#define phpext_twig_ptr &twig_module_entry +#ifndef PHP_WIN32 +zend_module_entry *get_module(void); +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +PHP_FUNCTION(twig_template_get_attributes); +PHP_RSHUTDOWN_FUNCTION(twig); + +#endif diff --git a/src/vendor/twig/twig/ext/twig/twig.c b/src/vendor/twig/twig/ext/twig/twig.c new file mode 100644 index 0000000..08171a3 --- /dev/null +++ b/src/vendor/twig/twig/ext/twig/twig.c @@ -0,0 +1,1127 @@ +/* + +----------------------------------------------------------------------+ + | Twig Extension | + +----------------------------------------------------------------------+ + | Copyright (c) 2011 Derick Rethans | + +----------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met (BSD-3-Clause). | + +----------------------------------------------------------------------+ + | Author: Derick Rethans | + +----------------------------------------------------------------------+ + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_twig.h" +#include "ext/standard/php_var.h" +#include "ext/standard/php_string.h" +#include "ext/standard/php_smart_str.h" +#include "ext/spl/spl_exceptions.h" + +#include "Zend/zend_object_handlers.h" +#include "Zend/zend_interfaces.h" +#include "Zend/zend_exceptions.h" + +#ifndef Z_ADDREF_P +#define Z_ADDREF_P(pz) (pz)->refcount++ +#endif + +#define FREE_DTOR(z) \ + zval_dtor(z); \ + efree(z); + +#if PHP_VERSION_ID >= 50300 + #define APPLY_TSRMLS_DC TSRMLS_DC + #define APPLY_TSRMLS_CC TSRMLS_CC + #define APPLY_TSRMLS_FETCH() +#else + #define APPLY_TSRMLS_DC + #define APPLY_TSRMLS_CC + #define APPLY_TSRMLS_FETCH() TSRMLS_FETCH() +#endif + +ZEND_BEGIN_ARG_INFO_EX(twig_template_get_attribute_args, ZEND_SEND_BY_VAL, ZEND_RETURN_VALUE, 6) + ZEND_ARG_INFO(0, template) + ZEND_ARG_INFO(0, object) + ZEND_ARG_INFO(0, item) + ZEND_ARG_INFO(0, arguments) + ZEND_ARG_INFO(0, type) + ZEND_ARG_INFO(0, isDefinedTest) +ZEND_END_ARG_INFO() + +#ifndef PHP_FE_END +#define PHP_FE_END { NULL, NULL, NULL} +#endif + +static const zend_function_entry twig_functions[] = { + PHP_FE(twig_template_get_attributes, twig_template_get_attribute_args) + PHP_FE_END +}; + +PHP_RSHUTDOWN_FUNCTION(twig) +{ +#if ZEND_DEBUG + CG(unclean_shutdown) = 0; /* get rid of PHPUnit's exit() and report memleaks */ +#endif + return SUCCESS; +} + +zend_module_entry twig_module_entry = { + STANDARD_MODULE_HEADER, + "twig", + twig_functions, + NULL, + NULL, + NULL, + PHP_RSHUTDOWN(twig), + NULL, + PHP_TWIG_VERSION, + STANDARD_MODULE_PROPERTIES +}; + + +#ifdef COMPILE_DL_TWIG +ZEND_GET_MODULE(twig) +#endif + +static int TWIG_ARRAY_KEY_EXISTS(zval *array, zval *key) +{ + if (Z_TYPE_P(array) != IS_ARRAY) { + return 0; + } + + switch (Z_TYPE_P(key)) { + case IS_NULL: + return zend_hash_exists(Z_ARRVAL_P(array), "", 1); + + case IS_BOOL: + case IS_DOUBLE: + convert_to_long(key); + case IS_LONG: + return zend_hash_index_exists(Z_ARRVAL_P(array), Z_LVAL_P(key)); + + default: + convert_to_string(key); + return zend_symtable_exists(Z_ARRVAL_P(array), Z_STRVAL_P(key), Z_STRLEN_P(key) + 1); + } +} + +static int TWIG_INSTANCE_OF(zval *object, zend_class_entry *interface TSRMLS_DC) +{ + if (Z_TYPE_P(object) != IS_OBJECT) { + return 0; + } + return instanceof_function(Z_OBJCE_P(object), interface TSRMLS_CC); +} + +static int TWIG_INSTANCE_OF_USERLAND(zval *object, char *interface TSRMLS_DC) +{ + zend_class_entry **pce; + if (Z_TYPE_P(object) != IS_OBJECT) { + return 0; + } + if (zend_lookup_class(interface, strlen(interface), &pce TSRMLS_CC) == FAILURE) { + return 0; + } + return instanceof_function(Z_OBJCE_P(object), *pce TSRMLS_CC); +} + +static zval *TWIG_GET_ARRAYOBJECT_ELEMENT(zval *object, zval *offset TSRMLS_DC) +{ + zend_class_entry *ce = Z_OBJCE_P(object); + zval *retval; + + if (Z_TYPE_P(object) == IS_OBJECT) { + SEPARATE_ARG_IF_REF(offset); + zend_call_method_with_1_params(&object, ce, NULL, "offsetget", &retval, offset); + + zval_ptr_dtor(&offset); + + if (!retval) { + if (!EG(exception)) { + zend_error(E_ERROR, "Undefined offset for object of type %s used as array.", ce->name); + } + return NULL; + } + + return retval; + } + return NULL; +} + +static int TWIG_ISSET_ARRAYOBJECT_ELEMENT(zval *object, zval *offset TSRMLS_DC) +{ + zend_class_entry *ce = Z_OBJCE_P(object); + zval *retval; + + if (Z_TYPE_P(object) == IS_OBJECT) { + SEPARATE_ARG_IF_REF(offset); + zend_call_method_with_1_params(&object, ce, NULL, "offsetexists", &retval, offset); + + zval_ptr_dtor(&offset); + + if (!retval) { + if (!EG(exception)) { + zend_error(E_ERROR, "Undefined offset for object of type %s used as array.", ce->name); + } + return 0; + } + + return (retval && Z_TYPE_P(retval) == IS_BOOL && Z_LVAL_P(retval)); + } + return 0; +} + +static char *TWIG_STRTOLOWER(const char *str, int str_len) +{ + char *item_dup; + + item_dup = estrndup(str, str_len); + php_strtolower(item_dup, str_len); + return item_dup; +} + +static zval *TWIG_CALL_USER_FUNC_ARRAY(zval *object, char *function, zval *arguments TSRMLS_DC) +{ + zend_fcall_info fci; + zval ***args = NULL; + int arg_count = 0; + HashTable *table; + HashPosition pos; + int i = 0; + zval *retval_ptr; + zval *zfunction; + + if (arguments) { + table = HASH_OF(arguments); + args = safe_emalloc(sizeof(zval **), table->nNumOfElements, 0); + + zend_hash_internal_pointer_reset_ex(table, &pos); + + while (zend_hash_get_current_data_ex(table, (void **)&args[i], &pos) == SUCCESS) { + i++; + zend_hash_move_forward_ex(table, &pos); + } + arg_count = table->nNumOfElements; + } + + MAKE_STD_ZVAL(zfunction); + ZVAL_STRING(zfunction, function, 1); + fci.size = sizeof(fci); + fci.function_table = EG(function_table); + fci.function_name = zfunction; + fci.symbol_table = NULL; +#if PHP_VERSION_ID >= 50300 + fci.object_ptr = object; +#else + fci.object_pp = &object; +#endif + fci.retval_ptr_ptr = &retval_ptr; + fci.param_count = arg_count; + fci.params = args; + fci.no_separation = 0; + + if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE) { + ALLOC_INIT_ZVAL(retval_ptr); + ZVAL_BOOL(retval_ptr, 0); + } + + if (args) { + efree(fci.params); + } + FREE_DTOR(zfunction); + return retval_ptr; +} + +static int TWIG_CALL_BOOLEAN(zval *object, char *functionName TSRMLS_DC) +{ + zval *ret; + int res; + + ret = TWIG_CALL_USER_FUNC_ARRAY(object, functionName, NULL TSRMLS_CC); + res = Z_LVAL_P(ret); + zval_ptr_dtor(&ret); + return res; +} + +static zval *TWIG_GET_STATIC_PROPERTY(zval *class, char *prop_name TSRMLS_DC) +{ + zval **tmp_zval; + zend_class_entry *ce; + + if (class == NULL || Z_TYPE_P(class) != IS_OBJECT) { + return NULL; + } + + ce = zend_get_class_entry(class TSRMLS_CC); +#if PHP_VERSION_ID >= 50400 + tmp_zval = zend_std_get_static_property(ce, prop_name, strlen(prop_name), 0, NULL TSRMLS_CC); +#else + tmp_zval = zend_std_get_static_property(ce, prop_name, strlen(prop_name), 0 TSRMLS_CC); +#endif + return *tmp_zval; +} + +static zval *TWIG_GET_ARRAY_ELEMENT_ZVAL(zval *class, zval *prop_name TSRMLS_DC) +{ + zval **tmp_zval; + + if (class == NULL || Z_TYPE_P(class) != IS_ARRAY) { + if (class != NULL && Z_TYPE_P(class) == IS_OBJECT && TWIG_INSTANCE_OF(class, zend_ce_arrayaccess TSRMLS_CC)) { + // array access object + return TWIG_GET_ARRAYOBJECT_ELEMENT(class, prop_name TSRMLS_CC); + } + return NULL; + } + + switch(Z_TYPE_P(prop_name)) { + case IS_NULL: + zend_hash_find(HASH_OF(class), "", 1, (void**) &tmp_zval); + return *tmp_zval; + + case IS_BOOL: + case IS_DOUBLE: + convert_to_long(prop_name); + case IS_LONG: + zend_hash_index_find(HASH_OF(class), Z_LVAL_P(prop_name), (void **) &tmp_zval); + return *tmp_zval; + + case IS_STRING: + zend_symtable_find(HASH_OF(class), Z_STRVAL_P(prop_name), Z_STRLEN_P(prop_name) + 1, (void**) &tmp_zval); + return *tmp_zval; + } + + return NULL; +} + +static zval *TWIG_GET_ARRAY_ELEMENT(zval *class, char *prop_name, int prop_name_length TSRMLS_DC) +{ + zval **tmp_zval; + + if (class == NULL/* || Z_TYPE_P(class) != IS_ARRAY*/) { + return NULL; + } + + if (class != NULL && Z_TYPE_P(class) == IS_OBJECT && TWIG_INSTANCE_OF(class, zend_ce_arrayaccess TSRMLS_CC)) { + // array access object + zval *tmp_name_zval; + zval *tmp_ret_zval; + + ALLOC_INIT_ZVAL(tmp_name_zval); + ZVAL_STRING(tmp_name_zval, prop_name, 1); + tmp_ret_zval = TWIG_GET_ARRAYOBJECT_ELEMENT(class, tmp_name_zval TSRMLS_CC); + FREE_DTOR(tmp_name_zval); + return tmp_ret_zval; + } + + if (zend_symtable_find(HASH_OF(class), prop_name, prop_name_length+1, (void**)&tmp_zval) == SUCCESS) { + return *tmp_zval; + } + return NULL; +} + +static zval *TWIG_PROPERTY(zval *object, zval *propname TSRMLS_DC) +{ + zval *tmp = NULL; + + if (Z_OBJ_HT_P(object)->read_property) { +#if PHP_VERSION_ID >= 50400 + tmp = Z_OBJ_HT_P(object)->read_property(object, propname, BP_VAR_IS, NULL TSRMLS_CC); +#else + tmp = Z_OBJ_HT_P(object)->read_property(object, propname, BP_VAR_IS TSRMLS_CC); +#endif + if (tmp == EG(uninitialized_zval_ptr)) { + ZVAL_NULL(tmp); + } + } + return tmp; +} + +static int TWIG_HAS_PROPERTY(zval *object, zval *propname TSRMLS_DC) +{ + if (Z_OBJ_HT_P(object)->has_property) { +#if PHP_VERSION_ID >= 50400 + return Z_OBJ_HT_P(object)->has_property(object, propname, 0, NULL TSRMLS_CC); +#else + return Z_OBJ_HT_P(object)->has_property(object, propname, 0 TSRMLS_CC); +#endif + } + return 0; +} + +static int TWIG_HAS_DYNAMIC_PROPERTY(zval *object, char *prop, int prop_len TSRMLS_DC) +{ + if (Z_OBJ_HT_P(object)->get_properties) { + return zend_hash_quick_exists( + Z_OBJ_HT_P(object)->get_properties(object TSRMLS_CC), // the properties hash + prop, // property name + prop_len + 1, // property length + zend_get_hash_value(prop, prop_len + 1) // hash value + ); + } + return 0; +} + +static zval *TWIG_PROPERTY_CHAR(zval *object, char *propname TSRMLS_DC) +{ + zval *tmp_name_zval, *tmp; + + ALLOC_INIT_ZVAL(tmp_name_zval); + ZVAL_STRING(tmp_name_zval, propname, 1); + tmp = TWIG_PROPERTY(object, tmp_name_zval TSRMLS_CC); + FREE_DTOR(tmp_name_zval); + return tmp; +} + +static zval *TWIG_CALL_S(zval *object, char *method, char *arg0 TSRMLS_DC) +{ + zend_fcall_info fci; + zval **args[1]; + zval *argument; + zval *zfunction; + zval *retval_ptr; + + MAKE_STD_ZVAL(argument); + ZVAL_STRING(argument, arg0, 1); + args[0] = &argument; + + MAKE_STD_ZVAL(zfunction); + ZVAL_STRING(zfunction, method, 1); + fci.size = sizeof(fci); + fci.function_table = EG(function_table); + fci.function_name = zfunction; + fci.symbol_table = NULL; +#if PHP_VERSION_ID >= 50300 + fci.object_ptr = object; +#else + fci.object_pp = &object; +#endif + fci.retval_ptr_ptr = &retval_ptr; + fci.param_count = 1; + fci.params = args; + fci.no_separation = 0; + + if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE) { + FREE_DTOR(zfunction); + zval_ptr_dtor(&argument); + return 0; + } + FREE_DTOR(zfunction); + zval_ptr_dtor(&argument); + return retval_ptr; +} + +static int TWIG_CALL_SB(zval *object, char *method, char *arg0 TSRMLS_DC) +{ + zval *retval_ptr; + int success; + + retval_ptr = TWIG_CALL_S(object, method, arg0 TSRMLS_CC); + success = (retval_ptr && (Z_TYPE_P(retval_ptr) == IS_BOOL) && Z_LVAL_P(retval_ptr)); + + if (retval_ptr) { + zval_ptr_dtor(&retval_ptr); + } + + return success; +} + +static int TWIG_CALL_ZZ(zval *object, char *method, zval *arg1, zval *arg2 TSRMLS_DC) +{ + zend_fcall_info fci; + zval **args[2]; + zval *zfunction; + zval *retval_ptr; + int success; + + args[0] = &arg1; + args[1] = &arg2; + + MAKE_STD_ZVAL(zfunction); + ZVAL_STRING(zfunction, method, 1); + fci.size = sizeof(fci); + fci.function_table = EG(function_table); + fci.function_name = zfunction; + fci.symbol_table = NULL; +#if PHP_VERSION_ID >= 50300 + fci.object_ptr = object; +#else + fci.object_pp = &object; +#endif + fci.retval_ptr_ptr = &retval_ptr; + fci.param_count = 2; + fci.params = args; + fci.no_separation = 0; + + if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE) { + FREE_DTOR(zfunction); + return 0; + } + + FREE_DTOR(zfunction); + + success = (retval_ptr && (Z_TYPE_P(retval_ptr) == IS_BOOL) && Z_LVAL_P(retval_ptr)); + if (retval_ptr) { + zval_ptr_dtor(&retval_ptr); + } + + return success; +} + +#ifndef Z_SET_REFCOUNT_P +# define Z_SET_REFCOUNT_P(pz, rc) pz->refcount = rc +# define Z_UNSET_ISREF_P(pz) pz->is_ref = 0 +#endif + +static void TWIG_NEW(zval *object, char *class, zval *arg0, zval *arg1 TSRMLS_DC) +{ + zend_class_entry **pce; + + if (zend_lookup_class(class, strlen(class), &pce TSRMLS_CC) == FAILURE) { + return; + } + + Z_TYPE_P(object) = IS_OBJECT; + object_init_ex(object, *pce); + Z_SET_REFCOUNT_P(object, 1); + Z_UNSET_ISREF_P(object); + + TWIG_CALL_ZZ(object, "__construct", arg0, arg1 TSRMLS_CC); +} + +static int twig_add_array_key_to_string(void *pDest APPLY_TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) +{ + smart_str *buf; + char *joiner; + APPLY_TSRMLS_FETCH(); + + buf = va_arg(args, smart_str*); + joiner = va_arg(args, char*); + + if (buf->len != 0) { + smart_str_appends(buf, joiner); + } + + if (hash_key->nKeyLength == 0) { + smart_str_append_long(buf, (long) hash_key->h); + } else { + char *key, *tmp_str; + int key_len, tmp_len; + key = php_addcslashes(hash_key->arKey, hash_key->nKeyLength - 1, &key_len, 0, "'\\", 2 TSRMLS_CC); + tmp_str = php_str_to_str_ex(key, key_len, "\0", 1, "' . \"\\0\" . '", 12, &tmp_len, 0, NULL); + + smart_str_appendl(buf, tmp_str, tmp_len); + efree(key); + efree(tmp_str); + } + + return 0; +} + +static char *TWIG_IMPLODE_ARRAY_KEYS(char *joiner, zval *array TSRMLS_DC) +{ + smart_str collector = { 0, 0, 0 }; + + smart_str_appendl(&collector, "", 0); + zend_hash_apply_with_arguments(HASH_OF(array) APPLY_TSRMLS_CC, twig_add_array_key_to_string, 2, &collector, joiner); + smart_str_0(&collector); + + return collector.c; +} + +static void TWIG_RUNTIME_ERROR(zval *template TSRMLS_DC, char *message, ...) +{ + char *buffer; + va_list args; + zend_class_entry **pce; + zval *ex; + zval *constructor; + zval *zmessage; + zval *lineno; + zval *filename_func; + zval *filename; + zval *constructor_args[3]; + zval *constructor_retval; + + if (zend_lookup_class("Twig_Error_Runtime", strlen("Twig_Error_Runtime"), &pce TSRMLS_CC) == FAILURE) { + return; + } + + va_start(args, message); + vspprintf(&buffer, 0, message, args); + va_end(args); + + MAKE_STD_ZVAL(ex); + object_init_ex(ex, *pce); + + // Call Twig_Error constructor + MAKE_STD_ZVAL(constructor); + MAKE_STD_ZVAL(zmessage); + MAKE_STD_ZVAL(lineno); + MAKE_STD_ZVAL(filename); + MAKE_STD_ZVAL(filename_func); + MAKE_STD_ZVAL(constructor_retval); + + ZVAL_STRINGL(constructor, "__construct", sizeof("__construct")-1, 1); + ZVAL_STRING(zmessage, buffer, 1); + ZVAL_LONG(lineno, -1); + + // Get template filename + ZVAL_STRINGL(filename_func, "getTemplateName", sizeof("getTemplateName")-1, 1); + call_user_function(EG(function_table), &template, filename_func, filename, 0, 0 TSRMLS_CC); + + constructor_args[0] = zmessage; + constructor_args[1] = lineno; + constructor_args[2] = filename; + call_user_function(EG(function_table), &ex, constructor, constructor_retval, 3, constructor_args TSRMLS_CC); + + zval_ptr_dtor(&constructor_retval); + zval_ptr_dtor(&zmessage); + zval_ptr_dtor(&lineno); + zval_ptr_dtor(&filename); + FREE_DTOR(constructor); + FREE_DTOR(filename_func); + efree(buffer); + + zend_throw_exception_object(ex TSRMLS_CC); +} + +static char *TWIG_GET_CLASS_NAME(zval *object TSRMLS_DC) +{ + char *class_name; + zend_uint class_name_len; + + if (Z_TYPE_P(object) != IS_OBJECT) { + return ""; + } +#if PHP_API_VERSION >= 20100412 + zend_get_object_classname(object, (const char **) &class_name, &class_name_len TSRMLS_CC); +#else + zend_get_object_classname(object, &class_name, &class_name_len TSRMLS_CC); +#endif + return class_name; +} + +static int twig_add_method_to_class(void *pDest APPLY_TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) +{ + zend_class_entry *ce; + zval *retval; + char *item; + size_t item_len; + zend_function *mptr = (zend_function *) pDest; + APPLY_TSRMLS_FETCH(); + + if (!(mptr->common.fn_flags & ZEND_ACC_PUBLIC)) { + return 0; + } + + ce = *va_arg(args, zend_class_entry**); + retval = va_arg(args, zval*); + + item_len = strlen(mptr->common.function_name); + item = estrndup(mptr->common.function_name, item_len); + php_strtolower(item, item_len); + + if (strcmp("getenvironment", item) == 0) { + zend_class_entry **twig_template_ce; + if (zend_lookup_class("Twig_Template", strlen("Twig_Template"), &twig_template_ce TSRMLS_CC) == FAILURE) { + return 0; + } + if (instanceof_function(ce, *twig_template_ce TSRMLS_CC)) { + return 0; + } + } + + add_assoc_stringl_ex(retval, item, item_len+1, item, item_len, 0); + + return 0; +} + +static int twig_add_property_to_class(void *pDest APPLY_TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) +{ + zend_class_entry *ce; + zval *retval; + char *class_name, *prop_name; + zend_property_info *pptr = (zend_property_info *) pDest; + APPLY_TSRMLS_FETCH(); + + if (!(pptr->flags & ZEND_ACC_PUBLIC) || (pptr->flags & ZEND_ACC_STATIC)) { + return 0; + } + + ce = *va_arg(args, zend_class_entry**); + retval = va_arg(args, zval*); + +#if PHP_API_VERSION >= 20100412 + zend_unmangle_property_name(pptr->name, pptr->name_length, (const char **) &class_name, (const char **) &prop_name); +#else + zend_unmangle_property_name(pptr->name, pptr->name_length, &class_name, &prop_name); +#endif + + add_assoc_string(retval, prop_name, prop_name, 1); + + return 0; +} + +static void twig_add_class_to_cache(zval *cache, zval *object, char *class_name TSRMLS_DC) +{ + zval *class_info, *class_methods, *class_properties; + zend_class_entry *class_ce; + + class_ce = zend_get_class_entry(object TSRMLS_CC); + + ALLOC_INIT_ZVAL(class_info); + ALLOC_INIT_ZVAL(class_methods); + ALLOC_INIT_ZVAL(class_properties); + array_init(class_info); + array_init(class_methods); + array_init(class_properties); + // add all methods to self::cache[$class]['methods'] + zend_hash_apply_with_arguments(&class_ce->function_table APPLY_TSRMLS_CC, twig_add_method_to_class, 2, &class_ce, class_methods); + zend_hash_apply_with_arguments(&class_ce->properties_info APPLY_TSRMLS_CC, twig_add_property_to_class, 2, &class_ce, class_properties); + + add_assoc_zval(class_info, "methods", class_methods); + add_assoc_zval(class_info, "properties", class_properties); + add_assoc_zval(cache, class_name, class_info); +} + +/* {{{ proto mixed twig_template_get_attributes(TwigTemplate template, mixed object, mixed item, array arguments, string type, boolean isDefinedTest, boolean ignoreStrictCheck) + A C implementation of TwigTemplate::getAttribute() */ +PHP_FUNCTION(twig_template_get_attributes) +{ + zval *template; + zval *object; + char *item; + int item_len; + zval *zitem, ztmpitem; + zval *arguments = NULL; + zval *ret = NULL; + char *type = NULL; + int type_len = 0; + zend_bool isDefinedTest = 0; + zend_bool ignoreStrictCheck = 0; + int free_ret = 0; + zval *tmp_self_cache; + char *class_name = NULL; + zval *tmp_class; + char *type_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ozz|asbb", &template, &object, &zitem, &arguments, &type, &type_len, &isDefinedTest, &ignoreStrictCheck) == FAILURE) { + return; + } + + // convert the item to a string + ztmpitem = *zitem; + zval_copy_ctor(&ztmpitem); + convert_to_string(&ztmpitem); + item_len = Z_STRLEN(ztmpitem); + item = estrndup(Z_STRVAL(ztmpitem), item_len); + zval_dtor(&ztmpitem); + + if (!type) { + type = "any"; + } + +/* + // array + if (Twig_Template::METHOD_CALL !== $type) { + $arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item; + + if ((is_array($object) && array_key_exists($arrayItem, $object)) + || ($object instanceof ArrayAccess && isset($object[$arrayItem])) + ) { + if ($isDefinedTest) { + return true; + } + + return $object[$arrayItem]; + } +*/ + + + if (strcmp("method", type) != 0) { + if ((TWIG_ARRAY_KEY_EXISTS(object, zitem)) + || (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC) && TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, zitem TSRMLS_CC)) + ) { + + if (isDefinedTest) { + efree(item); + RETURN_TRUE; + } + + ret = TWIG_GET_ARRAY_ELEMENT_ZVAL(object, zitem TSRMLS_CC); + + if (!ret) { + ret = &EG(uninitialized_zval); + } + RETVAL_ZVAL(ret, 1, 0); + if (free_ret) { + zval_ptr_dtor(&ret); + } + efree(item); + return; + } +/* + if (Twig_Template::ARRAY_CALL === $type) { + if ($isDefinedTest) { + return false; + } + if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { + return null; + } +*/ + if (strcmp("array", type) == 0 || Z_TYPE_P(object) != IS_OBJECT) { + if (isDefinedTest) { + efree(item); + RETURN_FALSE; + } + if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) { + efree(item); + return; + } +/* + if ($object instanceof ArrayAccess) { + $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist', $arrayItem, get_class($object)); + } elseif (is_object($object)) { + $message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface', $item, get_class($object)); + } elseif (is_array($object)) { + if (empty($object)) { + $message = sprintf('Key "%s" does not exist as the array is empty', $arrayItem); + } else { + $message = sprintf('Key "%s" for array with keys "%s" does not exist', $arrayItem, implode(', ', array_keys($object))); + } + } elseif (Twig_Template::ARRAY_CALL === $type) { + if (null === $object) { + $message = sprintf('Impossible to access a key ("%s") on a null variable', $item); + } else { + $message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s")', $item, gettype($object), $object); + } + } elseif (null === $object) { + $message = sprintf('Impossible to access an attribute ("%s") on a null variable', $item); + } else { + $message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s")', $item, gettype($object), $object); + } + throw new Twig_Error_Runtime($message, -1, $this->getTemplateName()); + } + } +*/ + if (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC)) { + TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Key \"%s\" in object with ArrayAccess of class \"%s\" does not exist.", item, TWIG_GET_CLASS_NAME(object TSRMLS_CC)); + } else if (Z_TYPE_P(object) == IS_OBJECT) { + TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Impossible to access a key \"%s\" on an object of class \"%s\" that does not implement ArrayAccess interface.", item, TWIG_GET_CLASS_NAME(object TSRMLS_CC)); + } else if (Z_TYPE_P(object) == IS_ARRAY) { + if (0 == zend_hash_num_elements(Z_ARRVAL_P(object))) { + TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Key \"%s\" does not exist as the array is empty.", item); + } else { + char *array_keys = TWIG_IMPLODE_ARRAY_KEYS(", ", object TSRMLS_CC); + TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Key \"%s\" for array with keys \"%s\" does not exist.", item, array_keys); + efree(array_keys); + } + } else { + char *type_name = zend_zval_type_name(object); + Z_ADDREF_P(object); + if (Z_TYPE_P(object) == IS_NULL) { + convert_to_string(object); + TWIG_RUNTIME_ERROR(template TSRMLS_CC, + (strcmp("array", type) == 0) + ? "Impossible to access a key (\"%s\") on a %s variable." + : "Impossible to access an attribute (\"%s\") on a %s variable.", + item, type_name); + } else { + convert_to_string(object); + TWIG_RUNTIME_ERROR(template TSRMLS_CC, + (strcmp("array", type) == 0) + ? "Impossible to access a key (\"%s\") on a %s variable (\"%s\")." + : "Impossible to access an attribute (\"%s\") on a %s variable (\"%s\").", + item, type_name, Z_STRVAL_P(object)); + } + zval_ptr_dtor(&object); + } + efree(item); + return; + } + } + +/* + if (!is_object($object)) { + if ($isDefinedTest) { + return false; + } +*/ + + if (Z_TYPE_P(object) != IS_OBJECT) { + if (isDefinedTest) { + efree(item); + RETURN_FALSE; + } +/* + if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { + return null; + } + + if (null === $object) { + $message = sprintf('Impossible to invoke a method ("%s") on a null variable', $item); + } else { + $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s")', $item, gettype($object), $object); + } + + throw new Twig_Error_Runtime($message, -1, $this->getTemplateName()); + } +*/ + if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) { + efree(item); + return; + } + + type_name = zend_zval_type_name(object); + Z_ADDREF_P(object); + if (Z_TYPE_P(object) == IS_NULL) { + convert_to_string_ex(&object); + + TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Impossible to invoke a method (\"%s\") on a %s variable.", item, type_name); + } else { + convert_to_string_ex(&object); + + TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Impossible to invoke a method (\"%s\") on a %s variable (\"%s\").", item, type_name, Z_STRVAL_P(object)); + } + + zval_ptr_dtor(&object); + efree(item); + return; + } +/* + $class = get_class($object); +*/ + + class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC); + tmp_self_cache = TWIG_GET_STATIC_PROPERTY(template, "cache" TSRMLS_CC); + tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC); + + if (!tmp_class) { + twig_add_class_to_cache(tmp_self_cache, object, class_name TSRMLS_CC); + tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC); + } + efree(class_name); + +/* + // object property + if (Twig_Template::METHOD_CALL !== $type && !$object instanceof Twig_Template) { + if (isset($object->$item) || array_key_exists((string) $item, $object)) { + if ($isDefinedTest) { + return true; + } + + if ($this->env->hasExtension('Twig_Extension_Sandbox')) { + $this->env->getExtension('Twig_Extension_Sandbox')->checkPropertyAllowed($object, $item); + } + + return $object->$item; + } + } +*/ + if (strcmp("method", type) != 0 && !TWIG_INSTANCE_OF_USERLAND(object, "Twig_Template" TSRMLS_CC)) { + zval *tmp_properties, *tmp_item; + + tmp_properties = TWIG_GET_ARRAY_ELEMENT(tmp_class, "properties", strlen("properties") TSRMLS_CC); + tmp_item = TWIG_GET_ARRAY_ELEMENT(tmp_properties, item, item_len TSRMLS_CC); + + if (tmp_item || TWIG_HAS_PROPERTY(object, zitem TSRMLS_CC) || TWIG_HAS_DYNAMIC_PROPERTY(object, item, item_len TSRMLS_CC)) { + if (isDefinedTest) { + efree(item); + RETURN_TRUE; + } + if (TWIG_CALL_SB(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "hasExtension", "Twig_Extension_Sandbox" TSRMLS_CC)) { + TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "Twig_Extension_Sandbox" TSRMLS_CC), "checkPropertyAllowed", object, zitem TSRMLS_CC); + } + if (EG(exception)) { + efree(item); + return; + } + + ret = TWIG_PROPERTY(object, zitem TSRMLS_CC); + efree(item); + RETURN_ZVAL(ret, 1, 0); + } + } +/* + // object method + if (!isset(self::$cache[$class]['methods'])) { + if ($object instanceof self) { + $ref = new ReflectionClass($class); + $methods = array(); + + foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) { + $methodName = strtolower($refMethod->name); + + // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment + if ('getenvironment' !== $methodName) { + $methods[$methodName] = true; + } + } + + self::$cache[$class]['methods'] = $methods; + } else { + self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object))); + } + } + + $call = false; + $lcItem = strtolower($item); + if (isset(self::$cache[$class]['methods'][$lcItem])) { + $method = (string) $item; + } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) { + $method = 'get'.$item; + } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) { + $method = 'is'.$item; + } elseif (isset(self::$cache[$class]['methods']['__call'])) { + $method = (string) $item; + $call = true; +*/ + { + int call = 0; + char *lcItem = TWIG_STRTOLOWER(item, item_len); + int lcItem_length; + char *method = NULL; + char *tmp_method_name_get; + char *tmp_method_name_is; + zval *zmethod; + zval *tmp_methods; + + lcItem_length = strlen(lcItem); + tmp_method_name_get = emalloc(4 + lcItem_length); + tmp_method_name_is = emalloc(3 + lcItem_length); + + sprintf(tmp_method_name_get, "get%s", lcItem); + sprintf(tmp_method_name_is, "is%s", lcItem); + + tmp_methods = TWIG_GET_ARRAY_ELEMENT(tmp_class, "methods", strlen("methods") TSRMLS_CC); + + if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, lcItem, lcItem_length TSRMLS_CC)) { + method = item; + } else if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, tmp_method_name_get, lcItem_length + 3 TSRMLS_CC)) { + method = tmp_method_name_get; + } else if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, tmp_method_name_is, lcItem_length + 2 TSRMLS_CC)) { + method = tmp_method_name_is; + } else if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, "__call", 6 TSRMLS_CC)) { + method = item; + call = 1; +/* + } else { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { + return null; + } + + throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist.', $item, get_class($object)), -1, $this->getTemplateName()); + } + + if ($isDefinedTest) { + return true; + } +*/ + } else { + efree(tmp_method_name_get); + efree(tmp_method_name_is); + efree(lcItem); + + if (isDefinedTest) { + efree(item); + RETURN_FALSE; + } + if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) { + efree(item); + return; + } + TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Neither the property \"%s\" nor one of the methods \"%s()\", \"get%s()\"/\"is%s()\" or \"__call()\" exist and have public access in class \"%s\".", item, item, item, item, TWIG_GET_CLASS_NAME(object TSRMLS_CC)); + efree(item); + return; + } + + if (isDefinedTest) { + efree(tmp_method_name_get); + efree(tmp_method_name_is); + efree(lcItem);efree(item); + RETURN_TRUE; + } +/* + if ($this->env->hasExtension('Twig_Extension_Sandbox')) { + $this->env->getExtension('Twig_Extension_Sandbox')->checkMethodAllowed($object, $method); + } +*/ + MAKE_STD_ZVAL(zmethod); + ZVAL_STRING(zmethod, method, 1); + if (TWIG_CALL_SB(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "hasExtension", "Twig_Extension_Sandbox" TSRMLS_CC)) { + TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "Twig_Extension_Sandbox" TSRMLS_CC), "checkMethodAllowed", object, zmethod TSRMLS_CC); + } + zval_ptr_dtor(&zmethod); + if (EG(exception)) { + efree(tmp_method_name_get); + efree(tmp_method_name_is); + efree(lcItem);efree(item); + return; + } +/* + // Some objects throw exceptions when they have __call, and the method we try + // to call is not supported. If ignoreStrictCheck is true, we should return null. + try { + $ret = call_user_func_array(array($object, $method), $arguments); + } catch (BadMethodCallException $e) { + if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) { + return null; + } + throw $e; + } +*/ + ret = TWIG_CALL_USER_FUNC_ARRAY(object, method, arguments TSRMLS_CC); + if (EG(exception) && TWIG_INSTANCE_OF(EG(exception), spl_ce_BadMethodCallException TSRMLS_CC)) { + if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) { + efree(tmp_method_name_get); + efree(tmp_method_name_is); + efree(lcItem);efree(item); + zend_clear_exception(TSRMLS_C); + return; + } + } + free_ret = 1; + efree(tmp_method_name_get); + efree(tmp_method_name_is); + efree(lcItem); + } +/* + // useful when calling a template method from a template + // this is not supported but unfortunately heavily used in the Symfony profiler + if ($object instanceof Twig_TemplateInterface) { + return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset()); + } + + return $ret; +*/ + efree(item); + // ret can be null, if e.g. the called method throws an exception + if (ret) { + if (TWIG_INSTANCE_OF_USERLAND(object, "Twig_TemplateInterface" TSRMLS_CC)) { + if (Z_STRLEN_P(ret) != 0) { + zval *charset = TWIG_CALL_USER_FUNC_ARRAY(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getCharset", NULL TSRMLS_CC); + TWIG_NEW(return_value, "Twig_Markup", ret, charset TSRMLS_CC); + zval_ptr_dtor(&charset); + if (ret) { + zval_ptr_dtor(&ret); + } + return; + } + } + + RETVAL_ZVAL(ret, 1, 0); + if (free_ret) { + zval_ptr_dtor(&ret); + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Autoloader.php b/src/vendor/twig/twig/lib/Twig/Autoloader.php new file mode 100644 index 0000000..1082e69 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Autoloader.php @@ -0,0 +1,54 @@ + + * + * @deprecated since 1.21 and will be removed in 2.0. Use Composer instead. 2.0. + */ +class Twig_Autoloader +{ + /** + * Registers Twig_Autoloader as an SPL autoloader. + * + * @param bool $prepend whether to prepend the autoloader or not + */ + public static function register($prepend = false) + { + @trigger_error('Using Twig_Autoloader is deprecated since version 1.21. Use Composer instead.', E_USER_DEPRECATED); + + if (PHP_VERSION_ID < 50300) { + spl_autoload_register(array(__CLASS__, 'autoload')); + } else { + spl_autoload_register(array(__CLASS__, 'autoload'), true, $prepend); + } + } + + /** + * Handles autoloading of classes. + * + * @param string $class a class name + */ + public static function autoload($class) + { + if (0 !== strpos($class, 'Twig')) { + return; + } + + if (is_file($file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php')) { + require $file; + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/BaseNodeVisitor.php b/src/vendor/twig/twig/lib/Twig/BaseNodeVisitor.php new file mode 100644 index 0000000..c2e7b7e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/BaseNodeVisitor.php @@ -0,0 +1,56 @@ + + */ +abstract class Twig_BaseNodeVisitor implements Twig_NodeVisitorInterface +{ + /** + * {@inheritdoc} + */ + final public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if (!$node instanceof Twig_Node) { + throw new LogicException('Twig_BaseNodeVisitor only supports Twig_Node instances.'); + } + + return $this->doEnterNode($node, $env); + } + + /** + * {@inheritdoc} + */ + final public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if (!$node instanceof Twig_Node) { + throw new LogicException('Twig_BaseNodeVisitor only supports Twig_Node instances.'); + } + + return $this->doLeaveNode($node, $env); + } + + /** + * Called before child nodes are visited. + * + * @return Twig_Node The modified node + */ + abstract protected function doEnterNode(Twig_Node $node, Twig_Environment $env); + + /** + * Called after child nodes are visited. + * + * @return Twig_Node|false The modified node or false if the node must be removed + */ + abstract protected function doLeaveNode(Twig_Node $node, Twig_Environment $env); +} diff --git a/src/vendor/twig/twig/lib/Twig/Cache/Filesystem.php b/src/vendor/twig/twig/lib/Twig/Cache/Filesystem.php new file mode 100644 index 0000000..9e70ca9 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Cache/Filesystem.php @@ -0,0 +1,98 @@ + + */ +class Twig_Cache_Filesystem implements Twig_CacheInterface +{ + const FORCE_BYTECODE_INVALIDATION = 1; + + private $directory; + private $options; + + /** + * @param $directory string The root cache directory + * @param $options int A set of options + */ + public function __construct($directory, $options = 0) + { + $this->directory = rtrim($directory, '\/').'/'; + $this->options = $options; + } + + /** + * {@inheritdoc} + */ + public function generateKey($name, $className) + { + $hash = hash('sha256', $className); + + return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php'; + } + + /** + * {@inheritdoc} + */ + public function load($key) + { + if (file_exists($key)) { + @include_once $key; + } + } + + /** + * {@inheritdoc} + */ + public function write($key, $content) + { + $dir = dirname($key); + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { + throw new RuntimeException(sprintf('Unable to create the cache directory (%s).', $dir)); + } + } elseif (!is_writable($dir)) { + throw new RuntimeException(sprintf('Unable to write in the cache directory (%s).', $dir)); + } + + $tmpFile = tempnam($dir, basename($key)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $key)) { + @chmod($key, 0666 & ~umask()); + + if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) { + // Compile cached file into bytecode cache + if (function_exists('opcache_invalidate')) { + opcache_invalidate($key, true); + } elseif (function_exists('apc_compile_file')) { + apc_compile_file($key); + } + } + + return; + } + + throw new RuntimeException(sprintf('Failed to write cache file "%s".', $key)); + } + + /** + * {@inheritdoc} + */ + public function getTimestamp($key) + { + if (!file_exists($key)) { + return 0; + } + + return (int) @filemtime($key); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Cache/Null.php b/src/vendor/twig/twig/lib/Twig/Cache/Null.php new file mode 100644 index 0000000..f608918 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Cache/Null.php @@ -0,0 +1,50 @@ + + */ +class Twig_Cache_Null implements Twig_CacheInterface +{ + /** + * {@inheritdoc} + */ + public function generateKey($name, $className) + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function write($key, $content) + { + } + + /** + * {@inheritdoc} + */ + public function load($key) + { + } + + /** + * {@inheritdoc} + */ + public function getTimestamp($key) + { + return 0; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/CacheInterface.php b/src/vendor/twig/twig/lib/Twig/CacheInterface.php new file mode 100644 index 0000000..9b17e0f --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/CacheInterface.php @@ -0,0 +1,56 @@ + + */ +interface Twig_CacheInterface +{ + /** + * Generates a cache key for the given template class name. + * + * @param string $name The template name + * @param string $className The template class name + * + * @return string + */ + public function generateKey($name, $className); + + /** + * Writes the compiled template to cache. + * + * @param string $key The cache key + * @param string $content The template representation as a PHP class + */ + public function write($key, $content); + + /** + * Loads a template from the cache. + * + * @param string $key The cache key + */ + public function load($key); + + /** + * Returns the modification timestamp of a key. + * + * @param string $key The cache key + * + * @return int + */ + public function getTimestamp($key); +} diff --git a/src/vendor/twig/twig/lib/Twig/Compiler.php b/src/vendor/twig/twig/lib/Twig/Compiler.php new file mode 100644 index 0000000..7ab8fef --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Compiler.php @@ -0,0 +1,281 @@ + + */ +class Twig_Compiler implements Twig_CompilerInterface +{ + protected $lastLine; + protected $source; + protected $indentation; + protected $env; + protected $debugInfo = array(); + protected $sourceOffset; + protected $sourceLine; + protected $filename; + + public function __construct(Twig_Environment $env) + { + $this->env = $env; + } + + /** + * @deprecated since 1.25 (to be removed in 2.0) + */ + public function getFilename() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + + return $this->filename; + } + + /** + * Returns the environment instance related to this compiler. + * + * @return Twig_Environment + */ + public function getEnvironment() + { + return $this->env; + } + + /** + * Gets the current PHP code after compilation. + * + * @return string The PHP code + */ + public function getSource() + { + return $this->source; + } + + /** + * Compiles a node. + * + * @param Twig_NodeInterface $node The node to compile + * @param int $indentation The current indentation + * + * @return $this + */ + public function compile(Twig_NodeInterface $node, $indentation = 0) + { + $this->lastLine = null; + $this->source = ''; + $this->debugInfo = array(); + $this->sourceOffset = 0; + // source code starts at 1 (as we then increment it when we encounter new lines) + $this->sourceLine = 1; + $this->indentation = $indentation; + + if ($node instanceof Twig_Node_Module) { + // to be removed in 2.0 + $this->filename = $node->getTemplateName(); + } + + $node->compile($this); + + return $this; + } + + public function subcompile(Twig_NodeInterface $node, $raw = true) + { + if (false === $raw) { + $this->source .= str_repeat(' ', $this->indentation * 4); + } + + $node->compile($this); + + return $this; + } + + /** + * Adds a raw string to the compiled code. + * + * @param string $string The string + * + * @return $this + */ + public function raw($string) + { + $this->source .= $string; + + return $this; + } + + /** + * Writes a string to the compiled code by adding indentation. + * + * @return $this + */ + public function write() + { + $strings = func_get_args(); + foreach ($strings as $string) { + $this->source .= str_repeat(' ', $this->indentation * 4).$string; + } + + return $this; + } + + /** + * Appends an indentation to the current PHP code after compilation. + * + * @return $this + * + * @deprecated since 1.27 (to be removed in 2.0). + */ + public function addIndentation() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use write(\'\') instead.', E_USER_DEPRECATED); + + $this->source .= str_repeat(' ', $this->indentation * 4); + + return $this; + } + + /** + * Adds a quoted string to the compiled code. + * + * @param string $value The string + * + * @return $this + */ + public function string($value) + { + $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); + + return $this; + } + + /** + * Returns a PHP representation of a given value. + * + * @param mixed $value The value to convert + * + * @return $this + */ + public function repr($value) + { + if (is_int($value) || is_float($value)) { + if (false !== $locale = setlocale(LC_NUMERIC, 0)) { + setlocale(LC_NUMERIC, 'C'); + } + + $this->raw($value); + + if (false !== $locale) { + setlocale(LC_NUMERIC, $locale); + } + } elseif (null === $value) { + $this->raw('null'); + } elseif (is_bool($value)) { + $this->raw($value ? 'true' : 'false'); + } elseif (is_array($value)) { + $this->raw('array('); + $first = true; + foreach ($value as $key => $v) { + if (!$first) { + $this->raw(', '); + } + $first = false; + $this->repr($key); + $this->raw(' => '); + $this->repr($v); + } + $this->raw(')'); + } else { + $this->string($value); + } + + return $this; + } + + /** + * Adds debugging information. + * + * @return $this + */ + public function addDebugInfo(Twig_NodeInterface $node) + { + if ($node->getTemplateLine() != $this->lastLine) { + $this->write(sprintf("// line %d\n", $node->getTemplateLine())); + + // when mbstring.func_overload is set to 2 + // mb_substr_count() replaces substr_count() + // but they have different signatures! + if (((int) ini_get('mbstring.func_overload')) & 2) { + @trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED); + + // this is much slower than the "right" version + $this->sourceLine += mb_substr_count(mb_substr($this->source, $this->sourceOffset), "\n"); + } else { + $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset); + } + $this->sourceOffset = strlen($this->source); + $this->debugInfo[$this->sourceLine] = $node->getTemplateLine(); + + $this->lastLine = $node->getTemplateLine(); + } + + return $this; + } + + public function getDebugInfo() + { + ksort($this->debugInfo); + + return $this->debugInfo; + } + + /** + * Indents the generated code. + * + * @param int $step The number of indentation to add + * + * @return $this + */ + public function indent($step = 1) + { + $this->indentation += $step; + + return $this; + } + + /** + * Outdents the generated code. + * + * @param int $step The number of indentation to remove + * + * @return $this + * + * @throws LogicException When trying to outdent too much so the indentation would become negative + */ + public function outdent($step = 1) + { + // can't outdent by more steps than the current indentation level + if ($this->indentation < $step) { + throw new LogicException('Unable to call outdent() as the indentation would become negative.'); + } + + $this->indentation -= $step; + + return $this; + } + + public function getVarName() + { + return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false)); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/CompilerInterface.php b/src/vendor/twig/twig/lib/Twig/CompilerInterface.php new file mode 100644 index 0000000..70350ca --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/CompilerInterface.php @@ -0,0 +1,34 @@ + + * + * @deprecated since 1.12 (to be removed in 3.0) + */ +interface Twig_CompilerInterface +{ + /** + * Compiles a node. + * + * @return $this + */ + public function compile(Twig_NodeInterface $node); + + /** + * Gets the current PHP code after compilation. + * + * @return string The PHP code + */ + public function getSource(); +} diff --git a/src/vendor/twig/twig/lib/Twig/Environment.php b/src/vendor/twig/twig/lib/Twig/Environment.php new file mode 100644 index 0000000..6f7f1b6 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Environment.php @@ -0,0 +1,1552 @@ + + */ +class Twig_Environment +{ + const VERSION = '1.29.0'; + const VERSION_ID = 12900; + const MAJOR_VERSION = 1; + const MINOR_VERSION = 29; + const RELEASE_VERSION = 0; + const EXTRA_VERSION = ''; + + protected $charset; + protected $loader; + protected $debug; + protected $autoReload; + protected $cache; + protected $lexer; + protected $parser; + protected $compiler; + protected $baseTemplateClass; + protected $extensions; + protected $parsers; + protected $visitors; + protected $filters; + protected $tests; + protected $functions; + protected $globals; + protected $runtimeInitialized = false; + protected $extensionInitialized = false; + protected $loadedTemplates; + protected $strictVariables; + protected $unaryOperators; + protected $binaryOperators; + protected $templateClassPrefix = '__TwigTemplate_'; + protected $functionCallbacks = array(); + protected $filterCallbacks = array(); + protected $staging; + + private $originalCache; + private $bcWriteCacheFile = false; + private $bcGetCacheFilename = false; + private $lastModifiedExtension = 0; + private $extensionsByClass = array(); + private $runtimeLoaders = array(); + private $runtimes = array(); + private $optionsHash; + + /** + * Constructor. + * + * Available options: + * + * * debug: When set to true, it automatically set "auto_reload" to true as + * well (default to false). + * + * * charset: The charset used by the templates (default to UTF-8). + * + * * base_template_class: The base template class to use for generated + * templates (default to Twig_Template). + * + * * cache: An absolute path where to store the compiled templates, + * a Twig_Cache_Interface implementation, + * or false to disable compilation cache (default). + * + * * auto_reload: Whether to reload the template if the original source changed. + * If you don't provide the auto_reload option, it will be + * determined automatically based on the debug value. + * + * * strict_variables: Whether to ignore invalid variables in templates + * (default to false). + * + * * autoescape: Whether to enable auto-escaping (default to html): + * * false: disable auto-escaping + * * true: equivalent to html + * * html, js: set the autoescaping to one of the supported strategies + * * name: set the autoescaping strategy based on the template name extension + * * PHP callback: a PHP callback that returns an escaping strategy based on the template "name" + * + * * optimizations: A flag that indicates which optimizations to apply + * (default to -1 which means that all optimizations are enabled; + * set it to 0 to disable). + * + * @param Twig_LoaderInterface $loader + * @param array $options An array of options + */ + public function __construct(Twig_LoaderInterface $loader = null, $options = array()) + { + if (null !== $loader) { + $this->setLoader($loader); + } else { + @trigger_error('Not passing a Twig_LoaderInterface as the first constructor argument of Twig_Environment is deprecated since version 1.21.', E_USER_DEPRECATED); + } + + $options = array_merge(array( + 'debug' => false, + 'charset' => 'UTF-8', + 'base_template_class' => 'Twig_Template', + 'strict_variables' => false, + 'autoescape' => 'html', + 'cache' => false, + 'auto_reload' => null, + 'optimizations' => -1, + ), $options); + + $this->debug = (bool) $options['debug']; + $this->charset = strtoupper($options['charset']); + $this->baseTemplateClass = $options['base_template_class']; + $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; + $this->strictVariables = (bool) $options['strict_variables']; + $this->setCache($options['cache']); + + $this->addExtension(new Twig_Extension_Core()); + $this->addExtension(new Twig_Extension_Escaper($options['autoescape'])); + $this->addExtension(new Twig_Extension_Optimizer($options['optimizations'])); + $this->staging = new Twig_Extension_Staging(); + + // For BC + if (is_string($this->originalCache)) { + $r = new ReflectionMethod($this, 'writeCacheFile'); + if ($r->getDeclaringClass()->getName() !== __CLASS__) { + @trigger_error('The Twig_Environment::writeCacheFile method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED); + + $this->bcWriteCacheFile = true; + } + + $r = new ReflectionMethod($this, 'getCacheFilename'); + if ($r->getDeclaringClass()->getName() !== __CLASS__) { + @trigger_error('The Twig_Environment::getCacheFilename method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED); + + $this->bcGetCacheFilename = true; + } + } + } + + /** + * Gets the base template class for compiled templates. + * + * @return string The base template class name + */ + public function getBaseTemplateClass() + { + return $this->baseTemplateClass; + } + + /** + * Sets the base template class for compiled templates. + * + * @param string $class The base template class name + */ + public function setBaseTemplateClass($class) + { + $this->baseTemplateClass = $class; + $this->updateOptionsHash(); + } + + /** + * Enables debugging mode. + */ + public function enableDebug() + { + $this->debug = true; + $this->updateOptionsHash(); + } + + /** + * Disables debugging mode. + */ + public function disableDebug() + { + $this->debug = false; + $this->updateOptionsHash(); + } + + /** + * Checks if debug mode is enabled. + * + * @return bool true if debug mode is enabled, false otherwise + */ + public function isDebug() + { + return $this->debug; + } + + /** + * Enables the auto_reload option. + */ + public function enableAutoReload() + { + $this->autoReload = true; + } + + /** + * Disables the auto_reload option. + */ + public function disableAutoReload() + { + $this->autoReload = false; + } + + /** + * Checks if the auto_reload option is enabled. + * + * @return bool true if auto_reload is enabled, false otherwise + */ + public function isAutoReload() + { + return $this->autoReload; + } + + /** + * Enables the strict_variables option. + */ + public function enableStrictVariables() + { + $this->strictVariables = true; + $this->updateOptionsHash(); + } + + /** + * Disables the strict_variables option. + */ + public function disableStrictVariables() + { + $this->strictVariables = false; + $this->updateOptionsHash(); + } + + /** + * Checks if the strict_variables option is enabled. + * + * @return bool true if strict_variables is enabled, false otherwise + */ + public function isStrictVariables() + { + return $this->strictVariables; + } + + /** + * Gets the current cache implementation. + * + * @param bool $original Whether to return the original cache option or the real cache instance + * + * @return Twig_CacheInterface|string|false A Twig_CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ + public function getCache($original = true) + { + return $original ? $this->originalCache : $this->cache; + } + + /** + * Sets the current cache implementation. + * + * @param Twig_CacheInterface|string|false $cache A Twig_CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ + public function setCache($cache) + { + if (is_string($cache)) { + $this->originalCache = $cache; + $this->cache = new Twig_Cache_Filesystem($cache); + } elseif (false === $cache) { + $this->originalCache = $cache; + $this->cache = new Twig_Cache_Null(); + } elseif (null === $cache) { + @trigger_error('Using "null" as the cache strategy is deprecated since version 1.23 and will be removed in Twig 2.0.', E_USER_DEPRECATED); + $this->originalCache = false; + $this->cache = new Twig_Cache_Null(); + } elseif ($cache instanceof Twig_CacheInterface) { + $this->originalCache = $this->cache = $cache; + } else { + throw new LogicException(sprintf('Cache can only be a string, false, or a Twig_CacheInterface implementation.')); + } + } + + /** + * Gets the cache filename for a given template. + * + * @param string $name The template name + * + * @return string|false The cache file name or false when caching is disabled + * + * @deprecated since 1.22 (to be removed in 2.0) + */ + public function getCacheFilename($name) + { + @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + $key = $this->cache->generateKey($name, $this->getTemplateClass($name)); + + return !$key ? false : $key; + } + + /** + * Gets the template class associated with the given string. + * + * The generated template class is based on the following parameters: + * + * * The cache key for the given template; + * * The currently enabled extensions; + * * Whether the Twig C extension is available or not; + * * PHP version; + * * Twig version; + * * Options with what environment was created. + * + * @param string $name The name for which to calculate the template class name + * @param int|null $index The index if it is an embedded template + * + * @return string The template class name + */ + public function getTemplateClass($name, $index = null) + { + $key = $this->getLoader()->getCacheKey($name).$this->optionsHash; + + return $this->templateClassPrefix.hash('sha256', $key).(null === $index ? '' : '_'.$index); + } + + /** + * Gets the template class prefix. + * + * @return string The template class prefix + * + * @deprecated since 1.22 (to be removed in 2.0) + */ + public function getTemplateClassPrefix() + { + @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + return $this->templateClassPrefix; + } + + /** + * Renders a template. + * + * @param string $name The template name + * @param array $context An array of parameters to pass to the template + * + * @return string The rendered template + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Syntax When an error occurred during compilation + * @throws Twig_Error_Runtime When an error occurred during rendering + */ + public function render($name, array $context = array()) + { + return $this->loadTemplate($name)->render($context); + } + + /** + * Displays a template. + * + * @param string $name The template name + * @param array $context An array of parameters to pass to the template + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Syntax When an error occurred during compilation + * @throws Twig_Error_Runtime When an error occurred during rendering + */ + public function display($name, array $context = array()) + { + $this->loadTemplate($name)->display($context); + } + + /** + * Loads a template. + * + * @param string|Twig_TemplateWrapper|Twig_Template $name The template name + * + * @return Twig_TemplateWrapper + */ + public function load($name) + { + if ($name instanceof Twig_TemplateWrapper) { + return $name; + } + + if ($name instanceof Twig_Template) { + return new Twig_TemplateWrapper($this, $name); + } + + return new Twig_TemplateWrapper($this, $this->loadTemplate($name)); + } + + /** + * Loads a template internal representation. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The template name + * @param int $index The index if it is an embedded template + * + * @return Twig_TemplateInterface A template instance representing the given template name + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Syntax When an error occurred during compilation + * + * @internal + */ + public function loadTemplate($name, $index = null) + { + $cls = $this->getTemplateClass($name, $index); + + if (isset($this->loadedTemplates[$cls])) { + return $this->loadedTemplates[$cls]; + } + + if (!class_exists($cls, false)) { + if ($this->bcGetCacheFilename) { + $key = $this->getCacheFilename($name); + } else { + $key = $this->cache->generateKey($name, $cls); + } + + if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) { + $this->cache->load($key); + } + + if (!class_exists($cls, false)) { + $loader = $this->getLoader(); + if (!$loader instanceof Twig_SourceContextLoaderInterface) { + $source = new Twig_Source($loader->getSource($name), $name); + } else { + $source = $loader->getSourceContext($name); + } + + $content = $this->compileSource($source); + + if ($this->bcWriteCacheFile) { + $this->writeCacheFile($key, $content); + } else { + $this->cache->write($key, $content); + $this->cache->load($key); + } + + if (!class_exists($cls, false)) { + /* Last line of defense if either $this->bcWriteCacheFile was used, + * $this->cache is implemented as a no-op or we have a race condition + * where the cache was cleared between the above calls to write to and load from + * the cache. + */ + eval('?>'.$content); + } + } + } + + if (!$this->runtimeInitialized) { + $this->initRuntime(); + } + + return $this->loadedTemplates[$cls] = new $cls($this); + } + + /** + * Creates a template from source. + * + * This method should not be used as a generic way to load templates. + * + * @param string $template The template name + * + * @return Twig_Template A template instance representing the given template name + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Syntax When an error occurred during compilation + */ + public function createTemplate($template) + { + $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), true), false)); + + $loader = new Twig_Loader_Chain(array( + new Twig_Loader_Array(array($name => $template)), + $current = $this->getLoader(), + )); + + $this->setLoader($loader); + try { + $template = $this->loadTemplate($name); + } catch (Exception $e) { + $this->setLoader($current); + + throw $e; + } catch (Throwable $e) { + $this->setLoader($current); + + throw $e; + } + $this->setLoader($current); + + return $template; + } + + /** + * Returns true if the template is still fresh. + * + * Besides checking the loader for freshness information, + * this method also checks if the enabled extensions have + * not changed. + * + * @param string $name The template name + * @param int $time The last modification time of the cached template + * + * @return bool true if the template is fresh, false otherwise + */ + public function isTemplateFresh($name, $time) + { + if (0 === $this->lastModifiedExtension) { + foreach ($this->extensions as $extension) { + $r = new ReflectionObject($extension); + if (file_exists($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModifiedExtension) { + $this->lastModifiedExtension = $extensionTime; + } + } + } + + return $this->lastModifiedExtension <= $time && $this->getLoader()->isFresh($name, $time); + } + + /** + * Tries to load a template consecutively from an array. + * + * Similar to loadTemplate() but it also accepts Twig_TemplateInterface instances and an array + * of templates where each is tried to be loaded. + * + * @param string|Twig_Template|array $names A template or an array of templates to try consecutively + * + * @return Twig_Template + * + * @throws Twig_Error_Loader When none of the templates can be found + * @throws Twig_Error_Syntax When an error occurred during compilation + */ + public function resolveTemplate($names) + { + if (!is_array($names)) { + $names = array($names); + } + + foreach ($names as $name) { + if ($name instanceof Twig_Template) { + return $name; + } + + try { + return $this->loadTemplate($name); + } catch (Twig_Error_Loader $e) { + } + } + + if (1 === count($names)) { + throw $e; + } + + throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); + } + + /** + * Clears the internal template cache. + * + * @deprecated since 1.18.3 (to be removed in 2.0) + */ + public function clearTemplateCache() + { + @trigger_error(sprintf('The %s method is deprecated since version 1.18.3 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + $this->loadedTemplates = array(); + } + + /** + * Clears the template cache files on the filesystem. + * + * @deprecated since 1.22 (to be removed in 2.0) + */ + public function clearCacheFiles() + { + @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + if (is_string($this->originalCache)) { + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->originalCache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($file->isFile()) { + @unlink($file->getPathname()); + } + } + } + } + + /** + * Gets the Lexer instance. + * + * @return Twig_LexerInterface + * + * @deprecated since 1.25 (to be removed in 2.0) + */ + public function getLexer() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + + if (null === $this->lexer) { + $this->lexer = new Twig_Lexer($this); + } + + return $this->lexer; + } + + public function setLexer(Twig_LexerInterface $lexer) + { + $this->lexer = $lexer; + } + + /** + * Tokenizes a source code. + * + * @param string|Twig_Source $source The template source code + * @param string $name The template name (deprecated) + * + * @return Twig_TokenStream + * + * @throws Twig_Error_Syntax When the code is syntactically wrong + */ + public function tokenize($source, $name = null) + { + if (!$source instanceof Twig_Source) { + @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); + $source = new Twig_Source($source, $name); + } + + if (null === $this->lexer) { + $this->lexer = new Twig_Lexer($this); + } + + return $this->lexer->tokenize($source); + } + + /** + * Gets the Parser instance. + * + * @return Twig_ParserInterface + * + * @deprecated since 1.25 (to be removed in 2.0) + */ + public function getParser() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + + if (null === $this->parser) { + $this->parser = new Twig_Parser($this); + } + + return $this->parser; + } + + public function setParser(Twig_ParserInterface $parser) + { + $this->parser = $parser; + } + + /** + * Converts a token stream to a node tree. + * + * @return Twig_Node_Module + * + * @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong + */ + public function parse(Twig_TokenStream $stream) + { + if (null === $this->parser) { + $this->parser = new Twig_Parser($this); + } + + return $this->parser->parse($stream); + } + + /** + * Gets the Compiler instance. + * + * @return Twig_CompilerInterface + * + * @deprecated since 1.25 (to be removed in 2.0) + */ + public function getCompiler() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + + if (null === $this->compiler) { + $this->compiler = new Twig_Compiler($this); + } + + return $this->compiler; + } + + public function setCompiler(Twig_CompilerInterface $compiler) + { + $this->compiler = $compiler; + } + + /** + * Compiles a node and returns the PHP code. + * + * @return string The compiled PHP source code + */ + public function compile(Twig_NodeInterface $node) + { + if (null === $this->compiler) { + $this->compiler = new Twig_Compiler($this); + } + + return $this->compiler->compile($node)->getSource(); + } + + /** + * Compiles a template source code. + * + * @param string|Twig_Source $source The template source code + * @param string $name The template name (deprecated) + * + * @return string The compiled PHP source code + * + * @throws Twig_Error_Syntax When there was an error during tokenizing, parsing or compiling + */ + public function compileSource($source, $name = null) + { + if (!$source instanceof Twig_Source) { + @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); + $source = new Twig_Source($source, $name); + } + + try { + return $this->compile($this->parse($this->tokenize($source))); + } catch (Twig_Error $e) { + $e->setSourceContext($source); + throw $e; + } catch (Exception $e) { + throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e); + } + } + + public function setLoader(Twig_LoaderInterface $loader) + { + if (!$loader instanceof Twig_SourceContextLoaderInterface && 0 !== strpos(get_class($loader), 'Mock_Twig_LoaderInterface')) { + @trigger_error(sprintf('Twig loader "%s" should implement Twig_SourceContextLoaderInterface since version 1.27.', get_class($loader)), E_USER_DEPRECATED); + } + + $this->loader = $loader; + } + + /** + * Gets the Loader instance. + * + * @return Twig_LoaderInterface + */ + public function getLoader() + { + if (null === $this->loader) { + throw new LogicException('You must set a loader first.'); + } + + return $this->loader; + } + + /** + * Sets the default template charset. + * + * @param string $charset The default charset + */ + public function setCharset($charset) + { + $this->charset = strtoupper($charset); + } + + /** + * Gets the default template charset. + * + * @return string The default charset + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Initializes the runtime environment. + * + * @deprecated since 1.23 (to be removed in 2.0) + */ + public function initRuntime() + { + $this->runtimeInitialized = true; + + foreach ($this->getExtensions() as $name => $extension) { + if (!$extension instanceof Twig_Extension_InitRuntimeInterface) { + $m = new ReflectionMethod($extension, 'initRuntime'); + + if ('Twig_Extension' !== $m->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Defining the initRuntime() method in the "%s" extension is deprecated since version 1.23. Use the `needs_environment` option to get the Twig_Environment instance in filters, functions, or tests; or explicitly implement Twig_Extension_InitRuntimeInterface if needed (not recommended).', $name), E_USER_DEPRECATED); + } + } + + $extension->initRuntime($this); + } + } + + /** + * Returns true if the given extension is registered. + * + * @param string $class The extension class name + * + * @return bool Whether the extension is registered or not + */ + public function hasExtension($class) + { + $class = ltrim($class, '\\'); + if (isset($this->extensions[$class])) { + if ($class !== get_class($this->extensions[$class])) { + @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + } + + return true; + } + + return isset($this->extensionsByClass[ltrim($class, '\\')]); + } + + /** + * Adds a runtime loader. + */ + public function addRuntimeLoader(Twig_RuntimeLoaderInterface $loader) + { + $this->runtimeLoaders[] = $loader; + } + + /** + * Gets an extension by class name. + * + * @param string $class The extension class name + * + * @return Twig_ExtensionInterface + */ + public function getExtension($class) + { + $class = ltrim($class, '\\'); + + if (isset($this->extensions[$class])) { + if ($class !== get_class($this->extensions[$class])) { + @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + } + + return $this->extensions[$class]; + } + + if (!isset($this->extensionsByClass[$class])) { + throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $class)); + } + + return $this->extensionsByClass[$class]; + } + + /** + * Returns the runtime implementation of a Twig element (filter/function/test). + * + * @param string $class A runtime class name + * + * @return object The runtime implementation + * + * @throws Twig_Error_Runtime When the template cannot be found + */ + public function getRuntime($class) + { + if (isset($this->runtimes[$class])) { + return $this->runtimes[$class]; + } + + foreach ($this->runtimeLoaders as $loader) { + if (null !== $runtime = $loader->load($class)) { + return $this->runtimes[$class] = $runtime; + } + } + + throw new Twig_Error_Runtime(sprintf('Unable to load the "%s" runtime.', $class)); + } + + public function addExtension(Twig_ExtensionInterface $extension) + { + if ($this->extensionInitialized) { + throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName())); + } + + $class = get_class($extension); + if ($class !== $extension->getName()) { + if (isset($this->extensions[$extension->getName()])) { + unset($this->extensions[$extension->getName()], $this->extensionsByClass[$class]); + @trigger_error(sprintf('The possibility to register the same extension twice ("%s") is deprecated since version 1.23 and will be removed in Twig 2.0. Use proper PHP inheritance instead.', $extension->getName()), E_USER_DEPRECATED); + } + } + + $this->lastModifiedExtension = 0; + $this->extensionsByClass[$class] = $extension; + $this->extensions[$extension->getName()] = $extension; + $this->updateOptionsHash(); + } + + /** + * Removes an extension by name. + * + * This method is deprecated and you should not use it. + * + * @param string $name The extension name + * + * @deprecated since 1.12 (to be removed in 2.0) + */ + public function removeExtension($name) + { + @trigger_error(sprintf('The %s method is deprecated since version 1.12 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + if ($this->extensionInitialized) { + throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name)); + } + + $class = ltrim($name, '\\'); + if (isset($this->extensions[$class])) { + if ($class !== get_class($this->extensions[$class])) { + @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + } + + unset($this->extensions[$class]); + } + + unset($this->extensions[$class]); + $this->updateOptionsHash(); + } + + /** + * Registers an array of extensions. + * + * @param array $extensions An array of extensions + */ + public function setExtensions(array $extensions) + { + foreach ($extensions as $extension) { + $this->addExtension($extension); + } + } + + /** + * Returns all registered extensions. + * + * @return Twig_ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on) + */ + public function getExtensions() + { + return $this->extensions; + } + + public function addTokenParser(Twig_TokenParserInterface $parser) + { + if ($this->extensionInitialized) { + throw new LogicException('Unable to add a token parser as extensions have already been initialized.'); + } + + $this->staging->addTokenParser($parser); + } + + /** + * Gets the registered Token Parsers. + * + * @return Twig_TokenParserBrokerInterface + * + * @internal + */ + public function getTokenParsers() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->parsers; + } + + /** + * Gets registered tags. + * + * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes. + * + * @return Twig_TokenParserInterface[] + * + * @internal + */ + public function getTags() + { + $tags = array(); + foreach ($this->getTokenParsers()->getParsers() as $parser) { + if ($parser instanceof Twig_TokenParserInterface) { + $tags[$parser->getTag()] = $parser; + } + } + + return $tags; + } + + public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) + { + if ($this->extensionInitialized) { + throw new LogicException('Unable to add a node visitor as extensions have already been initialized.'); + } + + $this->staging->addNodeVisitor($visitor); + } + + /** + * Gets the registered Node Visitors. + * + * @return Twig_NodeVisitorInterface[] + * + * @internal + */ + public function getNodeVisitors() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->visitors; + } + + /** + * Registers a Filter. + * + * @param string|Twig_SimpleFilter $name The filter name or a Twig_SimpleFilter instance + * @param Twig_FilterInterface|Twig_SimpleFilter $filter + */ + public function addFilter($name, $filter = null) + { + if (!$name instanceof Twig_SimpleFilter && !($filter instanceof Twig_SimpleFilter || $filter instanceof Twig_FilterInterface)) { + throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter.'); + } + + if ($name instanceof Twig_SimpleFilter) { + $filter = $name; + $name = $filter->getName(); + } else { + @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFilter" instead when defining filter "%s".', __METHOD__, $name), E_USER_DEPRECATED); + } + + if ($this->extensionInitialized) { + throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name)); + } + + $this->staging->addFilter($name, $filter); + } + + /** + * Get a filter by name. + * + * Subclasses may override this method and load filters differently; + * so no list of filters is available. + * + * @param string $name The filter name + * + * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exist + * + * @internal + */ + public function getFilter($name) + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + if (isset($this->filters[$name])) { + return $this->filters[$name]; + } + + foreach ($this->filters as $pattern => $filter) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count) { + if (preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $filter->setArguments($matches); + + return $filter; + } + } + } + + foreach ($this->filterCallbacks as $callback) { + if (false !== $filter = call_user_func($callback, $name)) { + return $filter; + } + } + + return false; + } + + public function registerUndefinedFilterCallback($callable) + { + $this->filterCallbacks[] = $callable; + } + + /** + * Gets the registered Filters. + * + * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback. + * + * @return Twig_FilterInterface[] + * + * @see registerUndefinedFilterCallback + * + * @internal + */ + public function getFilters() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->filters; + } + + /** + * Registers a Test. + * + * @param string|Twig_SimpleTest $name The test name or a Twig_SimpleTest instance + * @param Twig_TestInterface|Twig_SimpleTest $test A Twig_TestInterface instance or a Twig_SimpleTest instance + */ + public function addTest($name, $test = null) + { + if (!$name instanceof Twig_SimpleTest && !($test instanceof Twig_SimpleTest || $test instanceof Twig_TestInterface)) { + throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest.'); + } + + if ($name instanceof Twig_SimpleTest) { + $test = $name; + $name = $test->getName(); + } else { + @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleTest" instead when defining test "%s".', __METHOD__, $name), E_USER_DEPRECATED); + } + + if ($this->extensionInitialized) { + throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name)); + } + + $this->staging->addTest($name, $test); + } + + /** + * Gets the registered Tests. + * + * @return Twig_TestInterface[] + * + * @internal + */ + public function getTests() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->tests; + } + + /** + * Gets a test by name. + * + * @param string $name The test name + * + * @return Twig_Test|false A Twig_Test instance or false if the test does not exist + * + * @internal + */ + public function getTest($name) + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + if (isset($this->tests[$name])) { + return $this->tests[$name]; + } + + return false; + } + + /** + * Registers a Function. + * + * @param string|Twig_SimpleFunction $name The function name or a Twig_SimpleFunction instance + * @param Twig_FunctionInterface|Twig_SimpleFunction $function + */ + public function addFunction($name, $function = null) + { + if (!$name instanceof Twig_SimpleFunction && !($function instanceof Twig_SimpleFunction || $function instanceof Twig_FunctionInterface)) { + throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction.'); + } + + if ($name instanceof Twig_SimpleFunction) { + $function = $name; + $name = $function->getName(); + } else { + @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFunction" instead when defining function "%s".', __METHOD__, $name), E_USER_DEPRECATED); + } + + if ($this->extensionInitialized) { + throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name)); + } + + $this->staging->addFunction($name, $function); + } + + /** + * Get a function by name. + * + * Subclasses may override this method and load functions differently; + * so no list of functions is available. + * + * @param string $name function name + * + * @return Twig_Function|false A Twig_Function instance or false if the function does not exist + * + * @internal + */ + public function getFunction($name) + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + if (isset($this->functions[$name])) { + return $this->functions[$name]; + } + + foreach ($this->functions as $pattern => $function) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count) { + if (preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $function->setArguments($matches); + + return $function; + } + } + } + + foreach ($this->functionCallbacks as $callback) { + if (false !== $function = call_user_func($callback, $name)) { + return $function; + } + } + + return false; + } + + public function registerUndefinedFunctionCallback($callable) + { + $this->functionCallbacks[] = $callable; + } + + /** + * Gets registered functions. + * + * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback. + * + * @return Twig_FunctionInterface[] + * + * @see registerUndefinedFunctionCallback + * + * @internal + */ + public function getFunctions() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->functions; + } + + /** + * Registers a Global. + * + * New globals can be added before compiling or rendering a template; + * but after, you can only update existing globals. + * + * @param string $name The global name + * @param mixed $value The global value + */ + public function addGlobal($name, $value) + { + if ($this->extensionInitialized || $this->runtimeInitialized) { + if (null === $this->globals) { + $this->globals = $this->initGlobals(); + } + + if (!array_key_exists($name, $this->globals)) { + // The deprecation notice must be turned into the following exception in Twig 2.0 + @trigger_error(sprintf('Registering global variable "%s" at runtime or when the extensions have already been initialized is deprecated since version 1.21.', $name), E_USER_DEPRECATED); + //throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); + } + } + + if ($this->extensionInitialized || $this->runtimeInitialized) { + // update the value + $this->globals[$name] = $value; + } else { + $this->staging->addGlobal($name, $value); + } + } + + /** + * Gets the registered Globals. + * + * @return array An array of globals + * + * @internal + */ + public function getGlobals() + { + if (!$this->runtimeInitialized && !$this->extensionInitialized) { + return $this->initGlobals(); + } + + if (null === $this->globals) { + $this->globals = $this->initGlobals(); + } + + return $this->globals; + } + + /** + * Merges a context with the defined globals. + * + * @param array $context An array representing the context + * + * @return array The context merged with the globals + */ + public function mergeGlobals(array $context) + { + // we don't use array_merge as the context being generally + // bigger than globals, this code is faster. + foreach ($this->getGlobals() as $key => $value) { + if (!array_key_exists($key, $context)) { + $context[$key] = $value; + } + } + + return $context; + } + + /** + * Gets the registered unary Operators. + * + * @return array An array of unary operators + * + * @internal + */ + public function getUnaryOperators() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->unaryOperators; + } + + /** + * Gets the registered binary Operators. + * + * @return array An array of binary operators + * + * @internal + */ + public function getBinaryOperators() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->binaryOperators; + } + + /** + * @deprecated since 1.23 (to be removed in 2.0) + */ + public function computeAlternatives($name, $items) + { + @trigger_error(sprintf('The %s method is deprecated since version 1.23 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + return Twig_Error_Syntax::computeAlternatives($name, $items); + } + + /** + * @internal + */ + protected function initGlobals() + { + $globals = array(); + foreach ($this->extensions as $name => $extension) { + if (!$extension instanceof Twig_Extension_GlobalsInterface) { + $m = new ReflectionMethod($extension, 'getGlobals'); + + if ('Twig_Extension' !== $m->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Defining the getGlobals() method in the "%s" extension without explicitly implementing Twig_Extension_GlobalsInterface is deprecated since version 1.23.', $name), E_USER_DEPRECATED); + } + } + + $extGlob = $extension->getGlobals(); + if (!is_array($extGlob)) { + throw new UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', get_class($extension))); + } + + $globals[] = $extGlob; + } + + $globals[] = $this->staging->getGlobals(); + + return call_user_func_array('array_merge', $globals); + } + + /** + * @internal + */ + protected function initExtensions() + { + if ($this->extensionInitialized) { + return; + } + + $this->extensionInitialized = true; + $this->parsers = new Twig_TokenParserBroker(array(), array(), false); + $this->filters = array(); + $this->functions = array(); + $this->tests = array(); + $this->visitors = array(); + $this->unaryOperators = array(); + $this->binaryOperators = array(); + + foreach ($this->extensions as $extension) { + $this->initExtension($extension); + } + $this->initExtension($this->staging); + } + + /** + * @internal + */ + protected function initExtension(Twig_ExtensionInterface $extension) + { + // filters + foreach ($extension->getFilters() as $name => $filter) { + if ($filter instanceof Twig_SimpleFilter) { + $name = $filter->getName(); + } else { + @trigger_error(sprintf('Using an instance of "%s" for filter "%s" is deprecated since version 1.21. Use Twig_SimpleFilter instead.', get_class($filter), $name), E_USER_DEPRECATED); + } + + $this->filters[$name] = $filter; + } + + // functions + foreach ($extension->getFunctions() as $name => $function) { + if ($function instanceof Twig_SimpleFunction) { + $name = $function->getName(); + } else { + @trigger_error(sprintf('Using an instance of "%s" for function "%s" is deprecated since version 1.21. Use Twig_SimpleFunction instead.', get_class($function), $name), E_USER_DEPRECATED); + } + + $this->functions[$name] = $function; + } + + // tests + foreach ($extension->getTests() as $name => $test) { + if ($test instanceof Twig_SimpleTest) { + $name = $test->getName(); + } else { + @trigger_error(sprintf('Using an instance of "%s" for test "%s" is deprecated since version 1.21. Use Twig_SimpleTest instead.', get_class($test), $name), E_USER_DEPRECATED); + } + + $this->tests[$name] = $test; + } + + // token parsers + foreach ($extension->getTokenParsers() as $parser) { + if ($parser instanceof Twig_TokenParserInterface) { + $this->parsers->addTokenParser($parser); + } elseif ($parser instanceof Twig_TokenParserBrokerInterface) { + @trigger_error('Registering a Twig_TokenParserBrokerInterface instance is deprecated since version 1.21.', E_USER_DEPRECATED); + + $this->parsers->addTokenParserBroker($parser); + } else { + throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances.'); + } + } + + // node visitors + foreach ($extension->getNodeVisitors() as $visitor) { + $this->visitors[] = $visitor; + } + + // operators + if ($operators = $extension->getOperators()) { + if (!is_array($operators)) { + throw new InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', get_class($extension), is_object($operators) ? get_class($operators) : gettype($operators).(is_resource($operators) ? '' : '#'.$operators))); + } + + if (2 !== count($operators)) { + throw new InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', get_class($extension), count($operators))); + } + + $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); + $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); + } + } + + /** + * @deprecated since 1.22 (to be removed in 2.0) + */ + protected function writeCacheFile($file, $content) + { + $this->cache->write($file, $content); + } + + private function updateOptionsHash() + { + $hashParts = array_merge( + array_keys($this->extensions), + array( + (int) function_exists('twig_template_get_attributes'), + PHP_MAJOR_VERSION, + PHP_MINOR_VERSION, + self::VERSION, + (int) $this->debug, + $this->baseTemplateClass, + (int) $this->strictVariables, + ) + ); + $this->optionsHash = implode(':', $hashParts); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Error.php b/src/vendor/twig/twig/lib/Twig/Error.php new file mode 100644 index 0000000..28db05c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Error.php @@ -0,0 +1,365 @@ + + */ +class Twig_Error extends Exception +{ + protected $lineno; + // to be renamed to name in 2.0 + protected $filename; + protected $rawMessage; + protected $previous; + + private $sourcePath; + private $sourceCode; + + /** + * Constructor. + * + * Set both the line number and the name to false to + * disable automatic guessing of the original template name + * and line number. + * + * Set the line number to -1 to enable its automatic guessing. + * Set the name to null to enable its automatic guessing. + * + * By default, automatic guessing is enabled. + * + * @param string $message The error message + * @param int $lineno The template line where the error occurred + * @param Twig_Source|string|null $source The source context where the error occurred + * @param Exception $previous The previous exception + */ + public function __construct($message, $lineno = -1, $source = null, Exception $previous = null) + { + if (null === $source) { + $name = null; + } elseif (!$source instanceof Twig_Source) { + // for compat with the Twig C ext., passing the template name as string is accepted + $name = $source; + } else { + $name = $source->getName(); + $this->sourceCode = $source->getCode(); + $this->sourcePath = $source->getPath(); + } + if (PHP_VERSION_ID < 50300) { + $this->previous = $previous; + parent::__construct(''); + } else { + parent::__construct('', 0, $previous); + } + + $this->lineno = $lineno; + $this->filename = $name; + + if (-1 === $lineno || null === $name || null === $this->sourcePath) { + $this->guessTemplateInfo(); + } + + $this->rawMessage = $message; + + $this->updateRepr(); + } + + /** + * Gets the raw message. + * + * @return string The raw message + */ + public function getRawMessage() + { + return $this->rawMessage; + } + + /** + * Gets the logical name where the error occurred. + * + * @return string The name + * + * @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead. + */ + public function getTemplateFile() + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->filename; + } + + /** + * Sets the logical name where the error occurred. + * + * @param string $name The name + * + * @deprecated since 1.27 (to be removed in 2.0). Use setSourceContext() instead. + */ + public function setTemplateFile($name) + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + $this->filename = $name; + + $this->updateRepr(); + } + + /** + * Gets the logical name where the error occurred. + * + * @return string The name + * + * @deprecated since 1.29 (to be removed in 2.0). Use getSourceContext() instead. + */ + public function getTemplateName() + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->filename; + } + + /** + * Sets the logical name where the error occurred. + * + * @param string $name The name + * + * @deprecated since 1.29 (to be removed in 2.0). Use setSourceContext() instead. + */ + public function setTemplateName($name) + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + $this->filename = $name; + $this->sourceCode = $this->sourcePath = null; + + $this->updateRepr(); + } + + /** + * Gets the template line where the error occurred. + * + * @return int The template line + */ + public function getTemplateLine() + { + return $this->lineno; + } + + /** + * Sets the template line where the error occurred. + * + * @param int $lineno The template line + */ + public function setTemplateLine($lineno) + { + $this->lineno = $lineno; + + $this->updateRepr(); + } + + /** + * Gets the source context of the Twig template where the error occurred. + * + * @return Twig_Source|null + */ + public function getSourceContext() + { + return $this->filename ? new Twig_Source($this->sourceCode, $this->filename, $this->sourcePath) : null; + } + + /** + * Sets the source context of the Twig template where the error occurred. + */ + public function setSourceContext(Twig_Source $source = null) + { + if (null === $source) { + $this->sourceCode = $this->filename = $this->sourcePath = null; + } else { + $this->sourceCode = $source->getCode(); + $this->filename = $source->getName(); + $this->sourcePath = $source->getPath(); + } + + $this->updateRepr(); + } + + public function guess() + { + $this->guessTemplateInfo(); + $this->updateRepr(); + } + + /** + * For PHP < 5.3.0, provides access to the getPrevious() method. + * + * @param string $method The method name + * @param array $arguments The parameters to be passed to the method + * + * @return Exception The previous exception or null + * + * @throws BadMethodCallException + */ + public function __call($method, $arguments) + { + if ('getprevious' == strtolower($method)) { + return $this->previous; + } + + throw new BadMethodCallException(sprintf('Method "Twig_Error::%s()" does not exist.', $method)); + } + + public function appendMessage($rawMessage) + { + $this->rawMessage .= $rawMessage; + $this->updateRepr(); + } + + /** + * @internal + */ + protected function updateRepr() + { + $this->message = $this->rawMessage; + + if ($this->sourcePath && $this->lineno > 0) { + $this->file = $this->sourcePath; + $this->line = $this->lineno; + + return; + } + + $dot = false; + if ('.' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + $questionMark = false; + if ('?' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $questionMark = true; + } + + if ($this->filename) { + if (is_string($this->filename) || (is_object($this->filename) && method_exists($this->filename, '__toString'))) { + $name = sprintf('"%s"', $this->filename); + } else { + $name = json_encode($this->filename); + } + $this->message .= sprintf(' in %s', $name); + } + + if ($this->lineno && $this->lineno >= 0) { + $this->message .= sprintf(' at line %d', $this->lineno); + } + + if ($dot) { + $this->message .= '.'; + } + + if ($questionMark) { + $this->message .= '?'; + } + } + + /** + * @internal + */ + protected function guessTemplateInfo() + { + $template = null; + $templateClass = null; + + if (PHP_VERSION_ID >= 50306) { + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT); + } else { + $backtrace = debug_backtrace(); + } + + foreach ($backtrace as $trace) { + if (isset($trace['object']) && $trace['object'] instanceof Twig_Template && 'Twig_Template' !== get_class($trace['object'])) { + $currentClass = get_class($trace['object']); + $isEmbedContainer = 0 === strpos($templateClass, $currentClass); + if (null === $this->filename || ($this->filename == $trace['object']->getTemplateName() && !$isEmbedContainer)) { + $template = $trace['object']; + $templateClass = get_class($trace['object']); + } + } + } + + // update template name + if (null !== $template && null === $this->filename) { + $this->filename = $template->getTemplateName(); + } + + // update template path if any + if (null !== $template && null === $this->sourcePath) { + $src = $template->getSourceContext(); + $this->sourceCode = $src->getCode(); + $this->sourcePath = $src->getPath(); + } + + if (null === $template || $this->lineno > -1) { + return; + } + + $r = new ReflectionObject($template); + $file = $r->getFileName(); + + // hhvm has a bug where eval'ed files comes out as the current directory + if (is_dir($file)) { + $file = ''; + } + + $exceptions = array($e = $this); + while (($e instanceof self || method_exists($e, 'getPrevious')) && $e = $e->getPrevious()) { + $exceptions[] = $e; + } + + while ($e = array_pop($exceptions)) { + $traces = $e->getTrace(); + array_unshift($traces, array('file' => $e->getFile(), 'line' => $e->getLine())); + + while ($trace = array_shift($traces)) { + if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) { + continue; + } + + foreach ($template->getDebugInfo() as $codeLine => $templateLine) { + if ($codeLine <= $trace['line']) { + // update template line + $this->lineno = $templateLine; + + return; + } + } + } + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Error/Loader.php b/src/vendor/twig/twig/lib/Twig/Error/Loader.php new file mode 100644 index 0000000..dd514e7 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Error/Loader.php @@ -0,0 +1,38 @@ + + */ +class Twig_Error_Loader extends Twig_Error +{ + public function __construct($message, $lineno = -1, $source = null, Exception $previous = null) + { + if (PHP_VERSION_ID < 50300) { + $this->previous = $previous; + Exception::__construct(''); + } else { + Exception::__construct('', 0, $previous); + } + $this->appendMessage($message); + $this->setTemplateLine(false); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Error/Runtime.php b/src/vendor/twig/twig/lib/Twig/Error/Runtime.php new file mode 100644 index 0000000..8b6cedd --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Error/Runtime.php @@ -0,0 +1,20 @@ + + */ +class Twig_Error_Runtime extends Twig_Error +{ +} diff --git a/src/vendor/twig/twig/lib/Twig/Error/Syntax.php b/src/vendor/twig/twig/lib/Twig/Error/Syntax.php new file mode 100644 index 0000000..f73730a --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Error/Syntax.php @@ -0,0 +1,53 @@ + + */ +class Twig_Error_Syntax extends Twig_Error +{ + /** + * Tweaks the error message to include suggestions. + * + * @param string $name The original name of the item that does not exist + * @param array $items An array of possible items + */ + public function addSuggestions($name, array $items) + { + if (!$alternatives = self::computeAlternatives($name, $items)) { + return; + } + + $this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', $alternatives))); + } + + /** + * @internal + * + * To be merged with the addSuggestions() method in 2.0. + */ + public static function computeAlternatives($name, $items) + { + $alternatives = array(); + foreach ($items as $item) { + $lev = levenshtein($name, $item); + if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = $lev; + } + } + asort($alternatives); + + return array_keys($alternatives); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/ExistsLoaderInterface.php b/src/vendor/twig/twig/lib/Twig/ExistsLoaderInterface.php new file mode 100644 index 0000000..b168c3c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/ExistsLoaderInterface.php @@ -0,0 +1,29 @@ + + * + * @deprecated since 1.12 (to be removed in 3.0) + */ +interface Twig_ExistsLoaderInterface +{ + /** + * Check if we have the source code of a template, given its name. + * + * @param string $name The name of the template to check if we can load + * + * @return bool If the template source code is handled by this loader or not + */ + public function exists($name); +} diff --git a/src/vendor/twig/twig/lib/Twig/ExpressionParser.php b/src/vendor/twig/twig/lib/Twig/ExpressionParser.php new file mode 100644 index 0000000..a683fb3 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/ExpressionParser.php @@ -0,0 +1,739 @@ + + * + * @internal + */ +class Twig_ExpressionParser +{ + const OPERATOR_LEFT = 1; + const OPERATOR_RIGHT = 2; + + protected $parser; + protected $unaryOperators; + protected $binaryOperators; + + private $env; + + public function __construct(Twig_Parser $parser, Twig_Environment $env = null) + { + $this->parser = $parser; + + if ($env instanceof Twig_Environment) { + $this->env = $env; + $this->unaryOperators = $env->getUnaryOperators(); + $this->binaryOperators = $env->getBinaryOperators(); + } else { + @trigger_error('Passing the operators as constructor arguments to '.__METHOD__.' is deprecated since version 1.27. Pass the environment instead.', E_USER_DEPRECATED); + + $this->env = $parser->getEnvironment(); + $this->unaryOperators = func_get_arg(1); + $this->binaryOperators = func_get_arg(2); + } + } + + public function parseExpression($precedence = 0) + { + $expr = $this->getPrimary(); + $token = $this->parser->getCurrentToken(); + while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) { + $op = $this->binaryOperators[$token->getValue()]; + $this->parser->getStream()->next(); + + if ('is not' === $token->getValue()) { + $expr = $this->parseNotTestExpression($expr); + } elseif ('is' === $token->getValue()) { + $expr = $this->parseTestExpression($expr); + } elseif (isset($op['callable'])) { + $expr = call_user_func($op['callable'], $this->parser, $expr); + } else { + $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); + $class = $op['class']; + $expr = new $class($expr, $expr1, $token->getLine()); + } + + $token = $this->parser->getCurrentToken(); + } + + if (0 === $precedence) { + return $this->parseConditionalExpression($expr); + } + + return $expr; + } + + protected function getPrimary() + { + $token = $this->parser->getCurrentToken(); + + if ($this->isUnary($token)) { + $operator = $this->unaryOperators[$token->getValue()]; + $this->parser->getStream()->next(); + $expr = $this->parseExpression($operator['precedence']); + $class = $operator['class']; + + return $this->parsePostfixExpression(new $class($expr, $token->getLine())); + } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) { + $this->parser->getStream()->next(); + $expr = $this->parseExpression(); + $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); + + return $this->parsePostfixExpression($expr); + } + + return $this->parsePrimaryExpression(); + } + + protected function parseConditionalExpression($expr) + { + while ($this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, '?')) { + if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) { + $expr2 = $this->parseExpression(); + if ($this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) { + $expr3 = $this->parseExpression(); + } else { + $expr3 = new Twig_Node_Expression_Constant('', $this->parser->getCurrentToken()->getLine()); + } + } else { + $expr2 = $expr; + $expr3 = $this->parseExpression(); + } + + $expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine()); + } + + return $expr; + } + + protected function isUnary(Twig_Token $token) + { + return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]); + } + + protected function isBinary(Twig_Token $token) + { + return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]); + } + + public function parsePrimaryExpression() + { + $token = $this->parser->getCurrentToken(); + switch ($token->getType()) { + case Twig_Token::NAME_TYPE: + $this->parser->getStream()->next(); + switch ($token->getValue()) { + case 'true': + case 'TRUE': + $node = new Twig_Node_Expression_Constant(true, $token->getLine()); + break; + + case 'false': + case 'FALSE': + $node = new Twig_Node_Expression_Constant(false, $token->getLine()); + break; + + case 'none': + case 'NONE': + case 'null': + case 'NULL': + $node = new Twig_Node_Expression_Constant(null, $token->getLine()); + break; + + default: + if ('(' === $this->parser->getCurrentToken()->getValue()) { + $node = $this->getFunctionNode($token->getValue(), $token->getLine()); + } else { + $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine()); + } + } + break; + + case Twig_Token::NUMBER_TYPE: + $this->parser->getStream()->next(); + $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); + break; + + case Twig_Token::STRING_TYPE: + case Twig_Token::INTERPOLATION_START_TYPE: + $node = $this->parseStringExpression(); + break; + + case Twig_Token::OPERATOR_TYPE: + if (preg_match(Twig_Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) { + // in this context, string operators are variable names + $this->parser->getStream()->next(); + $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine()); + break; + } elseif (isset($this->unaryOperators[$token->getValue()])) { + $class = $this->unaryOperators[$token->getValue()]['class']; + + $ref = new ReflectionClass($class); + $negClass = 'Twig_Node_Expression_Unary_Neg'; + $posClass = 'Twig_Node_Expression_Unary_Pos'; + if (!(in_array($ref->getName(), array($negClass, $posClass)) || $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass))) { + throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } + + $this->parser->getStream()->next(); + $expr = $this->parsePrimaryExpression(); + + $node = new $class($expr, $token->getLine()); + break; + } + + default: + if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) { + $node = $this->parseArrayExpression(); + } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) { + $node = $this->parseHashExpression(); + } else { + throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } + } + + return $this->parsePostfixExpression($node); + } + + public function parseStringExpression() + { + $stream = $this->parser->getStream(); + + $nodes = array(); + // a string cannot be followed by another string in a single expression + $nextCanBeString = true; + while (true) { + if ($nextCanBeString && $token = $stream->nextIf(Twig_Token::STRING_TYPE)) { + $nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); + $nextCanBeString = false; + } elseif ($stream->nextIf(Twig_Token::INTERPOLATION_START_TYPE)) { + $nodes[] = $this->parseExpression(); + $stream->expect(Twig_Token::INTERPOLATION_END_TYPE); + $nextCanBeString = true; + } else { + break; + } + } + + $expr = array_shift($nodes); + foreach ($nodes as $node) { + $expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getTemplateLine()); + } + + return $expr; + } + + public function parseArrayExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); + + $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine()); + $first = true; + while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { + if (!$first) { + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); + + // trailing ,? + if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { + break; + } + } + $first = false; + + $node->addElement($this->parseExpression()); + } + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); + + return $node; + } + + public function parseHashExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); + + $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine()); + $first = true; + while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { + if (!$first) { + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); + + // trailing ,? + if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { + break; + } + } + $first = false; + + // a hash key can be: + // + // * a number -- 12 + // * a string -- 'a' + // * a name, which is equivalent to a string -- a + // * an expression, which must be enclosed in parentheses -- (1 + 2) + if (($token = $stream->nextIf(Twig_Token::STRING_TYPE)) || ($token = $stream->nextIf(Twig_Token::NAME_TYPE)) || $token = $stream->nextIf(Twig_Token::NUMBER_TYPE)) { + $key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); + } elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { + $key = $this->parseExpression(); + } else { + $current = $stream->getCurrent(); + + throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()); + } + + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); + $value = $this->parseExpression(); + + $node->addElement($value, $key); + } + $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); + + return $node; + } + + public function parsePostfixExpression($node) + { + while (true) { + $token = $this->parser->getCurrentToken(); + if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) { + if ('.' == $token->getValue() || '[' == $token->getValue()) { + $node = $this->parseSubscriptExpression($node); + } elseif ('|' == $token->getValue()) { + $node = $this->parseFilterExpression($node); + } else { + break; + } + } else { + break; + } + } + + return $node; + } + + public function getFunctionNode($name, $line) + { + switch ($name) { + case 'parent': + $this->parseArguments(); + if (!count($this->parser->getBlockStack())) { + throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext()); + } + + if (!$this->parser->getParent() && !$this->parser->hasTraits()) { + throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext()); + } + + return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line); + case 'block': + $args = $this->parseArguments(); + if (count($args) < 1) { + throw new Twig_Error_Syntax('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext()); + } + + return new Twig_Node_Expression_BlockReference($args->getNode(0), count($args) > 1 ? $args->getNode(1) : null, $line); + case 'attribute': + $args = $this->parseArguments(); + if (count($args) < 2) { + throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext()); + } + + return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : null, Twig_Template::ANY_CALL, $line); + default: + if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { + $arguments = new Twig_Node_Expression_Array(array(), $line); + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); + } + + $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line); + $node->setAttribute('safe', true); + + return $node; + } + + $args = $this->parseArguments(true); + $class = $this->getFunctionNodeClass($name, $line); + + return new $class($name, $args, $line); + } + } + + public function parseSubscriptExpression($node) + { + $stream = $this->parser->getStream(); + $token = $stream->next(); + $lineno = $token->getLine(); + $arguments = new Twig_Node_Expression_Array(array(), $lineno); + $type = Twig_Template::ANY_CALL; + if ($token->getValue() == '.') { + $token = $stream->next(); + if ( + $token->getType() == Twig_Token::NAME_TYPE + || + $token->getType() == Twig_Token::NUMBER_TYPE + || + ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue())) + ) { + $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno); + + if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { + $type = Twig_Template::METHOD_CALL; + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); + } + } + } else { + throw new Twig_Error_Syntax('Expected name or number.', $lineno, $stream->getSourceContext()); + } + + if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { + if (!$arg instanceof Twig_Node_Expression_Constant) { + throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext()); + } + + $name = $arg->getAttribute('value'); + + if ($this->parser->isReservedMacroName($name)) { + throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()); + } + + $node = new Twig_Node_Expression_MethodCall($node, 'get'.$name, $arguments, $lineno); + $node->setAttribute('safe', true); + + return $node; + } + } else { + $type = Twig_Template::ARRAY_CALL; + + // slice? + $slice = false; + if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) { + $slice = true; + $arg = new Twig_Node_Expression_Constant(0, $token->getLine()); + } else { + $arg = $this->parseExpression(); + } + + if ($stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) { + $slice = true; + } + + if ($slice) { + if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { + $length = new Twig_Node_Expression_Constant(null, $token->getLine()); + } else { + $length = $this->parseExpression(); + } + + $class = $this->getFilterNodeClass('slice', $token->getLine()); + $arguments = new Twig_Node(array($arg, $length)); + $filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine()); + + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']'); + + return $filter; + } + + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']'); + } + + return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno); + } + + public function parseFilterExpression($node) + { + $this->parser->getStream()->next(); + + return $this->parseFilterExpressionRaw($node); + } + + public function parseFilterExpressionRaw($node, $tag = null) + { + while (true) { + $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE); + + $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); + if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) { + $arguments = new Twig_Node(); + } else { + $arguments = $this->parseArguments(true); + } + + $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine()); + + $node = new $class($node, $name, $arguments, $token->getLine(), $tag); + + if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) { + break; + } + + $this->parser->getStream()->next(); + } + + return $node; + } + + /** + * Parses arguments. + * + * @param bool $namedArguments Whether to allow named arguments or not + * @param bool $definition Whether we are parsing arguments for a function definition + * + * @return Twig_Node + * + * @throws Twig_Error_Syntax + */ + public function parseArguments($namedArguments = false, $definition = false) + { + $args = array(); + $stream = $this->parser->getStream(); + + $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); + while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) { + if (!empty($args)) { + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); + } + + if ($definition) { + $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'An argument must be a name'); + $value = new Twig_Node_Expression_Name($token->getValue(), $this->parser->getCurrentToken()->getLine()); + } else { + $value = $this->parseExpression(); + } + + $name = null; + if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) { + if (!$value instanceof Twig_Node_Expression_Name) { + throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $stream->getSourceContext()); + } + $name = $value->getAttribute('name'); + + if ($definition) { + $value = $this->parsePrimaryExpression(); + + if (!$this->checkConstantExpression($value)) { + throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext()); + } + } else { + $value = $this->parseExpression(); + } + } + + if ($definition) { + if (null === $name) { + $name = $value->getAttribute('name'); + $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine()); + } + $args[$name] = $value; + } else { + if (null === $name) { + $args[] = $value; + } else { + $args[$name] = $value; + } + } + } + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); + + return new Twig_Node($args); + } + + public function parseAssignmentExpression() + { + $stream = $this->parser->getStream(); + $targets = array(); + while (true) { + $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to'); + $value = $token->getValue(); + if (in_array(strtolower($value), array('true', 'false', 'none', 'null'))) { + throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()); + } + $targets[] = new Twig_Node_Expression_AssignName($value, $token->getLine()); + + if (!$stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + + return new Twig_Node($targets); + } + + public function parseMultitargetExpression() + { + $targets = array(); + while (true) { + $targets[] = $this->parseExpression(); + if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + + return new Twig_Node($targets); + } + + private function parseNotTestExpression(Twig_NodeInterface $node) + { + return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine()); + } + + private function parseTestExpression(Twig_NodeInterface $node) + { + $stream = $this->parser->getStream(); + list($name, $test) = $this->getTest($node->getTemplateLine()); + + $class = $this->getTestNodeClass($test); + $arguments = null; + if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { + $arguments = $this->parser->getExpressionParser()->parseArguments(true); + } + + return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine()); + } + + private function getTest($line) + { + $stream = $this->parser->getStream(); + $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); + + if ($test = $this->env->getTest($name)) { + return array($name, $test); + } + + if ($stream->test(Twig_Token::NAME_TYPE)) { + // try 2-words tests + $name = $name.' '.$this->parser->getCurrentToken()->getValue(); + + if ($test = $this->env->getTest($name)) { + $stream->next(); + + return array($name, $test); + } + } + + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getTests())); + + throw $e; + } + + private function getTestNodeClass($test) + { + if ($test instanceof Twig_SimpleTest && $test->isDeprecated()) { + $stream = $this->parser->getStream(); + $message = sprintf('Twig Test "%s" is deprecated', $test->getName()); + if (!is_bool($test->getDeprecatedVersion())) { + $message .= sprintf(' since version %s', $test->getDeprecatedVersion()); + } + if ($test->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $test->getAlternative()); + } + $src = $stream->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $stream->getCurrent()->getLine()); + + @trigger_error($message, E_USER_DEPRECATED); + } + + if ($test instanceof Twig_SimpleTest) { + return $test->getNodeClass(); + } + + return $test instanceof Twig_Test_Node ? $test->getClass() : 'Twig_Node_Expression_Test'; + } + + protected function getFunctionNodeClass($name, $line) + { + if (false === $function = $this->env->getFunction($name)) { + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFunctions())); + + throw $e; + } + + if ($function instanceof Twig_SimpleFunction && $function->isDeprecated()) { + $message = sprintf('Twig Function "%s" is deprecated', $function->getName()); + if (!is_bool($function->getDeprecatedVersion())) { + $message .= sprintf(' since version %s', $function->getDeprecatedVersion()); + } + if ($function->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $function->getAlternative()); + } + $src = $this->parser->getStream()->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line); + + @trigger_error($message, E_USER_DEPRECATED); + } + + if ($function instanceof Twig_SimpleFunction) { + return $function->getNodeClass(); + } + + return $function instanceof Twig_Function_Node ? $function->getClass() : 'Twig_Node_Expression_Function'; + } + + protected function getFilterNodeClass($name, $line) + { + if (false === $filter = $this->env->getFilter($name)) { + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFilters())); + + throw $e; + } + + if ($filter instanceof Twig_SimpleFilter && $filter->isDeprecated()) { + $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName()); + if (!is_bool($filter->getDeprecatedVersion())) { + $message .= sprintf(' since version %s', $filter->getDeprecatedVersion()); + } + if ($filter->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $filter->getAlternative()); + } + $src = $this->parser->getStream()->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line); + + @trigger_error($message, E_USER_DEPRECATED); + } + + if ($filter instanceof Twig_SimpleFilter) { + return $filter->getNodeClass(); + } + + return $filter instanceof Twig_Filter_Node ? $filter->getClass() : 'Twig_Node_Expression_Filter'; + } + + // checks that the node only contains "constant" elements + protected function checkConstantExpression(Twig_NodeInterface $node) + { + if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array + || $node instanceof Twig_Node_Expression_Unary_Neg || $node instanceof Twig_Node_Expression_Unary_Pos + )) { + return false; + } + + foreach ($node as $n) { + if (!$this->checkConstantExpression($n)) { + return false; + } + } + + return true; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Extension.php b/src/vendor/twig/twig/lib/Twig/Extension.php new file mode 100644 index 0000000..fd674ee --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Extension.php @@ -0,0 +1,89 @@ +escapers[$strategy] = $callable; + } + + /** + * Gets all defined escapers. + * + * @return array An array of escapers + */ + public function getEscapers() + { + return $this->escapers; + } + + /** + * Sets the default format to be used by the date filter. + * + * @param string $format The default date format string + * @param string $dateIntervalFormat The default date interval format string + */ + public function setDateFormat($format = null, $dateIntervalFormat = null) + { + if (null !== $format) { + $this->dateFormats[0] = $format; + } + + if (null !== $dateIntervalFormat) { + $this->dateFormats[1] = $dateIntervalFormat; + } + } + + /** + * Gets the default format to be used by the date filter. + * + * @return array The default date format string and the default date interval format string + */ + public function getDateFormat() + { + return $this->dateFormats; + } + + /** + * Sets the default timezone to be used by the date filter. + * + * @param DateTimeZone|string $timezone The default timezone string or a DateTimeZone object + */ + public function setTimezone($timezone) + { + $this->timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone); + } + + /** + * Gets the default timezone to be used by the date filter. + * + * @return DateTimeZone The default timezone currently in use + */ + public function getTimezone() + { + if (null === $this->timezone) { + $this->timezone = new DateTimeZone(date_default_timezone_get()); + } + + return $this->timezone; + } + + /** + * Sets the default format to be used by the number_format filter. + * + * @param int $decimal the number of decimal places to use + * @param string $decimalPoint the character(s) to use for the decimal point + * @param string $thousandSep the character(s) to use for the thousands separator + */ + public function setNumberFormat($decimal, $decimalPoint, $thousandSep) + { + $this->numberFormat = array($decimal, $decimalPoint, $thousandSep); + } + + /** + * Get the default format used by the number_format filter. + * + * @return array The arguments for number_format() + */ + public function getNumberFormat() + { + return $this->numberFormat; + } + + public function getTokenParsers() + { + return array( + new Twig_TokenParser_For(), + new Twig_TokenParser_If(), + new Twig_TokenParser_Extends(), + new Twig_TokenParser_Include(), + new Twig_TokenParser_Block(), + new Twig_TokenParser_Use(), + new Twig_TokenParser_Filter(), + new Twig_TokenParser_Macro(), + new Twig_TokenParser_Import(), + new Twig_TokenParser_From(), + new Twig_TokenParser_Set(), + new Twig_TokenParser_Spaceless(), + new Twig_TokenParser_Flush(), + new Twig_TokenParser_Do(), + new Twig_TokenParser_Embed(), + new Twig_TokenParser_With(), + ); + } + + public function getFilters() + { + $filters = array( + // formatting filters + new Twig_SimpleFilter('date', 'twig_date_format_filter', array('needs_environment' => true)), + new Twig_SimpleFilter('date_modify', 'twig_date_modify_filter', array('needs_environment' => true)), + new Twig_SimpleFilter('format', 'sprintf'), + new Twig_SimpleFilter('replace', 'twig_replace_filter'), + new Twig_SimpleFilter('number_format', 'twig_number_format_filter', array('needs_environment' => true)), + new Twig_SimpleFilter('abs', 'abs'), + new Twig_SimpleFilter('round', 'twig_round'), + + // encoding + new Twig_SimpleFilter('url_encode', 'twig_urlencode_filter'), + new Twig_SimpleFilter('json_encode', 'twig_jsonencode_filter'), + new Twig_SimpleFilter('convert_encoding', 'twig_convert_encoding'), + + // string filters + new Twig_SimpleFilter('title', 'twig_title_string_filter', array('needs_environment' => true)), + new Twig_SimpleFilter('capitalize', 'twig_capitalize_string_filter', array('needs_environment' => true)), + new Twig_SimpleFilter('upper', 'strtoupper'), + new Twig_SimpleFilter('lower', 'strtolower'), + new Twig_SimpleFilter('striptags', 'strip_tags'), + new Twig_SimpleFilter('trim', 'trim'), + new Twig_SimpleFilter('nl2br', 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))), + + // array helpers + new Twig_SimpleFilter('join', 'twig_join_filter'), + new Twig_SimpleFilter('split', 'twig_split_filter', array('needs_environment' => true)), + new Twig_SimpleFilter('sort', 'twig_sort_filter'), + new Twig_SimpleFilter('merge', 'twig_array_merge'), + new Twig_SimpleFilter('batch', 'twig_array_batch'), + + // string/array filters + new Twig_SimpleFilter('reverse', 'twig_reverse_filter', array('needs_environment' => true)), + new Twig_SimpleFilter('length', 'twig_length_filter', array('needs_environment' => true)), + new Twig_SimpleFilter('slice', 'twig_slice', array('needs_environment' => true)), + new Twig_SimpleFilter('first', 'twig_first', array('needs_environment' => true)), + new Twig_SimpleFilter('last', 'twig_last', array('needs_environment' => true)), + + // iteration and runtime + new Twig_SimpleFilter('default', '_twig_default_filter', array('node_class' => 'Twig_Node_Expression_Filter_Default')), + new Twig_SimpleFilter('keys', 'twig_get_array_keys_filter'), + + // escaping + new Twig_SimpleFilter('escape', 'twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')), + new Twig_SimpleFilter('e', 'twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')), + ); + + if (function_exists('mb_get_info')) { + $filters[] = new Twig_SimpleFilter('upper', 'twig_upper_filter', array('needs_environment' => true)); + $filters[] = new Twig_SimpleFilter('lower', 'twig_lower_filter', array('needs_environment' => true)); + } + + return $filters; + } + + public function getFunctions() + { + return array( + new Twig_SimpleFunction('max', 'max'), + new Twig_SimpleFunction('min', 'min'), + new Twig_SimpleFunction('range', 'range'), + new Twig_SimpleFunction('constant', 'twig_constant'), + new Twig_SimpleFunction('cycle', 'twig_cycle'), + new Twig_SimpleFunction('random', 'twig_random', array('needs_environment' => true)), + new Twig_SimpleFunction('date', 'twig_date_converter', array('needs_environment' => true)), + new Twig_SimpleFunction('include', 'twig_include', array('needs_environment' => true, 'needs_context' => true, 'is_safe' => array('all'))), + new Twig_SimpleFunction('source', 'twig_source', array('needs_environment' => true, 'is_safe' => array('all'))), + ); + } + + public function getTests() + { + return array( + new Twig_SimpleTest('even', null, array('node_class' => 'Twig_Node_Expression_Test_Even')), + new Twig_SimpleTest('odd', null, array('node_class' => 'Twig_Node_Expression_Test_Odd')), + new Twig_SimpleTest('defined', null, array('node_class' => 'Twig_Node_Expression_Test_Defined')), + new Twig_SimpleTest('sameas', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas', 'deprecated' => '1.21', 'alternative' => 'same as')), + new Twig_SimpleTest('same as', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas')), + new Twig_SimpleTest('none', null, array('node_class' => 'Twig_Node_Expression_Test_Null')), + new Twig_SimpleTest('null', null, array('node_class' => 'Twig_Node_Expression_Test_Null')), + new Twig_SimpleTest('divisibleby', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby', 'deprecated' => '1.21', 'alternative' => 'divisible by')), + new Twig_SimpleTest('divisible by', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby')), + new Twig_SimpleTest('constant', null, array('node_class' => 'Twig_Node_Expression_Test_Constant')), + new Twig_SimpleTest('empty', 'twig_test_empty'), + new Twig_SimpleTest('iterable', 'twig_test_iterable'), + ); + } + + public function getOperators() + { + return array( + array( + 'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'), + '-' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'), + '+' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'), + ), + array( + 'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'b-or' => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'b-xor' => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'b-and' => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'matches' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Matches', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'starts with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_StartsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'ends with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_EndsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'is' => array('precedence' => 100, 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'is not' => array('precedence' => 100, 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT), + '??' => array('precedence' => 300, 'class' => 'Twig_Node_Expression_NullCoalesce', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT), + ), + ); + } + + public function getName() + { + return 'core'; + } +} + +/** + * Cycles over a value. + * + * @param ArrayAccess|array $values + * @param int $position The cycle position + * + * @return string The next value in the cycle + */ +function twig_cycle($values, $position) +{ + if (!is_array($values) && !$values instanceof ArrayAccess) { + return $values; + } + + return $values[$position % count($values)]; +} + +/** + * Returns a random value depending on the supplied parameter type: + * - a random item from a Traversable or array + * - a random character from a string + * - a random integer between 0 and the integer parameter. + * + * @param Twig_Environment $env + * @param Traversable|array|int|float|string $values The values to pick a random item from + * + * @throws Twig_Error_Runtime when $values is an empty array (does not apply to an empty string which is returned as is) + * + * @return mixed A random value from the given sequence + */ +function twig_random(Twig_Environment $env, $values = null) +{ + if (null === $values) { + return mt_rand(); + } + + if (is_int($values) || is_float($values)) { + return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values); + } + + if ($values instanceof Traversable) { + $values = iterator_to_array($values); + } elseif (is_string($values)) { + if ('' === $values) { + return ''; + } + if (null !== $charset = $env->getCharset()) { + if ('UTF-8' !== $charset) { + $values = twig_convert_encoding($values, 'UTF-8', $charset); + } + + // unicode version of str_split() + // split at all positions, but not after the start and not before the end + $values = preg_split('/(? $value) { + $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8'); + } + } + } else { + return $values[mt_rand(0, strlen($values) - 1)]; + } + } + + if (!is_array($values)) { + return $values; + } + + if (0 === count($values)) { + throw new Twig_Error_Runtime('The random function cannot pick from an empty array.'); + } + + return $values[array_rand($values, 1)]; +} + +/** + * Converts a date to the given format. + * + *
    + *   {{ post.published_at|date("m/d/Y") }}
    + * 
    + * + * @param Twig_Environment $env + * @param DateTime|DateTimeInterface|DateInterval|string $date A date + * @param string|null $format The target format, null to use the default + * @param DateTimeZone|string|null|false $timezone The target timezone, null to use the default, false to leave unchanged + * + * @return string The formatted date + */ +function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $timezone = null) +{ + if (null === $format) { + $formats = $env->getExtension('Twig_Extension_Core')->getDateFormat(); + $format = $date instanceof DateInterval ? $formats[1] : $formats[0]; + } + + if ($date instanceof DateInterval) { + return $date->format($format); + } + + return twig_date_converter($env, $date, $timezone)->format($format); +} + +/** + * Returns a new date object modified. + * + *
    + *   {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
    + * 
    + * + * @param Twig_Environment $env + * @param DateTime|string $date A date + * @param string $modifier A modifier string + * + * @return DateTime A new date object + */ +function twig_date_modify_filter(Twig_Environment $env, $date, $modifier) +{ + $date = twig_date_converter($env, $date, false); + $resultDate = $date->modify($modifier); + + // This is a hack to ensure PHP 5.2 support and support for DateTimeImmutable + // DateTime::modify does not return the modified DateTime object < 5.3.0 + // and DateTimeImmutable does not modify $date. + return null === $resultDate ? $date : $resultDate; +} + +/** + * Converts an input to a DateTime instance. + * + *
    + *    {% if date(user.created_at) < date('+2days') %}
    + *      {# do something #}
    + *    {% endif %}
    + * 
    + * + * @param Twig_Environment $env + * @param DateTime|DateTimeInterface|string|null $date A date + * @param DateTimeZone|string|null|false $timezone The target timezone, null to use the default, false to leave unchanged + * + * @return DateTime A DateTime instance + */ +function twig_date_converter(Twig_Environment $env, $date = null, $timezone = null) +{ + // determine the timezone + if (false !== $timezone) { + if (null === $timezone) { + $timezone = $env->getExtension('Twig_Extension_Core')->getTimezone(); + } elseif (!$timezone instanceof DateTimeZone) { + $timezone = new DateTimeZone($timezone); + } + } + + // immutable dates + if ($date instanceof DateTimeImmutable) { + return false !== $timezone ? $date->setTimezone($timezone) : $date; + } + + if ($date instanceof DateTime || $date instanceof DateTimeInterface) { + $date = clone $date; + if (false !== $timezone) { + $date->setTimezone($timezone); + } + + return $date; + } + + if (null === $date || 'now' === $date) { + return new DateTime($date, false !== $timezone ? $timezone : $env->getExtension('Twig_Extension_Core')->getTimezone()); + } + + $asString = (string) $date; + if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { + $date = new DateTime('@'.$date); + } else { + $date = new DateTime($date, $env->getExtension('Twig_Extension_Core')->getTimezone()); + } + + if (false !== $timezone) { + $date->setTimezone($timezone); + } + + return $date; +} + +/** + * Replaces strings within a string. + * + * @param string $str String to replace in + * @param array|Traversable $from Replace values + * @param string|null $to Replace to, deprecated (@see http://php.net/manual/en/function.strtr.php) + * + * @return string + */ +function twig_replace_filter($str, $from, $to = null) +{ + if ($from instanceof Traversable) { + $from = iterator_to_array($from); + } elseif (is_string($from) && is_string($to)) { + @trigger_error('Using "replace" with character by character replacement is deprecated since version 1.22 and will be removed in Twig 2.0', E_USER_DEPRECATED); + + return strtr($str, $from, $to); + } elseif (!is_array($from)) { + throw new Twig_Error_Runtime(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', is_object($from) ? get_class($from) : gettype($from))); + } + + return strtr($str, $from); +} + +/** + * Rounds a number. + * + * @param int|float $value The value to round + * @param int|float $precision The rounding precision + * @param string $method The method to use for rounding + * + * @return int|float The rounded number + */ +function twig_round($value, $precision = 0, $method = 'common') +{ + if ('common' == $method) { + return round($value, $precision); + } + + if ('ceil' != $method && 'floor' != $method) { + throw new Twig_Error_Runtime('The round filter only supports the "common", "ceil", and "floor" methods.'); + } + + return $method($value * pow(10, $precision)) / pow(10, $precision); +} + +/** + * Number format filter. + * + * All of the formatting options can be left null, in that case the defaults will + * be used. Supplying any of the parameters will override the defaults set in the + * environment object. + * + * @param Twig_Environment $env + * @param mixed $number A float/int/string of the number to format + * @param int $decimal the number of decimal points to display + * @param string $decimalPoint the character(s) to use for the decimal point + * @param string $thousandSep the character(s) to use for the thousands separator + * + * @return string The formatted number + */ +function twig_number_format_filter(Twig_Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null) +{ + $defaults = $env->getExtension('Twig_Extension_Core')->getNumberFormat(); + if (null === $decimal) { + $decimal = $defaults[0]; + } + + if (null === $decimalPoint) { + $decimalPoint = $defaults[1]; + } + + if (null === $thousandSep) { + $thousandSep = $defaults[2]; + } + + return number_format((float) $number, $decimal, $decimalPoint, $thousandSep); +} + +/** + * URL encodes (RFC 3986) a string as a path segment or an array as a query string. + * + * @param string|array $url A URL or an array of query parameters + * + * @return string The URL encoded value + */ +function twig_urlencode_filter($url) +{ + if (is_array($url)) { + if (defined('PHP_QUERY_RFC3986')) { + return http_build_query($url, '', '&', PHP_QUERY_RFC3986); + } + + return http_build_query($url, '', '&'); + } + + return rawurlencode($url); +} + +if (PHP_VERSION_ID < 50300) { + /** + * JSON encodes a variable. + * + * @param mixed $value the value to encode + * @param int $options Not used on PHP 5.2.x + * + * @return mixed The JSON encoded value + */ + function twig_jsonencode_filter($value, $options = 0) + { + if ($value instanceof Twig_Markup) { + $value = (string) $value; + } elseif (is_array($value)) { + array_walk_recursive($value, '_twig_markup2string'); + } + + return json_encode($value); + } +} else { + /** + * JSON encodes a variable. + * + * @param mixed $value the value to encode + * @param int $options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT + * + * @return mixed The JSON encoded value + */ + function twig_jsonencode_filter($value, $options = 0) + { + if ($value instanceof Twig_Markup) { + $value = (string) $value; + } elseif (is_array($value)) { + array_walk_recursive($value, '_twig_markup2string'); + } + + return json_encode($value, $options); + } +} + +function _twig_markup2string(&$value) +{ + if ($value instanceof Twig_Markup) { + $value = (string) $value; + } +} + +/** + * Merges an array with another one. + * + *
    + *  {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
    + *
    + *  {% set items = items|merge({ 'peugeot': 'car' }) %}
    + *
    + *  {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #}
    + * 
    + * + * @param array|Traversable $arr1 An array + * @param array|Traversable $arr2 An array + * + * @return array The merged array + */ +function twig_array_merge($arr1, $arr2) +{ + if ($arr1 instanceof Traversable) { + $arr1 = iterator_to_array($arr1); + } elseif (!is_array($arr1)) { + throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', gettype($arr1))); + } + + if ($arr2 instanceof Traversable) { + $arr2 = iterator_to_array($arr2); + } elseif (!is_array($arr2)) { + throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', gettype($arr2))); + } + + return array_merge($arr1, $arr2); +} + +/** + * Slices a variable. + * + * @param Twig_Environment $env + * @param mixed $item A variable + * @param int $start Start of the slice + * @param int $length Size of the slice + * @param bool $preserveKeys Whether to preserve key or not (when the input is an array) + * + * @return mixed The sliced variable + */ +function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false) +{ + if ($item instanceof Traversable) { + if ($item instanceof IteratorAggregate) { + $item = $item->getIterator(); + } + + if ($start >= 0 && $length >= 0 && $item instanceof Iterator) { + try { + return iterator_to_array(new LimitIterator($item, $start, $length === null ? -1 : $length), $preserveKeys); + } catch (OutOfBoundsException $exception) { + return array(); + } + } + + $item = iterator_to_array($item, $preserveKeys); + } + + if (is_array($item)) { + return array_slice($item, $start, $length, $preserveKeys); + } + + $item = (string) $item; + + if (function_exists('mb_get_info') && null !== $charset = $env->getCharset()) { + return (string) mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset); + } + + return (string) (null === $length ? substr($item, $start) : substr($item, $start, $length)); +} + +/** + * Returns the first element of the item. + * + * @param Twig_Environment $env + * @param mixed $item A variable + * + * @return mixed The first element of the item + */ +function twig_first(Twig_Environment $env, $item) +{ + $elements = twig_slice($env, $item, 0, 1, false); + + return is_string($elements) ? $elements : current($elements); +} + +/** + * Returns the last element of the item. + * + * @param Twig_Environment $env + * @param mixed $item A variable + * + * @return mixed The last element of the item + */ +function twig_last(Twig_Environment $env, $item) +{ + $elements = twig_slice($env, $item, -1, 1, false); + + return is_string($elements) ? $elements : current($elements); +} + +/** + * Joins the values to a string. + * + * The separator between elements is an empty string per default, you can define it with the optional parameter. + * + *
    + *  {{ [1, 2, 3]|join('|') }}
    + *  {# returns 1|2|3 #}
    + *
    + *  {{ [1, 2, 3]|join }}
    + *  {# returns 123 #}
    + * 
    + * + * @param array $value An array + * @param string $glue The separator + * + * @return string The concatenated string + */ +function twig_join_filter($value, $glue = '') +{ + if ($value instanceof Traversable) { + $value = iterator_to_array($value, false); + } + + return implode($glue, (array) $value); +} + +/** + * Splits the string into an array. + * + *
    + *  {{ "one,two,three"|split(',') }}
    + *  {# returns [one, two, three] #}
    + *
    + *  {{ "one,two,three,four,five"|split(',', 3) }}
    + *  {# returns [one, two, "three,four,five"] #}
    + *
    + *  {{ "123"|split('') }}
    + *  {# returns [1, 2, 3] #}
    + *
    + *  {{ "aabbcc"|split('', 2) }}
    + *  {# returns [aa, bb, cc] #}
    + * 
    + * + * @param Twig_Environment $env + * @param string $value A string + * @param string $delimiter The delimiter + * @param int $limit The limit + * + * @return array The split string as an array + */ +function twig_split_filter(Twig_Environment $env, $value, $delimiter, $limit = null) +{ + if (!empty($delimiter)) { + return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); + } + + if (!function_exists('mb_get_info') || null === $charset = $env->getCharset()) { + return str_split($value, null === $limit ? 1 : $limit); + } + + if ($limit <= 1) { + return preg_split('/(? + * {% for key in array|keys %} + * {# ... #} + * {% endfor %} + * + * + * @param array $array An array + * + * @return array The keys + */ +function twig_get_array_keys_filter($array) +{ + if ($array instanceof Traversable) { + return array_keys(iterator_to_array($array)); + } + + if (!is_array($array)) { + return array(); + } + + return array_keys($array); +} + +/** + * Reverses a variable. + * + * @param Twig_Environment $env + * @param array|Traversable|string $item An array, a Traversable instance, or a string + * @param bool $preserveKeys Whether to preserve key or not + * + * @return mixed The reversed input + */ +function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false) +{ + if ($item instanceof Traversable) { + return array_reverse(iterator_to_array($item), $preserveKeys); + } + + if (is_array($item)) { + return array_reverse($item, $preserveKeys); + } + + if (null !== $charset = $env->getCharset()) { + $string = (string) $item; + + if ('UTF-8' !== $charset) { + $item = twig_convert_encoding($string, 'UTF-8', $charset); + } + + preg_match_all('/./us', $item, $matches); + + $string = implode('', array_reverse($matches[0])); + + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, $charset, 'UTF-8'); + } + + return $string; + } + + return strrev((string) $item); +} + +/** + * Sorts an array. + * + * @param array|Traversable $array + * + * @return array + */ +function twig_sort_filter($array) +{ + if ($array instanceof Traversable) { + $array = iterator_to_array($array); + } elseif (!is_array($array)) { + throw new Twig_Error_Runtime(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', gettype($array))); + } + + asort($array); + + return $array; +} + +/** + * @internal + */ +function twig_in_filter($value, $compare) +{ + if (is_array($compare)) { + return in_array($value, $compare, is_object($value) || is_resource($value)); + } elseif (is_string($compare) && (is_string($value) || is_int($value) || is_float($value))) { + return '' === $value || false !== strpos($compare, (string) $value); + } elseif ($compare instanceof Traversable) { + return in_array($value, iterator_to_array($compare, false), is_object($value) || is_resource($value)); + } + + return false; +} + +/** + * Escapes a string. + * + * @param Twig_Environment $env + * @param mixed $string The value to be escaped + * @param string $strategy The escaping strategy + * @param string $charset The charset + * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) + * + * @return string + */ +function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) +{ + if ($autoescape && $string instanceof Twig_Markup) { + return $string; + } + + if (!is_string($string)) { + if (is_object($string) && method_exists($string, '__toString')) { + $string = (string) $string; + } elseif (in_array($strategy, array('html', 'js', 'css', 'html_attr', 'url'))) { + return $string; + } + } + + if (null === $charset) { + $charset = $env->getCharset(); + } + + switch ($strategy) { + case 'html': + // see http://php.net/htmlspecialchars + + // Using a static variable to avoid initializing the array + // each time the function is called. Moving the declaration on the + // top of the function slow downs other escaping strategies. + static $htmlspecialcharsCharsets; + + if (null === $htmlspecialcharsCharsets) { + if (defined('HHVM_VERSION')) { + $htmlspecialcharsCharsets = array('utf-8' => true, 'UTF-8' => true); + } else { + $htmlspecialcharsCharsets = array( + 'ISO-8859-1' => true, 'ISO8859-1' => true, + 'ISO-8859-15' => true, 'ISO8859-15' => true, + 'utf-8' => true, 'UTF-8' => true, + 'CP866' => true, 'IBM866' => true, '866' => true, + 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, + '1251' => true, + 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, + 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, + 'BIG5' => true, '950' => true, + 'GB2312' => true, '936' => true, + 'BIG5-HKSCS' => true, + 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, + 'EUC-JP' => true, 'EUCJP' => true, + 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, + ); + } + } + + if (isset($htmlspecialcharsCharsets[$charset])) { + return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); + } + + if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { + // cache the lowercase variant for future iterations + $htmlspecialcharsCharsets[$charset] = true; + + return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); + } + + $string = twig_convert_encoding($string, 'UTF-8', $charset); + $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); + + return twig_convert_encoding($string, $charset, 'UTF-8'); + + case 'js': + // escape all non-alphanumeric characters + // into their \xHH or \uHHHH representations + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (0 == strlen($string) ? false : 1 !== preg_match('/^./su', $string)) { + throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', '_twig_escape_js_callback', $string); + + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, $charset, 'UTF-8'); + } + + return $string; + + case 'css': + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (0 == strlen($string) ? false : 1 !== preg_match('/^./su', $string)) { + throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', '_twig_escape_css_callback', $string); + + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, $charset, 'UTF-8'); + } + + return $string; + + case 'html_attr': + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (0 == strlen($string) ? false : 1 !== preg_match('/^./su', $string)) { + throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', '_twig_escape_html_attr_callback', $string); + + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, $charset, 'UTF-8'); + } + + return $string; + + case 'url': + if (PHP_VERSION_ID < 50300) { + return str_replace('%7E', '~', rawurlencode($string)); + } + + return rawurlencode($string); + + default: + static $escapers; + + if (null === $escapers) { + $escapers = $env->getExtension('Twig_Extension_Core')->getEscapers(); + } + + if (isset($escapers[$strategy])) { + return call_user_func($escapers[$strategy], $env, $string, $charset); + } + + $validStrategies = implode(', ', array_merge(array('html', 'js', 'url', 'css', 'html_attr'), array_keys($escapers))); + + throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies)); + } +} + +/** + * @internal + */ +function twig_escape_filter_is_safe(Twig_Node $filterArgs) +{ + foreach ($filterArgs as $arg) { + if ($arg instanceof Twig_Node_Expression_Constant) { + return array($arg->getAttribute('value')); + } + + return array(); + } + + return array('html'); +} + +if (function_exists('mb_convert_encoding')) { + function twig_convert_encoding($string, $to, $from) + { + return mb_convert_encoding($string, $to, $from); + } +} elseif (function_exists('iconv')) { + function twig_convert_encoding($string, $to, $from) + { + return iconv($from, $to, $string); + } +} else { + function twig_convert_encoding($string, $to, $from) + { + throw new Twig_Error_Runtime('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).'); + } +} + +function _twig_escape_js_callback($matches) +{ + $char = $matches[0]; + + // \xHH + if (!isset($char[1])) { + return '\\x'.strtoupper(substr('00'.bin2hex($char), -2)); + } + + // \uHHHH + $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); + + return '\\u'.strtoupper(substr('0000'.bin2hex($char), -4)); +} + +function _twig_escape_css_callback($matches) +{ + $char = $matches[0]; + + // \xHH + if (!isset($char[1])) { + $hex = ltrim(strtoupper(bin2hex($char)), '0'); + if (0 === strlen($hex)) { + $hex = '0'; + } + + return '\\'.$hex.' '; + } + + // \uHHHH + $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); + + return '\\'.ltrim(strtoupper(bin2hex($char)), '0').' '; +} + +/** + * This function is adapted from code coming from Zend Framework. + * + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +function _twig_escape_html_attr_callback($matches) +{ + /* + * While HTML supports far more named entities, the lowest common denominator + * has become HTML5's XML Serialisation which is restricted to the those named + * entities that XML supports. Using HTML entities would result in this error: + * XML Parsing Error: undefined entity + */ + static $entityMap = array( + 34 => 'quot', /* quotation mark */ + 38 => 'amp', /* ampersand */ + 60 => 'lt', /* less-than sign */ + 62 => 'gt', /* greater-than sign */ + ); + + $chr = $matches[0]; + $ord = ord($chr); + + /* + * The following replaces characters undefined in HTML with the + * hex entity for the Unicode replacement character. + */ + if (($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r") || ($ord >= 0x7f && $ord <= 0x9f)) { + return '�'; + } + + /* + * Check if the current character to escape has a name entity we should + * replace it with while grabbing the hex value of the character. + */ + if (strlen($chr) == 1) { + $hex = strtoupper(substr('00'.bin2hex($chr), -2)); + } else { + $chr = twig_convert_encoding($chr, 'UTF-16BE', 'UTF-8'); + $hex = strtoupper(substr('0000'.bin2hex($chr), -4)); + } + + $int = hexdec($hex); + if (array_key_exists($int, $entityMap)) { + return sprintf('&%s;', $entityMap[$int]); + } + + /* + * Per OWASP recommendations, we'll use hex entities for any other + * characters where a named entity does not exist. + */ + return sprintf('&#x%s;', $hex); +} + +// add multibyte extensions if possible +if (function_exists('mb_get_info')) { + /** + * Returns the length of a variable. + * + * @param Twig_Environment $env + * @param mixed $thing A variable + * + * @return int The length of the value + */ + function twig_length_filter(Twig_Environment $env, $thing) + { + return is_scalar($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing); + } + + /** + * Converts a string to uppercase. + * + * @param Twig_Environment $env + * @param string $string A string + * + * @return string The uppercased string + */ + function twig_upper_filter(Twig_Environment $env, $string) + { + if (null !== $charset = $env->getCharset()) { + return mb_strtoupper($string, $charset); + } + + return strtoupper($string); + } + + /** + * Converts a string to lowercase. + * + * @param Twig_Environment $env + * @param string $string A string + * + * @return string The lowercased string + */ + function twig_lower_filter(Twig_Environment $env, $string) + { + if (null !== $charset = $env->getCharset()) { + return mb_strtolower($string, $charset); + } + + return strtolower($string); + } + + /** + * Returns a titlecased string. + * + * @param Twig_Environment $env + * @param string $string A string + * + * @return string The titlecased string + */ + function twig_title_string_filter(Twig_Environment $env, $string) + { + if (null !== $charset = $env->getCharset()) { + return mb_convert_case($string, MB_CASE_TITLE, $charset); + } + + return ucwords(strtolower($string)); + } + + /** + * Returns a capitalized string. + * + * @param Twig_Environment $env + * @param string $string A string + * + * @return string The capitalized string + */ + function twig_capitalize_string_filter(Twig_Environment $env, $string) + { + if (null !== $charset = $env->getCharset()) { + return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset); + } + + return ucfirst(strtolower($string)); + } +} +// and byte fallback +else { + /** + * Returns the length of a variable. + * + * @param Twig_Environment $env + * @param mixed $thing A variable + * + * @return int The length of the value + */ + function twig_length_filter(Twig_Environment $env, $thing) + { + return is_scalar($thing) ? strlen($thing) : count($thing); + } + + /** + * Returns a titlecased string. + * + * @param Twig_Environment $env + * @param string $string A string + * + * @return string The titlecased string + */ + function twig_title_string_filter(Twig_Environment $env, $string) + { + return ucwords(strtolower($string)); + } + + /** + * Returns a capitalized string. + * + * @param Twig_Environment $env + * @param string $string A string + * + * @return string The capitalized string + */ + function twig_capitalize_string_filter(Twig_Environment $env, $string) + { + return ucfirst(strtolower($string)); + } +} + +/** + * @internal + */ +function twig_ensure_traversable($seq) +{ + if ($seq instanceof Traversable || is_array($seq)) { + return $seq; + } + + return array(); +} + +/** + * Checks if a variable is empty. + * + *
    + * {# evaluates to true if the foo variable is null, false, or the empty string #}
    + * {% if foo is empty %}
    + *     {# ... #}
    + * {% endif %}
    + * 
    + * + * @param mixed $value A variable + * + * @return bool true if the value is empty, false otherwise + */ +function twig_test_empty($value) +{ + if ($value instanceof Countable) { + return 0 == count($value); + } + + return '' === $value || false === $value || null === $value || array() === $value; +} + +/** + * Checks if a variable is traversable. + * + *
    + * {# evaluates to true if the foo variable is an array or a traversable object #}
    + * {% if foo is traversable %}
    + *     {# ... #}
    + * {% endif %}
    + * 
    + * + * @param mixed $value A variable + * + * @return bool true if the value is traversable + */ +function twig_test_iterable($value) +{ + return $value instanceof Traversable || is_array($value); +} + +/** + * Renders a template. + * + * @param Twig_Environment $env + * @param array $context + * @param string|array $template The template to render or an array of templates to try consecutively + * @param array $variables The variables to pass to the template + * @param bool $withContext + * @param bool $ignoreMissing Whether to ignore missing templates or not + * @param bool $sandboxed Whether to sandbox the template or not + * + * @return string The rendered template + */ +function twig_include(Twig_Environment $env, $context, $template, $variables = array(), $withContext = true, $ignoreMissing = false, $sandboxed = false) +{ + $alreadySandboxed = false; + $sandbox = null; + if ($withContext) { + $variables = array_merge($context, $variables); + } + + if ($isSandboxed = $sandboxed && $env->hasExtension('Twig_Extension_Sandbox')) { + $sandbox = $env->getExtension('Twig_Extension_Sandbox'); + if (!$alreadySandboxed = $sandbox->isSandboxed()) { + $sandbox->enableSandbox(); + } + } + + $result = null; + try { + $result = $env->resolveTemplate($template)->render($variables); + } catch (Twig_Error_Loader $e) { + if (!$ignoreMissing) { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + + throw $e; + } + } catch (Throwable $e) { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + + throw $e; + } catch (Exception $e) { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + + throw $e; + } + + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + + return $result; +} + +/** + * Returns a template content without rendering it. + * + * @param Twig_Environment $env + * @param string $name The template name + * @param bool $ignoreMissing Whether to ignore missing templates or not + * + * @return string The template source + */ +function twig_source(Twig_Environment $env, $name, $ignoreMissing = false) +{ + $loader = $env->getLoader(); + try { + if (!$loader instanceof Twig_SourceContextLoaderInterface) { + return $loader->getSource($name); + } else { + return $loader->getSourceContext($name)->getCode(); + } + } catch (Twig_Error_Loader $e) { + if (!$ignoreMissing) { + throw $e; + } + } +} + +/** + * Provides the ability to get constants from instances as well as class/global constants. + * + * @param string $constant The name of the constant + * @param null|object $object The object to get the constant from + * + * @return string + */ +function twig_constant($constant, $object = null) +{ + if (null !== $object) { + $constant = get_class($object).'::'.$constant; + } + + return constant($constant); +} + +/** + * Checks if a constant exists. + * + * @param string $constant The name of the constant + * @param null|object $object The object to get the constant from + * + * @return bool + */ +function twig_constant_is_defined($constant, $object = null) +{ + if (null !== $object) { + $constant = get_class($object).'::'.$constant; + } + + return defined($constant); +} + +/** + * Batches item. + * + * @param array $items An array of items + * @param int $size The size of the batch + * @param mixed $fill A value used to fill missing items + * + * @return array + */ +function twig_array_batch($items, $size, $fill = null) +{ + if ($items instanceof Traversable) { + $items = iterator_to_array($items, false); + } + + $size = ceil($size); + + $result = array_chunk($items, $size, true); + + if (null !== $fill && !empty($result)) { + $last = count($result) - 1; + if ($fillCount = $size - count($result[$last])) { + $result[$last] = array_merge( + $result[$last], + array_fill(0, $fillCount, $fill) + ); + } + } + + return $result; +} diff --git a/src/vendor/twig/twig/lib/Twig/Extension/Debug.php b/src/vendor/twig/twig/lib/Twig/Extension/Debug.php new file mode 100644 index 0000000..3642049 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Extension/Debug.php @@ -0,0 +1,65 @@ + $isDumpOutputHtmlSafe ? array('html') : array(), 'needs_context' => true, 'needs_environment' => true)), + ); + } + + public function getName() + { + return 'debug'; + } +} + +function twig_var_dump(Twig_Environment $env, $context) +{ + if (!$env->isDebug()) { + return; + } + + ob_start(); + + $count = func_num_args(); + if (2 === $count) { + $vars = array(); + foreach ($context as $key => $value) { + if (!$value instanceof Twig_Template) { + $vars[$key] = $value; + } + } + + var_dump($vars); + } else { + for ($i = 2; $i < $count; ++$i) { + var_dump(func_get_arg($i)); + } + } + + return ob_get_clean(); +} diff --git a/src/vendor/twig/twig/lib/Twig/Extension/Escaper.php b/src/vendor/twig/twig/lib/Twig/Extension/Escaper.php new file mode 100644 index 0000000..c95f87a --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Extension/Escaper.php @@ -0,0 +1,110 @@ +setDefaultStrategy($defaultStrategy); + } + + public function getTokenParsers() + { + return array(new Twig_TokenParser_AutoEscape()); + } + + public function getNodeVisitors() + { + return array(new Twig_NodeVisitor_Escaper()); + } + + public function getFilters() + { + return array( + new Twig_SimpleFilter('raw', 'twig_raw_filter', array('is_safe' => array('all'))), + ); + } + + /** + * Sets the default strategy to use when not defined by the user. + * + * The strategy can be a valid PHP callback that takes the template + * name as an argument and returns the strategy to use. + * + * @param string|false|callable $defaultStrategy An escaping strategy + */ + public function setDefaultStrategy($defaultStrategy) + { + // for BC + if (true === $defaultStrategy) { + @trigger_error('Using "true" as the default strategy is deprecated since version 1.21. Use "html" instead.', E_USER_DEPRECATED); + + $defaultStrategy = 'html'; + } + + if ('filename' === $defaultStrategy) { + @trigger_error('Using "filename" as the default strategy is deprecated since version 1.27. Use "name" instead.', E_USER_DEPRECATED); + + $defaultStrategy = 'name'; + } + + if ('name' === $defaultStrategy) { + $defaultStrategy = array('Twig_FileExtensionEscapingStrategy', 'guess'); + } + + $this->defaultStrategy = $defaultStrategy; + } + + /** + * Gets the default strategy to use when not defined by the user. + * + * @param string $name The template name + * + * @return string|false The default strategy to use for the template + */ + public function getDefaultStrategy($name) + { + // disable string callables to avoid calling a function named html or js, + // or any other upcoming escaping strategy + if (!is_string($this->defaultStrategy) && false !== $this->defaultStrategy) { + return call_user_func($this->defaultStrategy, $name); + } + + return $this->defaultStrategy; + } + + public function getName() + { + return 'escaper'; + } +} + +/** + * Marks a variable as being safe. + * + * @param string $string A PHP variable + * + * @return string + */ +function twig_raw_filter($string) +{ + return $string; +} diff --git a/src/vendor/twig/twig/lib/Twig/Extension/GlobalsInterface.php b/src/vendor/twig/twig/lib/Twig/Extension/GlobalsInterface.php new file mode 100644 index 0000000..5370b8e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Extension/GlobalsInterface.php @@ -0,0 +1,22 @@ + + */ +interface Twig_Extension_GlobalsInterface +{ +} diff --git a/src/vendor/twig/twig/lib/Twig/Extension/InitRuntimeInterface.php b/src/vendor/twig/twig/lib/Twig/Extension/InitRuntimeInterface.php new file mode 100644 index 0000000..7a07582 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Extension/InitRuntimeInterface.php @@ -0,0 +1,22 @@ + + */ +interface Twig_Extension_InitRuntimeInterface +{ +} diff --git a/src/vendor/twig/twig/lib/Twig/Extension/Optimizer.php b/src/vendor/twig/twig/lib/Twig/Extension/Optimizer.php new file mode 100644 index 0000000..e468fce --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Extension/Optimizer.php @@ -0,0 +1,33 @@ +optimizers = $optimizers; + } + + public function getNodeVisitors() + { + return array(new Twig_NodeVisitor_Optimizer($this->optimizers)); + } + + public function getName() + { + return 'optimizer'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Extension/Profiler.php b/src/vendor/twig/twig/lib/Twig/Extension/Profiler.php new file mode 100644 index 0000000..643632b --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Extension/Profiler.php @@ -0,0 +1,46 @@ +actives[] = $profile; + } + + public function enter(Twig_Profiler_Profile $profile) + { + $this->actives[0]->addProfile($profile); + array_unshift($this->actives, $profile); + } + + public function leave(Twig_Profiler_Profile $profile) + { + $profile->leave(); + array_shift($this->actives); + + if (1 === count($this->actives)) { + $this->actives[0]->leave(); + } + } + + public function getNodeVisitors() + { + return array(new Twig_Profiler_NodeVisitor_Profiler(get_class($this))); + } + + public function getName() + { + return 'profiler'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Extension/Sandbox.php b/src/vendor/twig/twig/lib/Twig/Extension/Sandbox.php new file mode 100644 index 0000000..f290415 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Extension/Sandbox.php @@ -0,0 +1,101 @@ +policy = $policy; + $this->sandboxedGlobally = $sandboxed; + } + + public function getTokenParsers() + { + return array(new Twig_TokenParser_Sandbox()); + } + + public function getNodeVisitors() + { + return array(new Twig_NodeVisitor_Sandbox()); + } + + public function enableSandbox() + { + $this->sandboxed = true; + } + + public function disableSandbox() + { + $this->sandboxed = false; + } + + public function isSandboxed() + { + return $this->sandboxedGlobally || $this->sandboxed; + } + + public function isSandboxedGlobally() + { + return $this->sandboxedGlobally; + } + + public function setSecurityPolicy(Twig_Sandbox_SecurityPolicyInterface $policy) + { + $this->policy = $policy; + } + + public function getSecurityPolicy() + { + return $this->policy; + } + + public function checkSecurity($tags, $filters, $functions) + { + if ($this->isSandboxed()) { + $this->policy->checkSecurity($tags, $filters, $functions); + } + } + + public function checkMethodAllowed($obj, $method) + { + if ($this->isSandboxed()) { + $this->policy->checkMethodAllowed($obj, $method); + } + } + + public function checkPropertyAllowed($obj, $method) + { + if ($this->isSandboxed()) { + $this->policy->checkPropertyAllowed($obj, $method); + } + } + + public function ensureToStringAllowed($obj) + { + if ($this->isSandboxed() && is_object($obj)) { + $this->policy->checkMethodAllowed($obj, '__toString'); + } + + return $obj; + } + + public function getName() + { + return 'sandbox'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Extension/Staging.php b/src/vendor/twig/twig/lib/Twig/Extension/Staging.php new file mode 100644 index 0000000..d21004d --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Extension/Staging.php @@ -0,0 +1,94 @@ + + * + * @internal + */ +class Twig_Extension_Staging extends Twig_Extension +{ + protected $functions = array(); + protected $filters = array(); + protected $visitors = array(); + protected $tokenParsers = array(); + protected $globals = array(); + protected $tests = array(); + + public function addFunction($name, $function) + { + $this->functions[$name] = $function; + } + + public function getFunctions() + { + return $this->functions; + } + + public function addFilter($name, $filter) + { + $this->filters[$name] = $filter; + } + + public function getFilters() + { + return $this->filters; + } + + public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) + { + $this->visitors[] = $visitor; + } + + public function getNodeVisitors() + { + return $this->visitors; + } + + public function addTokenParser(Twig_TokenParserInterface $parser) + { + $this->tokenParsers[] = $parser; + } + + public function getTokenParsers() + { + return $this->tokenParsers; + } + + public function addGlobal($name, $value) + { + $this->globals[$name] = $value; + } + + public function getGlobals() + { + return $this->globals; + } + + public function addTest($name, $test) + { + $this->tests[$name] = $test; + } + + public function getTests() + { + return $this->tests; + } + + public function getName() + { + return 'staging'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Extension/StringLoader.php b/src/vendor/twig/twig/lib/Twig/Extension/StringLoader.php new file mode 100644 index 0000000..26b49da --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Extension/StringLoader.php @@ -0,0 +1,45 @@ + true)), + ); + } + + public function getName() + { + return 'string_loader'; + } +} + +/** + * Loads a template from a string. + * + *
    + * {{ include(template_from_string("Hello {{ name }}")) }}
    + * 
    + * + * @param Twig_Environment $env A Twig_Environment instance + * @param string $template A template as a string or object implementing __toString() + * + * @return Twig_Template + */ +function twig_template_from_string(Twig_Environment $env, $template) +{ + return $env->createTemplate((string) $template); +} diff --git a/src/vendor/twig/twig/lib/Twig/ExtensionInterface.php b/src/vendor/twig/twig/lib/Twig/ExtensionInterface.php new file mode 100644 index 0000000..ebc6806 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/ExtensionInterface.php @@ -0,0 +1,87 @@ + + */ +interface Twig_ExtensionInterface +{ + /** + * Initializes the runtime environment. + * + * This is where you can load some file that contains filter functions for instance. + * + * @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_InitRuntimeInterface instead + */ + public function initRuntime(Twig_Environment $environment); + + /** + * Returns the token parser instances to add to the existing list. + * + * @return Twig_TokenParserInterface[] + */ + public function getTokenParsers(); + + /** + * Returns the node visitor instances to add to the existing list. + * + * @return Twig_NodeVisitorInterface[] + */ + public function getNodeVisitors(); + + /** + * Returns a list of filters to add to the existing list. + * + * @return Twig_SimpleFilter[] + */ + public function getFilters(); + + /** + * Returns a list of tests to add to the existing list. + * + * @return Twig_SimpleTest[] + */ + public function getTests(); + + /** + * Returns a list of functions to add to the existing list. + * + * @return Twig_SimpleFunction[] + */ + public function getFunctions(); + + /** + * Returns a list of operators to add to the existing list. + * + * @return array First array of unary operators, second array of binary operators + */ + public function getOperators(); + + /** + * Returns a list of global variables to add to the existing list. + * + * @return array An array of global variables + * + * @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_GlobalsInterface instead + */ + public function getGlobals(); + + /** + * Returns the name of the extension. + * + * @return string The extension name + * + * @deprecated since 1.26 (to be removed in 2.0), not used anymore internally + */ + public function getName(); +} diff --git a/src/vendor/twig/twig/lib/Twig/FileExtensionEscapingStrategy.php b/src/vendor/twig/twig/lib/Twig/FileExtensionEscapingStrategy.php new file mode 100644 index 0000000..772139e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/FileExtensionEscapingStrategy.php @@ -0,0 +1,58 @@ + + */ +class Twig_FileExtensionEscapingStrategy +{ + /** + * Guesses the best autoescaping strategy based on the file name. + * + * @param string $name The template name + * + * @return string|false The escaping strategy name to use or false to disable + */ + public static function guess($name) + { + if (in_array(substr($name, -1), array('/', '\\'))) { + return 'html'; // return html for directories + } + + if ('.twig' === substr($name, -5)) { + $name = substr($name, 0, -5); + } + + $extension = pathinfo($name, PATHINFO_EXTENSION); + + switch ($extension) { + case 'js': + return 'js'; + + case 'css': + return 'css'; + + case 'txt': + return false; + + default: + return 'html'; + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Filter.php b/src/vendor/twig/twig/lib/Twig/Filter.php new file mode 100644 index 0000000..101d2e7 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Filter.php @@ -0,0 +1,84 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableInterface +{ + protected $options; + protected $arguments = array(); + + public function __construct(array $options = array()) + { + $this->options = array_merge(array( + 'needs_environment' => false, + 'needs_context' => false, + 'pre_escape' => null, + 'preserves_safety' => null, + 'callable' => null, + ), $options); + } + + public function setArguments($arguments) + { + $this->arguments = $arguments; + } + + public function getArguments() + { + return $this->arguments; + } + + public function needsEnvironment() + { + return $this->options['needs_environment']; + } + + public function needsContext() + { + return $this->options['needs_context']; + } + + public function getSafe(Twig_Node $filterArgs) + { + if (isset($this->options['is_safe'])) { + return $this->options['is_safe']; + } + + if (isset($this->options['is_safe_callback'])) { + return call_user_func($this->options['is_safe_callback'], $filterArgs); + } + } + + public function getPreservesSafety() + { + return $this->options['preserves_safety']; + } + + public function getPreEscape() + { + return $this->options['pre_escape']; + } + + public function getCallable() + { + return $this->options['callable']; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Filter/Function.php b/src/vendor/twig/twig/lib/Twig/Filter/Function.php new file mode 100644 index 0000000..d679cab --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Filter/Function.php @@ -0,0 +1,40 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Filter_Function extends Twig_Filter +{ + protected $function; + + public function __construct($function, array $options = array()) + { + $options['callable'] = $function; + + parent::__construct($options); + + $this->function = $function; + } + + public function compile() + { + return $this->function; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Filter/Method.php b/src/vendor/twig/twig/lib/Twig/Filter/Method.php new file mode 100644 index 0000000..5ca0511 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Filter/Method.php @@ -0,0 +1,42 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Filter_Method extends Twig_Filter +{ + protected $extension; + protected $method; + + public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array()) + { + $options['callable'] = array($extension, $method); + + parent::__construct($options); + + $this->extension = $extension; + $this->method = $method; + } + + public function compile() + { + return sprintf('$this->env->getExtension(\'%s\')->%s', get_class($this->extension), $this->method); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Filter/Node.php b/src/vendor/twig/twig/lib/Twig/Filter/Node.php new file mode 100644 index 0000000..a922f50 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Filter/Node.php @@ -0,0 +1,42 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Filter_Node extends Twig_Filter +{ + protected $class; + + public function __construct($class, array $options = array()) + { + parent::__construct($options); + + $this->class = $class; + } + + public function getClass() + { + return $this->class; + } + + public function compile() + { + } +} diff --git a/src/vendor/twig/twig/lib/Twig/FilterCallableInterface.php b/src/vendor/twig/twig/lib/Twig/FilterCallableInterface.php new file mode 100644 index 0000000..5679861 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/FilterCallableInterface.php @@ -0,0 +1,24 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_FilterCallableInterface +{ + public function getCallable(); +} diff --git a/src/vendor/twig/twig/lib/Twig/FilterInterface.php b/src/vendor/twig/twig/lib/Twig/FilterInterface.php new file mode 100644 index 0000000..6b0be0e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/FilterInterface.php @@ -0,0 +1,43 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_FilterInterface +{ + /** + * Compiles a filter. + * + * @return string The PHP code for the filter + */ + public function compile(); + + public function needsEnvironment(); + + public function needsContext(); + + public function getSafe(Twig_Node $filterArgs); + + public function getPreservesSafety(); + + public function getPreEscape(); + + public function setArguments($arguments); + + public function getArguments(); +} diff --git a/src/vendor/twig/twig/lib/Twig/Function.php b/src/vendor/twig/twig/lib/Twig/Function.php new file mode 100644 index 0000000..9fc76a8 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Function.php @@ -0,0 +1,74 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCallableInterface +{ + protected $options; + protected $arguments = array(); + + public function __construct(array $options = array()) + { + $this->options = array_merge(array( + 'needs_environment' => false, + 'needs_context' => false, + 'callable' => null, + ), $options); + } + + public function setArguments($arguments) + { + $this->arguments = $arguments; + } + + public function getArguments() + { + return $this->arguments; + } + + public function needsEnvironment() + { + return $this->options['needs_environment']; + } + + public function needsContext() + { + return $this->options['needs_context']; + } + + public function getSafe(Twig_Node $functionArgs) + { + if (isset($this->options['is_safe'])) { + return $this->options['is_safe']; + } + + if (isset($this->options['is_safe_callback'])) { + return call_user_func($this->options['is_safe_callback'], $functionArgs); + } + + return array(); + } + + public function getCallable() + { + return $this->options['callable']; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Function/Function.php b/src/vendor/twig/twig/lib/Twig/Function/Function.php new file mode 100644 index 0000000..ae83e15 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Function/Function.php @@ -0,0 +1,41 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Function_Function extends Twig_Function +{ + protected $function; + + public function __construct($function, array $options = array()) + { + $options['callable'] = $function; + + parent::__construct($options); + + $this->function = $function; + } + + public function compile() + { + return $this->function; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Function/Method.php b/src/vendor/twig/twig/lib/Twig/Function/Method.php new file mode 100644 index 0000000..d014be4 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Function/Method.php @@ -0,0 +1,43 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Function_Method extends Twig_Function +{ + protected $extension; + protected $method; + + public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array()) + { + $options['callable'] = array($extension, $method); + + parent::__construct($options); + + $this->extension = $extension; + $this->method = $method; + } + + public function compile() + { + return sprintf('$this->env->getExtension(\'%s\')->%s', get_class($this->extension), $this->method); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Function/Node.php b/src/vendor/twig/twig/lib/Twig/Function/Node.php new file mode 100644 index 0000000..118b0ba --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Function/Node.php @@ -0,0 +1,42 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Function_Node extends Twig_Function +{ + protected $class; + + public function __construct($class, array $options = array()) + { + parent::__construct($options); + + $this->class = $class; + } + + public function getClass() + { + return $this->class; + } + + public function compile() + { + } +} diff --git a/src/vendor/twig/twig/lib/Twig/FunctionCallableInterface.php b/src/vendor/twig/twig/lib/Twig/FunctionCallableInterface.php new file mode 100644 index 0000000..87d795e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/FunctionCallableInterface.php @@ -0,0 +1,24 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_FunctionCallableInterface +{ + public function getCallable(); +} diff --git a/src/vendor/twig/twig/lib/Twig/FunctionInterface.php b/src/vendor/twig/twig/lib/Twig/FunctionInterface.php new file mode 100644 index 0000000..f449234 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/FunctionInterface.php @@ -0,0 +1,40 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_FunctionInterface +{ + /** + * Compiles a function. + * + * @return string The PHP code for the function + */ + public function compile(); + + public function needsEnvironment(); + + public function needsContext(); + + public function getSafe(Twig_Node $filterArgs); + + public function setArguments($arguments); + + public function getArguments(); +} diff --git a/src/vendor/twig/twig/lib/Twig/Lexer.php b/src/vendor/twig/twig/lib/Twig/Lexer.php new file mode 100644 index 0000000..295379a --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Lexer.php @@ -0,0 +1,425 @@ + + */ +class Twig_Lexer implements Twig_LexerInterface +{ + protected $tokens; + protected $code; + protected $cursor; + protected $lineno; + protected $end; + protected $state; + protected $states; + protected $brackets; + protected $env; + // to be renamed to $name in 2.0 (where it is private) + protected $filename; + protected $options; + protected $regexes; + protected $position; + protected $positions; + protected $currentVarBlockLine; + + private $source; + + const STATE_DATA = 0; + const STATE_BLOCK = 1; + const STATE_VAR = 2; + const STATE_STRING = 3; + const STATE_INTERPOLATION = 4; + + const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; + const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A'; + const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; + const REGEX_DQ_STRING_DELIM = '/"/A'; + const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; + const PUNCTUATION = '()[]{}?:.,|'; + + public function __construct(Twig_Environment $env, array $options = array()) + { + $this->env = $env; + + $this->options = array_merge(array( + 'tag_comment' => array('{#', '#}'), + 'tag_block' => array('{%', '%}'), + 'tag_variable' => array('{{', '}}'), + 'whitespace_trim' => '-', + 'interpolation' => array('#{', '}'), + ), $options); + + $this->regexes = array( + 'lex_var' => '/\s*'.preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A', + 'lex_block' => '/\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')\n?/A', + 'lex_raw_data' => '/('.preg_quote($this->options['tag_block'][0].$this->options['whitespace_trim'], '/').'|'.preg_quote($this->options['tag_block'][0], '/').')\s*(?:end%s)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/s', + 'operator' => $this->getOperatorRegex(), + 'lex_comment' => '/(?:'.preg_quote($this->options['whitespace_trim'], '/').preg_quote($this->options['tag_comment'][1], '/').'\s*|'.preg_quote($this->options['tag_comment'][1], '/').')\n?/s', + 'lex_block_raw' => '/\s*(raw|verbatim)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/As', + 'lex_block_line' => '/\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', + 'lex_tokens_start' => '/('.preg_quote($this->options['tag_variable'][0], '/').'|'.preg_quote($this->options['tag_block'][0], '/').'|'.preg_quote($this->options['tag_comment'][0], '/').')('.preg_quote($this->options['whitespace_trim'], '/').')?/s', + 'interpolation_start' => '/'.preg_quote($this->options['interpolation'][0], '/').'\s*/A', + 'interpolation_end' => '/\s*'.preg_quote($this->options['interpolation'][1], '/').'/A', + ); + } + + /** + * {@inheritdoc} + */ + public function tokenize($code, $name = null) + { + if (!$code instanceof Twig_Source) { + @trigger_error(sprintf('Passing a string as the $code argument of %s() is deprecated since version 1.27 and will be removed in 2.0. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); + $this->source = new Twig_Source($code, $name); + } else { + $this->source = $code; + } + + if (((int) ini_get('mbstring.func_overload')) & 2) { + @trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED); + } + + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } else { + $mbEncoding = null; + } + + $this->code = str_replace(array("\r\n", "\r"), "\n", $this->source->getCode()); + $this->filename = $this->source->getName(); + $this->cursor = 0; + $this->lineno = 1; + $this->end = strlen($this->code); + $this->tokens = array(); + $this->state = self::STATE_DATA; + $this->states = array(); + $this->brackets = array(); + $this->position = -1; + + // find all token starts in one go + preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE); + $this->positions = $matches; + + while ($this->cursor < $this->end) { + // dispatch to the lexing functions depending + // on the current state + switch ($this->state) { + case self::STATE_DATA: + $this->lexData(); + break; + + case self::STATE_BLOCK: + $this->lexBlock(); + break; + + case self::STATE_VAR: + $this->lexVar(); + break; + + case self::STATE_STRING: + $this->lexString(); + break; + + case self::STATE_INTERPOLATION: + $this->lexInterpolation(); + break; + } + } + + $this->pushToken(Twig_Token::EOF_TYPE); + + if (!empty($this->brackets)) { + list($expect, $lineno) = array_pop($this->brackets); + throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + + if ($mbEncoding) { + mb_internal_encoding($mbEncoding); + } + + return new Twig_TokenStream($this->tokens, $this->source); + } + + protected function lexData() + { + // if no matches are left we return the rest of the template as simple text token + if ($this->position == count($this->positions[0]) - 1) { + $this->pushToken(Twig_Token::TEXT_TYPE, substr($this->code, $this->cursor)); + $this->cursor = $this->end; + + return; + } + + // Find the first token after the current cursor + $position = $this->positions[0][++$this->position]; + while ($position[1] < $this->cursor) { + if ($this->position == count($this->positions[0]) - 1) { + return; + } + $position = $this->positions[0][++$this->position]; + } + + // push the template text first + $text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor); + if (isset($this->positions[2][$this->position][0])) { + $text = rtrim($text); + } + $this->pushToken(Twig_Token::TEXT_TYPE, $text); + $this->moveCursor($textContent.$position[0]); + + switch ($this->positions[1][$this->position][0]) { + case $this->options['tag_comment'][0]: + $this->lexComment(); + break; + + case $this->options['tag_block'][0]: + // raw data? + if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, null, $this->cursor)) { + $this->moveCursor($match[0]); + $this->lexRawData($match[1]); + // {% line \d+ %} + } elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, null, $this->cursor)) { + $this->moveCursor($match[0]); + $this->lineno = (int) $match[1]; + } else { + $this->pushToken(Twig_Token::BLOCK_START_TYPE); + $this->pushState(self::STATE_BLOCK); + $this->currentVarBlockLine = $this->lineno; + } + break; + + case $this->options['tag_variable'][0]: + $this->pushToken(Twig_Token::VAR_START_TYPE); + $this->pushState(self::STATE_VAR); + $this->currentVarBlockLine = $this->lineno; + break; + } + } + + protected function lexBlock() + { + if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, null, $this->cursor)) { + $this->pushToken(Twig_Token::BLOCK_END_TYPE); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + protected function lexVar() + { + if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, null, $this->cursor)) { + $this->pushToken(Twig_Token::VAR_END_TYPE); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + protected function lexExpression() + { + // whitespace + if (preg_match('/\s+/A', $this->code, $match, null, $this->cursor)) { + $this->moveCursor($match[0]); + + if ($this->cursor >= $this->end) { + throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $this->state === self::STATE_BLOCK ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source); + } + } + + // operators + if (preg_match($this->regexes['operator'], $this->code, $match, null, $this->cursor)) { + $this->pushToken(Twig_Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0])); + $this->moveCursor($match[0]); + } + // names + elseif (preg_match(self::REGEX_NAME, $this->code, $match, null, $this->cursor)) { + $this->pushToken(Twig_Token::NAME_TYPE, $match[0]); + $this->moveCursor($match[0]); + } + // numbers + elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, null, $this->cursor)) { + $number = (float) $match[0]; // floats + if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) { + $number = (int) $match[0]; // integers lower than the maximum + } + $this->pushToken(Twig_Token::NUMBER_TYPE, $number); + $this->moveCursor($match[0]); + } + // punctuation + elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) { + // opening bracket + if (false !== strpos('([{', $this->code[$this->cursor])) { + $this->brackets[] = array($this->code[$this->cursor], $this->lineno); + } + // closing bracket + elseif (false !== strpos(')]}', $this->code[$this->cursor])) { + if (empty($this->brackets)) { + throw new Twig_Error_Syntax(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + + list($expect, $lineno) = array_pop($this->brackets); + if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) { + throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + } + + $this->pushToken(Twig_Token::PUNCTUATION_TYPE, $this->code[$this->cursor]); + ++$this->cursor; + } + // strings + elseif (preg_match(self::REGEX_STRING, $this->code, $match, null, $this->cursor)) { + $this->pushToken(Twig_Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1))); + $this->moveCursor($match[0]); + } + // opening double quoted string + elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) { + $this->brackets[] = array('"', $this->lineno); + $this->pushState(self::STATE_STRING); + $this->moveCursor($match[0]); + } + // unlexable + else { + throw new Twig_Error_Syntax(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + } + + protected function lexRawData($tag) + { + if ('raw' === $tag) { + @trigger_error(sprintf('Twig Tag "raw" is deprecated since version 1.21. Use "verbatim" instead in %s at line %d.', $this->filename, $this->lineno), E_USER_DEPRECATED); + } + + if (!preg_match(str_replace('%s', $tag, $this->regexes['lex_raw_data']), $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { + throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s" block.', $tag), $this->lineno, $this->source); + } + + $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); + $this->moveCursor($text.$match[0][0]); + + if (false !== strpos($match[1][0], $this->options['whitespace_trim'])) { + $text = rtrim($text); + } + + $this->pushToken(Twig_Token::TEXT_TYPE, $text); + } + + protected function lexComment() + { + if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { + throw new Twig_Error_Syntax('Unclosed comment.', $this->lineno, $this->source); + } + + $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]); + } + + protected function lexString() + { + if (preg_match($this->regexes['interpolation_start'], $this->code, $match, null, $this->cursor)) { + $this->brackets[] = array($this->options['interpolation'][0], $this->lineno); + $this->pushToken(Twig_Token::INTERPOLATION_START_TYPE); + $this->moveCursor($match[0]); + $this->pushState(self::STATE_INTERPOLATION); + } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, null, $this->cursor) && strlen($match[0]) > 0) { + $this->pushToken(Twig_Token::STRING_TYPE, stripcslashes($match[0])); + $this->moveCursor($match[0]); + } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) { + list($expect, $lineno) = array_pop($this->brackets); + if ($this->code[$this->cursor] != '"') { + throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + + $this->popState(); + ++$this->cursor; + } + } + + protected function lexInterpolation() + { + $bracket = end($this->brackets); + if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, null, $this->cursor)) { + array_pop($this->brackets); + $this->pushToken(Twig_Token::INTERPOLATION_END_TYPE); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + protected function pushToken($type, $value = '') + { + // do not push empty text tokens + if (Twig_Token::TEXT_TYPE === $type && '' === $value) { + return; + } + + $this->tokens[] = new Twig_Token($type, $value, $this->lineno); + } + + protected function moveCursor($text) + { + $this->cursor += strlen($text); + $this->lineno += substr_count($text, "\n"); + } + + protected function getOperatorRegex() + { + $operators = array_merge( + array('='), + array_keys($this->env->getUnaryOperators()), + array_keys($this->env->getBinaryOperators()) + ); + + $operators = array_combine($operators, array_map('strlen', $operators)); + arsort($operators); + + $regex = array(); + foreach ($operators as $operator => $length) { + // an operator that ends with a character must be followed by + // a whitespace or a parenthesis + if (ctype_alpha($operator[$length - 1])) { + $r = preg_quote($operator, '/').'(?=[\s()])'; + } else { + $r = preg_quote($operator, '/'); + } + + // an operator with a space can be any amount of whitespaces + $r = preg_replace('/\s+/', '\s+', $r); + + $regex[] = $r; + } + + return '/'.implode('|', $regex).'/A'; + } + + protected function pushState($state) + { + $this->states[] = $this->state; + $this->state = $state; + } + + protected function popState() + { + if (0 === count($this->states)) { + throw new Exception('Cannot pop state without a previous state.'); + } + + $this->state = array_pop($this->states); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/LexerInterface.php b/src/vendor/twig/twig/lib/Twig/LexerInterface.php new file mode 100644 index 0000000..afd8633 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/LexerInterface.php @@ -0,0 +1,32 @@ + + * + * @deprecated since 1.12 (to be removed in 3.0) + */ +interface Twig_LexerInterface +{ + /** + * Tokenizes a source code. + * + * @param string|Twig_Source $code The source code + * @param string $name A unique identifier for the source code + * + * @return Twig_TokenStream + * + * @throws Twig_Error_Syntax When the code is syntactically wrong + */ + public function tokenize($code, $name = null); +} diff --git a/src/vendor/twig/twig/lib/Twig/Loader/Array.php b/src/vendor/twig/twig/lib/Twig/Loader/Array.php new file mode 100644 index 0000000..2c4ae25 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Loader/Array.php @@ -0,0 +1,110 @@ + + */ +class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface +{ + protected $templates = array(); + + /** + * @param array $templates An array of templates (keys are the names, and values are the source code) + */ + public function __construct(array $templates = array()) + { + $this->templates = $templates; + } + + /** + * Adds or overrides a template. + * + * @param string $name The template name + * @param string $template The template source + */ + public function setTemplate($name, $template) + { + $this->templates[(string) $name] = $template; + } + + /** + * {@inheritdoc} + */ + public function getSource($name) + { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', get_class($this)), E_USER_DEPRECATED); + + $name = (string) $name; + if (!isset($this->templates[$name])) { + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); + } + + return $this->templates[$name]; + } + + /** + * {@inheritdoc} + */ + public function getSourceContext($name) + { + $name = (string) $name; + if (!isset($this->templates[$name])) { + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); + } + + return new Twig_Source($this->templates[$name], $name); + } + + /** + * {@inheritdoc} + */ + public function exists($name) + { + return isset($this->templates[(string) $name]); + } + + /** + * {@inheritdoc} + */ + public function getCacheKey($name) + { + $name = (string) $name; + if (!isset($this->templates[$name])) { + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); + } + + return $this->templates[$name]; + } + + /** + * {@inheritdoc} + */ + public function isFresh($name, $time) + { + $name = (string) $name; + if (!isset($this->templates[$name])) { + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); + } + + return true; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Loader/Chain.php b/src/vendor/twig/twig/lib/Twig/Loader/Chain.php new file mode 100644 index 0000000..c48d921 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Loader/Chain.php @@ -0,0 +1,164 @@ + + */ +class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface +{ + private $hasSourceCache = array(); + protected $loaders = array(); + + /** + * @param Twig_LoaderInterface[] $loaders + */ + public function __construct(array $loaders = array()) + { + foreach ($loaders as $loader) { + $this->addLoader($loader); + } + } + + public function addLoader(Twig_LoaderInterface $loader) + { + $this->loaders[] = $loader; + $this->hasSourceCache = array(); + } + + /** + * {@inheritdoc} + */ + public function getSource($name) + { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', get_class($this)), E_USER_DEPRECATED); + + $exceptions = array(); + foreach ($this->loaders as $loader) { + if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) { + continue; + } + + try { + return $loader->getSource($name); + } catch (Twig_Error_Loader $e) { + $exceptions[] = $e->getMessage(); + } + } + + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + /** + * {@inheritdoc} + */ + public function getSourceContext($name) + { + $exceptions = array(); + foreach ($this->loaders as $loader) { + if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) { + continue; + } + + try { + if ($loader instanceof Twig_SourceContextLoaderInterface) { + return $loader->getSourceContext($name); + } + + return new Twig_Source($loader->getSource($name), $name); + } catch (Twig_Error_Loader $e) { + $exceptions[] = $e->getMessage(); + } + } + + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + /** + * {@inheritdoc} + */ + public function exists($name) + { + $name = (string) $name; + + if (isset($this->hasSourceCache[$name])) { + return $this->hasSourceCache[$name]; + } + + foreach ($this->loaders as $loader) { + if ($loader instanceof Twig_ExistsLoaderInterface) { + if ($loader->exists($name)) { + return $this->hasSourceCache[$name] = true; + } + + continue; + } + + try { + if ($loader instanceof Twig_SourceContextLoaderInterface) { + $loader->getSourceContext($name); + } else { + $loader->getSource($name); + } + + return $this->hasSourceCache[$name] = true; + } catch (Twig_Error_Loader $e) { + } + } + + return $this->hasSourceCache[$name] = false; + } + + /** + * {@inheritdoc} + */ + public function getCacheKey($name) + { + $exceptions = array(); + foreach ($this->loaders as $loader) { + if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) { + continue; + } + + try { + return $loader->getCacheKey($name); + } catch (Twig_Error_Loader $e) { + $exceptions[] = get_class($loader).': '.$e->getMessage(); + } + } + + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + /** + * {@inheritdoc} + */ + public function isFresh($name, $time) + { + $exceptions = array(); + foreach ($this->loaders as $loader) { + if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) { + continue; + } + + try { + return $loader->isFresh($name, $time); + } catch (Twig_Error_Loader $e) { + $exceptions[] = get_class($loader).': '.$e->getMessage(); + } + } + + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Loader/Filesystem.php b/src/vendor/twig/twig/lib/Twig/Loader/Filesystem.php new file mode 100644 index 0000000..b920478 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Loader/Filesystem.php @@ -0,0 +1,303 @@ + + */ +class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface +{ + /** Identifier of the main namespace. */ + const MAIN_NAMESPACE = '__main__'; + + protected $paths = array(); + protected $cache = array(); + protected $errorCache = array(); + + private $rootPath; + + /** + * @param string|array $paths A path or an array of paths where to look for templates + * @param string|null $rootPath The root path common to all relative paths (null for getcwd()) + */ + public function __construct($paths = array(), $rootPath = null) + { + $this->rootPath = (null === $rootPath ? getcwd() : $rootPath).DIRECTORY_SEPARATOR; + if (false !== $realPath = realpath($rootPath)) { + $this->rootPath = $realPath.DIRECTORY_SEPARATOR; + } + + if ($paths) { + $this->setPaths($paths); + } + } + + /** + * Returns the paths to the templates. + * + * @param string $namespace A path namespace + * + * @return array The array of paths where to look for templates + */ + public function getPaths($namespace = self::MAIN_NAMESPACE) + { + return isset($this->paths[$namespace]) ? $this->paths[$namespace] : array(); + } + + /** + * Returns the path namespaces. + * + * The main namespace is always defined. + * + * @return array The array of defined namespaces + */ + public function getNamespaces() + { + return array_keys($this->paths); + } + + /** + * Sets the paths where templates are stored. + * + * @param string|array $paths A path or an array of paths where to look for templates + * @param string $namespace A path namespace + */ + public function setPaths($paths, $namespace = self::MAIN_NAMESPACE) + { + if (!is_array($paths)) { + $paths = array($paths); + } + + $this->paths[$namespace] = array(); + foreach ($paths as $path) { + $this->addPath($path, $namespace); + } + } + + /** + * Adds a path where templates are stored. + * + * @param string $path A path where to look for templates + * @param string $namespace A path namespace + * + * @throws Twig_Error_Loader + */ + public function addPath($path, $namespace = self::MAIN_NAMESPACE) + { + // invalidate the cache + $this->cache = $this->errorCache = array(); + + $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; + if (!is_dir($checkPath)) { + throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); + } + + $this->paths[$namespace][] = rtrim($path, '/\\'); + } + + /** + * Prepends a path where templates are stored. + * + * @param string $path A path where to look for templates + * @param string $namespace A path namespace + * + * @throws Twig_Error_Loader + */ + public function prependPath($path, $namespace = self::MAIN_NAMESPACE) + { + // invalidate the cache + $this->cache = $this->errorCache = array(); + + $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; + if (!is_dir($checkPath)) { + throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); + } + + $path = rtrim($path, '/\\'); + + if (!isset($this->paths[$namespace])) { + $this->paths[$namespace][] = $path; + } else { + array_unshift($this->paths[$namespace], $path); + } + } + + /** + * {@inheritdoc} + */ + public function getSource($name) + { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', get_class($this)), E_USER_DEPRECATED); + + return file_get_contents($this->findTemplate($name)); + } + + /** + * {@inheritdoc} + */ + public function getSourceContext($name) + { + $path = $this->findTemplate($name); + + return new Twig_Source(file_get_contents($path), $name, $path); + } + + /** + * {@inheritdoc} + */ + public function getCacheKey($name) + { + $path = $this->findTemplate($name); + $len = strlen($this->rootPath); + if (0 === strncmp($this->rootPath, $path, $len)) { + return substr($path, $len); + } + + return $path; + } + + /** + * {@inheritdoc} + */ + public function exists($name) + { + $name = $this->normalizeName($name); + + if (isset($this->cache[$name])) { + return true; + } + + try { + return false !== $this->findTemplate($name, false); + } catch (Twig_Error_Loader $exception) { + @trigger_error(sprintf('In %s::findTemplate(), you must accept a second argument that when set to "false" returns "false" instead of throwing an exception. Not supporting this argument is deprecated since version 1.27.', get_class($this)), E_USER_DEPRECATED); + + return false; + } + } + + /** + * {@inheritdoc} + */ + public function isFresh($name, $time) + { + return filemtime($this->findTemplate($name)) <= $time; + } + + protected function findTemplate($name) + { + $throw = func_num_args() > 1 ? func_get_arg(1) : true; + $name = $this->normalizeName($name); + + if (isset($this->cache[$name])) { + return $this->cache[$name]; + } + + if (isset($this->errorCache[$name])) { + if (!$throw) { + return false; + } + + throw new Twig_Error_Loader($this->errorCache[$name]); + } + + $this->validateName($name); + + list($namespace, $shortname) = $this->parseName($name); + + if (!isset($this->paths[$namespace])) { + $this->errorCache[$name] = sprintf('There are no registered paths for namespace "%s".', $namespace); + + if (!$throw) { + return false; + } + + throw new Twig_Error_Loader($this->errorCache[$name]); + } + + foreach ($this->paths[$namespace] as $path) { + if (!$this->isAbsolutePath($path)) { + $path = $this->rootPath.'/'.$path; + } + + if (is_file($path.'/'.$shortname)) { + if (false !== $realpath = realpath($path.'/'.$shortname)) { + return $this->cache[$name] = $realpath; + } + + return $this->cache[$name] = $path.'/'.$shortname; + } + } + + $this->errorCache[$name] = sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); + + if (!$throw) { + return false; + } + + throw new Twig_Error_Loader($this->errorCache[$name]); + } + + protected function parseName($name, $default = self::MAIN_NAMESPACE) + { + if (isset($name[0]) && '@' == $name[0]) { + if (false === $pos = strpos($name, '/')) { + throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); + } + + $namespace = substr($name, 1, $pos - 1); + $shortname = substr($name, $pos + 1); + + return array($namespace, $shortname); + } + + return array($default, $name); + } + + protected function normalizeName($name) + { + return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name)); + } + + protected function validateName($name) + { + if (false !== strpos($name, "\0")) { + throw new Twig_Error_Loader('A template name cannot contain NUL bytes.'); + } + + $name = ltrim($name, '/'); + $parts = explode('/', $name); + $level = 0; + foreach ($parts as $part) { + if ('..' === $part) { + --$level; + } elseif ('.' !== $part) { + ++$level; + } + + if ($level < 0) { + throw new Twig_Error_Loader(sprintf('Looks like you try to load a template outside configured directories (%s).', $name)); + } + } + } + + private function isAbsolutePath($file) + { + return strspn($file, '/\\', 0, 1) + || (strlen($file) > 3 && ctype_alpha($file[0]) + && substr($file, 1, 1) === ':' + && strspn($file, '/\\', 2, 1) + ) + || null !== parse_url($file, PHP_URL_SCHEME) + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Loader/String.php b/src/vendor/twig/twig/lib/Twig/Loader/String.php new file mode 100644 index 0000000..46da4a4 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Loader/String.php @@ -0,0 +1,73 @@ + + */ +class Twig_Loader_String implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface +{ + /** + * {@inheritdoc} + */ + public function getSource($name) + { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', get_class($this)), E_USER_DEPRECATED); + + return $name; + } + + /** + * {@inheritdoc} + */ + public function getSourceContext($name) + { + return new Twig_Source($name, $name); + } + + /** + * {@inheritdoc} + */ + public function exists($name) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getCacheKey($name) + { + return $name; + } + + /** + * {@inheritdoc} + */ + public function isFresh($name, $time) + { + return true; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/LoaderInterface.php b/src/vendor/twig/twig/lib/Twig/LoaderInterface.php new file mode 100644 index 0000000..840ab14 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/LoaderInterface.php @@ -0,0 +1,55 @@ + + */ +interface Twig_LoaderInterface +{ + /** + * Gets the source code of a template, given its name. + * + * @param string $name The name of the template to load + * + * @return string The template source code + * + * @throws Twig_Error_Loader When $name is not found + * + * @deprecated since 1.27 (to be removed in 2.0), implement Twig_SourceContextLoaderInterface + */ + public function getSource($name); + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name The name of the template to load + * + * @return string The cache key + * + * @throws Twig_Error_Loader When $name is not found + */ + public function getCacheKey($name); + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param int $time Timestamp of the last modification time of the + * cached template + * + * @return bool true if the template is fresh, false otherwise + * + * @throws Twig_Error_Loader When $name is not found + */ + public function isFresh($name, $time); +} diff --git a/src/vendor/twig/twig/lib/Twig/Markup.php b/src/vendor/twig/twig/lib/Twig/Markup.php new file mode 100644 index 0000000..69871fc --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Markup.php @@ -0,0 +1,37 @@ + + */ +class Twig_Markup implements Countable +{ + protected $content; + protected $charset; + + public function __construct($content, $charset) + { + $this->content = (string) $content; + $this->charset = $charset; + } + + public function __toString() + { + return $this->content; + } + + public function count() + { + return function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : strlen($this->content); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node.php b/src/vendor/twig/twig/lib/Twig/Node.php new file mode 100644 index 0000000..ba0596c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node.php @@ -0,0 +1,253 @@ + + */ +class Twig_Node implements Twig_NodeInterface +{ + protected $nodes; + protected $attributes; + protected $lineno; + protected $tag; + + private $name; + + /** + * Constructor. + * + * The nodes are automatically made available as properties ($this->node). + * The attributes are automatically made available as array items ($this['name']). + * + * @param array $nodes An array of named nodes + * @param array $attributes An array of attributes (should not be nodes) + * @param int $lineno The line number + * @param string $tag The tag name associated with the Node + */ + public function __construct(array $nodes = array(), array $attributes = array(), $lineno = 0, $tag = null) + { + foreach ($nodes as $name => $node) { + if (!$node instanceof Twig_NodeInterface) { + @trigger_error(sprintf('Using "%s" for the value of node "%s" of "%s" is deprecated since version 1.25 and will be removed in 2.0.', is_object($node) ? get_class($node) : null === $node ? 'null' : gettype($node), $name, get_class($this)), E_USER_DEPRECATED); + } + } + $this->nodes = $nodes; + $this->attributes = $attributes; + $this->lineno = $lineno; + $this->tag = $tag; + } + + public function __toString() + { + $attributes = array(); + foreach ($this->attributes as $name => $value) { + $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); + } + + $repr = array(get_class($this).'('.implode(', ', $attributes)); + + if (count($this->nodes)) { + foreach ($this->nodes as $name => $node) { + $len = strlen($name) + 4; + $noderepr = array(); + foreach (explode("\n", (string) $node) as $line) { + $noderepr[] = str_repeat(' ', $len).$line; + } + + $repr[] = sprintf(' %s: %s', $name, ltrim(implode("\n", $noderepr))); + } + + $repr[] = ')'; + } else { + $repr[0] .= ')'; + } + + return implode("\n", $repr); + } + + /** + * @deprecated since 1.16.1 (to be removed in 2.0) + */ + public function toXml($asDom = false) + { + @trigger_error(sprintf('%s is deprecated since version 1.16.1 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED); + + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + $dom->appendChild($xml = $dom->createElement('twig')); + + $xml->appendChild($node = $dom->createElement('node')); + $node->setAttribute('class', get_class($this)); + + foreach ($this->attributes as $name => $value) { + $node->appendChild($attribute = $dom->createElement('attribute')); + $attribute->setAttribute('name', $name); + $attribute->appendChild($dom->createTextNode($value)); + } + + foreach ($this->nodes as $name => $n) { + if (null === $n) { + continue; + } + + $child = $n->toXml(true)->getElementsByTagName('node')->item(0); + $child = $dom->importNode($child, true); + $child->setAttribute('name', $name); + + $node->appendChild($child); + } + + return $asDom ? $dom : $dom->saveXML(); + } + + public function compile(Twig_Compiler $compiler) + { + foreach ($this->nodes as $node) { + $node->compile($compiler); + } + } + + public function getTemplateLine() + { + return $this->lineno; + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function getLine() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateLine() instead.', E_USER_DEPRECATED); + + return $this->lineno; + } + + public function getNodeTag() + { + return $this->tag; + } + + /** + * @return bool + */ + public function hasAttribute($name) + { + return array_key_exists($name, $this->attributes); + } + + /** + * @return mixed + */ + public function getAttribute($name) + { + if (!array_key_exists($name, $this->attributes)) { + throw new LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this))); + } + + return $this->attributes[$name]; + } + + /** + * @param string $name + * @param mixed $value + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + } + + public function removeAttribute($name) + { + unset($this->attributes[$name]); + } + + /** + * @return bool + */ + public function hasNode($name) + { + return array_key_exists($name, $this->nodes); + } + + /** + * @return Twig_Node + */ + public function getNode($name) + { + if (!array_key_exists($name, $this->nodes)) { + throw new LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this))); + } + + return $this->nodes[$name]; + } + + public function setNode($name, $node = null) + { + if (!$node instanceof Twig_NodeInterface) { + @trigger_error(sprintf('Using "%s" for the value of node "%s" of "%s" is deprecated since version 1.25 and will be removed in 2.0.', is_object($node) ? get_class($node) : null === $node ? 'null' : gettype($node), $name, get_class($this)), E_USER_DEPRECATED); + } + + $this->nodes[$name] = $node; + } + + public function removeNode($name) + { + unset($this->nodes[$name]); + } + + public function count() + { + return count($this->nodes); + } + + public function getIterator() + { + return new ArrayIterator($this->nodes); + } + + public function setTemplateName($name) + { + $this->name = $name; + foreach ($this->nodes as $node) { + if (null !== $node) { + $node->setTemplateName($name); + } + } + } + + public function getTemplateName() + { + return $this->name; + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function setFilename($name) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use setTemplateName() instead.', E_USER_DEPRECATED); + + $this->setTemplateName($name); + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function getFilename() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateName() instead.', E_USER_DEPRECATED); + + return $this->name; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/AutoEscape.php b/src/vendor/twig/twig/lib/Twig/Node/AutoEscape.php new file mode 100644 index 0000000..47cc998 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/AutoEscape.php @@ -0,0 +1,34 @@ + + */ +class Twig_Node_AutoEscape extends Twig_Node +{ + public function __construct($value, Twig_NodeInterface $body, $lineno, $tag = 'autoescape') + { + parent::__construct(array('body' => $body), array('value' => $value), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->subcompile($this->getNode('body')); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Block.php b/src/vendor/twig/twig/lib/Twig/Node/Block.php new file mode 100644 index 0000000..a05af6f --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Block.php @@ -0,0 +1,39 @@ + + */ +class Twig_Node_Block extends Twig_Node +{ + public function __construct($name, Twig_NodeInterface $body, $lineno, $tag = null) + { + parent::__construct(array('body' => $body), array('name' => $name), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write(sprintf("public function block_%s(\$context, array \$blocks = array())\n", $this->getAttribute('name')), "{\n") + ->indent() + ; + + $compiler + ->subcompile($this->getNode('body')) + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/BlockReference.php b/src/vendor/twig/twig/lib/Twig/Node/BlockReference.php new file mode 100644 index 0000000..9cd1551 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/BlockReference.php @@ -0,0 +1,32 @@ + + */ +class Twig_Node_BlockReference extends Twig_Node implements Twig_NodeOutputInterface +{ + public function __construct($name, $lineno, $tag = null) + { + parent::__construct(array(), array('name' => $name), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write(sprintf("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name'))) + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Body.php b/src/vendor/twig/twig/lib/Twig/Node/Body.php new file mode 100644 index 0000000..3ffb134 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Body.php @@ -0,0 +1,19 @@ + + */ +class Twig_Node_Body extends Twig_Node +{ +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/CheckSecurity.php b/src/vendor/twig/twig/lib/Twig/Node/CheckSecurity.php new file mode 100644 index 0000000..cdaaf2e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/CheckSecurity.php @@ -0,0 +1,78 @@ + + */ +class Twig_Node_CheckSecurity extends Twig_Node +{ + protected $usedFilters; + protected $usedTags; + protected $usedFunctions; + + public function __construct(array $usedFilters, array $usedTags, array $usedFunctions) + { + $this->usedFilters = $usedFilters; + $this->usedTags = $usedTags; + $this->usedFunctions = $usedFunctions; + + parent::__construct(); + } + + public function compile(Twig_Compiler $compiler) + { + $tags = $filters = $functions = array(); + foreach (array('tags', 'filters', 'functions') as $type) { + foreach ($this->{'used'.ucfirst($type)} as $name => $node) { + if ($node instanceof Twig_Node) { + ${$type}[$name] = $node->getTemplateLine(); + } else { + ${$type}[$node] = null; + } + } + } + + $compiler + ->write('$tags = ')->repr(array_filter($tags))->raw(";\n") + ->write('$filters = ')->repr(array_filter($filters))->raw(";\n") + ->write('$functions = ')->repr(array_filter($functions))->raw(";\n\n") + ->write("try {\n") + ->indent() + ->write("\$this->env->getExtension('Twig_Extension_Sandbox')->checkSecurity(\n") + ->indent() + ->write(!$tags ? "array(),\n" : "array('".implode("', '", array_keys($tags))."'),\n") + ->write(!$filters ? "array(),\n" : "array('".implode("', '", array_keys($filters))."'),\n") + ->write(!$functions ? "array()\n" : "array('".implode("', '", array_keys($functions))."')\n") + ->outdent() + ->write(");\n") + ->outdent() + ->write("} catch (Twig_Sandbox_SecurityError \$e) {\n") + ->indent() + ->write("\$e->setSourceContext(\$this->getSourceContext());\n\n") + ->write("if (\$e instanceof Twig_Sandbox_SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n") + ->outdent() + ->write("} elseif (\$e instanceof Twig_Sandbox_SecurityNotAllowedFilterError && isset(\$filters[\$e->getFilterName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$filters[\$e->getFilterName()]);\n") + ->outdent() + ->write("} elseif (\$e instanceof Twig_Sandbox_SecurityNotAllowedFunctionError && isset(\$functions[\$e->getFunctionName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$functions[\$e->getFunctionName()]);\n") + ->outdent() + ->write("}\n\n") + ->write("throw \$e;\n") + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Do.php b/src/vendor/twig/twig/lib/Twig/Node/Do.php new file mode 100644 index 0000000..14fb84e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Do.php @@ -0,0 +1,33 @@ + + */ +class Twig_Node_Do extends Twig_Node +{ + public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null) + { + parent::__construct(array('expr' => $expr), array(), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('') + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Embed.php b/src/vendor/twig/twig/lib/Twig/Node/Embed.php new file mode 100644 index 0000000..d9af37b --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Embed.php @@ -0,0 +1,44 @@ + + */ +class Twig_Node_Embed extends Twig_Node_Include +{ + // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module) + public function __construct($name, $index, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) + { + parent::__construct(new Twig_Node_Expression_Constant('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag); + + $this->setAttribute('name', $name); + // to be removed in 2.0, used name instead + $this->setAttribute('filename', $name); + $this->setAttribute('index', $index); + } + + protected function addGetTemplate(Twig_Compiler $compiler) + { + $compiler + ->write('$this->loadTemplate(') + ->string($this->getAttribute('name')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(', ') + ->string($this->getAttribute('index')) + ->raw(')') + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression.php b/src/vendor/twig/twig/lib/Twig/Node/Expression.php new file mode 100644 index 0000000..a7382e7 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression.php @@ -0,0 +1,20 @@ + + */ +abstract class Twig_Node_Expression extends Twig_Node +{ +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Array.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Array.php new file mode 100644 index 0000000..aba1825 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Array.php @@ -0,0 +1,81 @@ +index = -1; + foreach ($this->getKeyValuePairs() as $pair) { + if ($pair['key'] instanceof Twig_Node_Expression_Constant && ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) { + $this->index = $pair['key']->getAttribute('value'); + } + } + } + + public function getKeyValuePairs() + { + $pairs = array(); + + foreach (array_chunk($this->nodes, 2) as $pair) { + $pairs[] = array( + 'key' => $pair[0], + 'value' => $pair[1], + ); + } + + return $pairs; + } + + public function hasElement(Twig_Node_Expression $key) + { + foreach ($this->getKeyValuePairs() as $pair) { + // we compare the string representation of the keys + // to avoid comparing the line numbers which are not relevant here. + if ((string) $key === (string) $pair['key']) { + return true; + } + } + + return false; + } + + public function addElement(Twig_Node_Expression $value, Twig_Node_Expression $key = null) + { + if (null === $key) { + $key = new Twig_Node_Expression_Constant(++$this->index, $value->getTemplateLine()); + } + + array_push($this->nodes, $key, $value); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->raw('array('); + $first = true; + foreach ($this->getKeyValuePairs() as $pair) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + $compiler + ->subcompile($pair['key']) + ->raw(' => ') + ->subcompile($pair['value']) + ; + } + $compiler->raw(')'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/AssignName.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/AssignName.php new file mode 100644 index 0000000..ce0c5fb --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/AssignName.php @@ -0,0 +1,23 @@ +raw('$context[') + ->string($this->getAttribute('name')) + ->raw(']') + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary.php new file mode 100644 index 0000000..c821db5 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary.php @@ -0,0 +1,35 @@ + $left, 'right' => $right), array(), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('left')) + ->raw(' ') + ; + $this->operator($compiler); + $compiler + ->raw(' ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + abstract public function operator(Twig_Compiler $compiler); +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Add.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Add.php new file mode 100644 index 0000000..0ef8e11 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Add.php @@ -0,0 +1,18 @@ +raw('+'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/And.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/And.php new file mode 100644 index 0000000..d5752eb --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/And.php @@ -0,0 +1,18 @@ +raw('&&'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php new file mode 100644 index 0000000..9a46d84 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php @@ -0,0 +1,18 @@ +raw('&'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php new file mode 100644 index 0000000..058a20b --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php @@ -0,0 +1,18 @@ +raw('|'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php new file mode 100644 index 0000000..f4da73d --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php @@ -0,0 +1,18 @@ +raw('^'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php new file mode 100644 index 0000000..f9a6462 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php @@ -0,0 +1,18 @@ +raw('.'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Div.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Div.php new file mode 100644 index 0000000..e0797a6 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Div.php @@ -0,0 +1,18 @@ +raw('/'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php new file mode 100644 index 0000000..93b3b96 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php @@ -0,0 +1,30 @@ +getVarName(); + $right = $compiler->getVarName(); + $compiler + ->raw(sprintf('(is_string($%s = ', $left)) + ->subcompile($this->getNode('left')) + ->raw(sprintf(') && is_string($%s = ', $right)) + ->subcompile($this->getNode('right')) + ->raw(sprintf(') && (\'\' === $%2$s || $%2$s === substr($%1$s, -strlen($%2$s))))', $left, $right)) + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw(''); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php new file mode 100644 index 0000000..7b1236d --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php @@ -0,0 +1,17 @@ +raw('=='); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php new file mode 100644 index 0000000..6bbba62 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php @@ -0,0 +1,24 @@ +raw('(int) floor('); + parent::compile($compiler); + $compiler->raw(')'); + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw('/'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php new file mode 100644 index 0000000..a110bd9 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php @@ -0,0 +1,17 @@ +raw('>'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php new file mode 100644 index 0000000..3754fed --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php @@ -0,0 +1,17 @@ +raw('>='); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/In.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/In.php new file mode 100644 index 0000000..9565a60 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/In.php @@ -0,0 +1,28 @@ +raw('twig_in_filter(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw('in'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Less.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Less.php new file mode 100644 index 0000000..45fd300 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Less.php @@ -0,0 +1,17 @@ +raw('<'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/LessEqual.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/LessEqual.php new file mode 100644 index 0000000..e38e257 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/LessEqual.php @@ -0,0 +1,17 @@ +raw('<='); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Matches.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Matches.php new file mode 100644 index 0000000..93bb292 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Matches.php @@ -0,0 +1,28 @@ +raw('preg_match(') + ->subcompile($this->getNode('right')) + ->raw(', ') + ->subcompile($this->getNode('left')) + ->raw(')') + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw(''); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php new file mode 100644 index 0000000..9924114 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php @@ -0,0 +1,18 @@ +raw('%'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php new file mode 100644 index 0000000..c91529c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php @@ -0,0 +1,18 @@ +raw('*'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php new file mode 100644 index 0000000..26867ba --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php @@ -0,0 +1,17 @@ +raw('!='); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php new file mode 100644 index 0000000..49ab39e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php @@ -0,0 +1,28 @@ +raw('!twig_in_filter(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw('not in'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Or.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Or.php new file mode 100644 index 0000000..adba49c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Or.php @@ -0,0 +1,18 @@ +raw('||'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Power.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Power.php new file mode 100644 index 0000000..76f3a8c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Power.php @@ -0,0 +1,32 @@ += 50600) { + return parent::compile($compiler); + } + + $compiler + ->raw('pow(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw('**'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Range.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Range.php new file mode 100644 index 0000000..692ec9c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Range.php @@ -0,0 +1,28 @@ +raw('range(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw('..'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php new file mode 100644 index 0000000..d2e30d6 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php @@ -0,0 +1,30 @@ +getVarName(); + $right = $compiler->getVarName(); + $compiler + ->raw(sprintf('(is_string($%s = ', $left)) + ->subcompile($this->getNode('left')) + ->raw(sprintf(') && is_string($%s = ', $right)) + ->subcompile($this->getNode('right')) + ->raw(sprintf(') && (\'\' === $%2$s || 0 === strpos($%1$s, $%2$s)))', $left, $right)) + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw(''); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php new file mode 100644 index 0000000..d446399 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php @@ -0,0 +1,18 @@ +raw('-'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/BlockReference.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/BlockReference.php new file mode 100644 index 0000000..adc63fc --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/BlockReference.php @@ -0,0 +1,91 @@ + + */ +class Twig_Node_Expression_BlockReference extends Twig_Node_Expression +{ + /** + * @param Twig_Node|null $template + */ + public function __construct(Twig_NodeInterface $name, $template = null, $lineno, $tag = null) + { + if (is_bool($template)) { + @trigger_error(sprintf('The %s method "$asString" argument is deprecated since version 1.28 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED); + + $template = null; + } + + $nodes = array('name' => $name); + if (null !== $template) { + $nodes['template'] = $template; + } + + parent::__construct($nodes, array('is_defined_test' => false, 'output' => false), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + if ($this->getAttribute('is_defined_test')) { + $this->compileTemplateCall($compiler, 'hasBlock'); + } else { + if ($this->getAttribute('output')) { + $compiler->addDebugInfo($this); + + $this + ->compileTemplateCall($compiler, 'displayBlock') + ->raw(";\n"); + } else { + $this->compileTemplateCall($compiler, 'renderBlock'); + } + } + } + + private function compileTemplateCall(Twig_Compiler $compiler, $method) + { + if (!$this->hasNode('template')) { + $compiler->write('$this'); + } else { + $compiler + ->write('$this->loadTemplate(') + ->subcompile($this->getNode('template')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; + } + + $compiler->raw(sprintf('->%s', $method)); + $this->compileBlockArguments($compiler); + + return $compiler; + } + + private function compileBlockArguments(Twig_Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('name')) + ->raw(', $context'); + + if (!$this->hasNode('template')) { + $compiler->raw(', $blocks'); + } + + return $compiler->raw(')'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Call.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Call.php new file mode 100644 index 0000000..a5c88c7 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Call.php @@ -0,0 +1,289 @@ +hasAttribute('callable') && $callable = $this->getAttribute('callable')) { + if (is_string($callable) && false === strpos($callable, '::')) { + $compiler->raw($callable); + } else { + list($r, $callable) = $this->reflectCallable($callable); + if ($r instanceof ReflectionMethod && is_string($callable[0])) { + if ($r->isStatic()) { + $compiler->raw(sprintf('%s::%s', $callable[0], $callable[1])); + } else { + $compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); + } + } elseif ($r instanceof ReflectionMethod && $callable[0] instanceof Twig_ExtensionInterface) { + $compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', get_class($callable[0]), $callable[1])); + } else { + $type = ucfirst($this->getAttribute('type')); + $compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), array', $type, $this->getAttribute('name'))); + $closingParenthesis = true; + } + } + } else { + $compiler->raw($this->getAttribute('thing')->compile()); + } + + $this->compileArguments($compiler); + + if ($closingParenthesis) { + $compiler->raw(')'); + } + } + + protected function compileArguments(Twig_Compiler $compiler) + { + $compiler->raw('('); + + $first = true; + + if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { + $compiler->raw('$this->env'); + $first = false; + } + + if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->raw('$context'); + $first = false; + } + + if ($this->hasAttribute('arguments')) { + foreach ($this->getAttribute('arguments') as $argument) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->string($argument); + $first = false; + } + } + + if ($this->hasNode('node')) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->subcompile($this->getNode('node')); + $first = false; + } + + if ($this->hasNode('arguments')) { + $callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null; + + $arguments = $this->getArguments($callable, $this->getNode('arguments')); + + foreach ($arguments as $node) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->subcompile($node); + $first = false; + } + } + + $compiler->raw(')'); + } + + protected function getArguments($callable, $arguments) + { + $callType = $this->getAttribute('type'); + $callName = $this->getAttribute('name'); + + $parameters = array(); + $named = false; + foreach ($arguments as $name => $node) { + if (!is_int($name)) { + $named = true; + $name = $this->normalizeName($name); + } elseif ($named) { + throw new Twig_Error_Syntax(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName)); + } + + $parameters[$name] = $node; + } + + $isVariadic = $this->hasAttribute('is_variadic') && $this->getAttribute('is_variadic'); + if (!$named && !$isVariadic) { + return $parameters; + } + + if (!$callable) { + if ($named) { + $message = sprintf('Named arguments are not supported for %s "%s".', $callType, $callName); + } else { + $message = sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName); + } + + throw new LogicException($message); + } + + $callableParameters = $this->getCallableParameters($callable, $isVariadic); + $arguments = array(); + $names = array(); + $missingArguments = array(); + $optionalArguments = array(); + $pos = 0; + foreach ($callableParameters as $callableParameter) { + $names[] = $name = $this->normalizeName($callableParameter->name); + + if (array_key_exists($name, $parameters)) { + if (array_key_exists($pos, $parameters)) { + throw new Twig_Error_Syntax(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName)); + } + + if (count($missingArguments)) { + throw new Twig_Error_Syntax(sprintf( + 'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".', + $name, $callType, $callName, implode(', ', $names), count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments)) + ); + } + + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $parameters[$name]; + unset($parameters[$name]); + $optionalArguments = array(); + } elseif (array_key_exists($pos, $parameters)) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $parameters[$pos]; + unset($parameters[$pos]); + $optionalArguments = array(); + ++$pos; + } elseif ($callableParameter->isDefaultValueAvailable()) { + $optionalArguments[] = new Twig_Node_Expression_Constant($callableParameter->getDefaultValue(), -1); + } elseif ($callableParameter->isOptional()) { + if (empty($parameters)) { + break; + } else { + $missingArguments[] = $name; + } + } else { + throw new Twig_Error_Syntax(sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName)); + } + } + + if ($isVariadic) { + $arbitraryArguments = new Twig_Node_Expression_Array(array(), -1); + foreach ($parameters as $key => $value) { + if (is_int($key)) { + $arbitraryArguments->addElement($value); + } else { + $arbitraryArguments->addElement($value, new Twig_Node_Expression_Constant($key, -1)); + } + unset($parameters[$key]); + } + + if ($arbitraryArguments->count()) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $arbitraryArguments; + } + } + + if (!empty($parameters)) { + $unknownParameter = null; + foreach ($parameters as $parameter) { + if ($parameter instanceof Twig_Node) { + $unknownParameter = $parameter; + break; + } + } + + throw new Twig_Error_Syntax(sprintf( + 'Unknown argument%s "%s" for %s "%s(%s)".', + count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names) + ), $unknownParameter ? $unknownParameter->getTemplateLine() : -1); + } + + return $arguments; + } + + protected function normalizeName($name) + { + return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), $name)); + } + + private function getCallableParameters($callable, $isVariadic) + { + list($r) = $this->reflectCallable($callable); + if (null === $r) { + return array(); + } + + $parameters = $r->getParameters(); + if ($this->hasNode('node')) { + array_shift($parameters); + } + if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { + array_shift($parameters); + } + if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { + array_shift($parameters); + } + if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) { + foreach ($this->getAttribute('arguments') as $argument) { + array_shift($parameters); + } + } + if ($isVariadic) { + $argument = end($parameters); + if ($argument && $argument->isArray() && $argument->isDefaultValueAvailable() && array() === $argument->getDefaultValue()) { + array_pop($parameters); + } else { + $callableName = $r->name; + if ($r instanceof ReflectionMethod) { + $callableName = $r->getDeclaringClass()->name.'::'.$callableName; + } + + throw new LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = array()".', $callableName, $this->getAttribute('type'), $this->getAttribute('name'))); + } + } + + return $parameters; + } + + private function reflectCallable($callable) + { + if (null !== $this->reflector) { + return $this->reflector; + } + + if (is_array($callable)) { + if (!method_exists($callable[0], $callable[1])) { + // __call() + return array(null, array()); + } + $r = new ReflectionMethod($callable[0], $callable[1]); + } elseif (is_object($callable) && !$callable instanceof Closure) { + $r = new ReflectionObject($callable); + $r = $r->getMethod('__invoke'); + $callable = array($callable, '__invoke'); + } elseif (is_string($callable) && false !== $pos = strpos($callable, '::')) { + $class = substr($callable, 0, $pos); + $method = substr($callable, $pos + 2); + if (!method_exists($class, $method)) { + // __staticCall() + return array(null, array()); + } + $r = new ReflectionMethod($callable); + $callable = array($class, $method); + } else { + $r = new ReflectionFunction($callable); + } + + return $this->reflector = array($r, $callable); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Conditional.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Conditional.php new file mode 100644 index 0000000..edcb1e2 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Conditional.php @@ -0,0 +1,31 @@ + $expr1, 'expr2' => $expr2, 'expr3' => $expr3), array(), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('((') + ->subcompile($this->getNode('expr1')) + ->raw(') ? (') + ->subcompile($this->getNode('expr2')) + ->raw(') : (') + ->subcompile($this->getNode('expr3')) + ->raw('))') + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Constant.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Constant.php new file mode 100644 index 0000000..a91dc69 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Constant.php @@ -0,0 +1,23 @@ + $value), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->repr($this->getAttribute('value')); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/ExtensionReference.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/ExtensionReference.php new file mode 100644 index 0000000..b4882e3 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/ExtensionReference.php @@ -0,0 +1,32 @@ + + * + * @deprecated since 1.23 and will be removed in 2.0. + */ +class Twig_Node_Expression_ExtensionReference extends Twig_Node_Expression +{ + public function __construct($name, $lineno, $tag = null) + { + parent::__construct(array(), array('name' => $name), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->raw(sprintf("\$this->env->getExtension('%s')", $this->getAttribute('name'))); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Filter.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Filter.php new file mode 100644 index 0000000..a906232 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Filter.php @@ -0,0 +1,39 @@ + $node, 'filter' => $filterName, 'arguments' => $arguments), array(), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $name = $this->getNode('filter')->getAttribute('value'); + $filter = $compiler->getEnvironment()->getFilter($name); + + $this->setAttribute('name', $name); + $this->setAttribute('type', 'filter'); + $this->setAttribute('thing', $filter); + $this->setAttribute('needs_environment', $filter->needsEnvironment()); + $this->setAttribute('needs_context', $filter->needsContext()); + $this->setAttribute('arguments', $filter->getArguments()); + if ($filter instanceof Twig_FilterCallableInterface || $filter instanceof Twig_SimpleFilter) { + $this->setAttribute('callable', $filter->getCallable()); + } + if ($filter instanceof Twig_SimpleFilter) { + $this->setAttribute('is_variadic', $filter->isVariadic()); + } + + $this->compileCallable($compiler); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Filter/Default.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Filter/Default.php new file mode 100644 index 0000000..5ce172b --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Filter/Default.php @@ -0,0 +1,43 @@ + + * {{ var.foo|default('foo item on var is not defined') }} + * + * + * @author Fabien Potencier + */ +class Twig_Node_Expression_Filter_Default extends Twig_Node_Expression_Filter +{ + public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filterName, Twig_NodeInterface $arguments, $lineno, $tag = null) + { + $default = new Twig_Node_Expression_Filter($node, new Twig_Node_Expression_Constant('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine()); + + if ('default' === $filterName->getAttribute('value') && ($node instanceof Twig_Node_Expression_Name || $node instanceof Twig_Node_Expression_GetAttr)) { + $test = new Twig_Node_Expression_Test_Defined(clone $node, 'defined', new Twig_Node(), $node->getTemplateLine()); + $false = count($arguments) ? $arguments->getNode(0) : new Twig_Node_Expression_Constant('', $node->getTemplateLine()); + + $node = new Twig_Node_Expression_Conditional($test, $default, $false, $node->getTemplateLine()); + } else { + $node = $default; + } + + parent::__construct($node, $filterName, $arguments, $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->subcompile($this->getNode('node')); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Function.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Function.php new file mode 100644 index 0000000..207f655 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Function.php @@ -0,0 +1,43 @@ + $arguments), array('name' => $name, 'is_defined_test' => false), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $name = $this->getAttribute('name'); + $function = $compiler->getEnvironment()->getFunction($name); + + $this->setAttribute('name', $name); + $this->setAttribute('type', 'function'); + $this->setAttribute('thing', $function); + $this->setAttribute('needs_environment', $function->needsEnvironment()); + $this->setAttribute('needs_context', $function->needsContext()); + $this->setAttribute('arguments', $function->getArguments()); + if ($function instanceof Twig_FunctionCallableInterface || $function instanceof Twig_SimpleFunction) { + $callable = $function->getCallable(); + if ('constant' === $name && $this->getAttribute('is_defined_test')) { + $callable = 'twig_constant_is_defined'; + } + + $this->setAttribute('callable', $callable); + } + if ($function instanceof Twig_SimpleFunction) { + $this->setAttribute('is_variadic', $function->isVariadic()); + } + + $this->compileCallable($compiler); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/GetAttr.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/GetAttr.php new file mode 100644 index 0000000..9a8d381 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/GetAttr.php @@ -0,0 +1,68 @@ + $node, 'attribute' => $attribute); + if (null !== $arguments) { + $nodes['arguments'] = $arguments; + } + + parent::__construct($nodes, array('type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'disable_c_ext' => false), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + if (function_exists('twig_template_get_attributes') && !$this->getAttribute('disable_c_ext')) { + $compiler->raw('twig_template_get_attributes($this, '); + } else { + $compiler->raw('$this->getAttribute('); + } + + if ($this->getAttribute('ignore_strict_check')) { + $this->getNode('node')->setAttribute('ignore_strict_check', true); + } + + $compiler->subcompile($this->getNode('node')); + + $compiler->raw(', ')->subcompile($this->getNode('attribute')); + + // only generate optional arguments when needed (to make generated code more readable) + $needFourth = $this->getAttribute('ignore_strict_check'); + $needThird = $needFourth || $this->getAttribute('is_defined_test'); + $needSecond = $needThird || Twig_Template::ANY_CALL !== $this->getAttribute('type'); + $needFirst = $needSecond || $this->hasNode('arguments'); + + if ($needFirst) { + if ($this->hasNode('arguments')) { + $compiler->raw(', ')->subcompile($this->getNode('arguments')); + } else { + $compiler->raw(', array()'); + } + } + + if ($needSecond) { + $compiler->raw(', ')->repr($this->getAttribute('type')); + } + + if ($needThird) { + $compiler->raw(', ')->repr($this->getAttribute('is_defined_test')); + } + + if ($needFourth) { + $compiler->raw(', ')->repr($this->getAttribute('ignore_strict_check')); + } + + $compiler->raw(')'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/MethodCall.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/MethodCall.php new file mode 100644 index 0000000..620b02b --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/MethodCall.php @@ -0,0 +1,41 @@ + $node, 'arguments' => $arguments), array('method' => $method, 'safe' => false), $lineno); + + if ($node instanceof Twig_Node_Expression_Name) { + $node->setAttribute('always_defined', true); + } + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->subcompile($this->getNode('node')) + ->raw('->') + ->raw($this->getAttribute('method')) + ->raw('(') + ; + $first = true; + foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + $compiler->subcompile($pair['value']); + } + $compiler->raw(')'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Name.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Name.php new file mode 100644 index 0000000..d18aaca --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Name.php @@ -0,0 +1,100 @@ + '$this', + '_context' => '$context', + '_charset' => '$this->env->getCharset()', + ); + + public function __construct($name, $lineno) + { + parent::__construct(array(), array('name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $name = $this->getAttribute('name'); + + $compiler->addDebugInfo($this); + + if ($this->getAttribute('is_defined_test')) { + if ($this->isSpecial()) { + $compiler->repr(true); + } else { + $compiler->raw('array_key_exists(')->repr($name)->raw(', $context)'); + } + } elseif ($this->isSpecial()) { + $compiler->raw($this->specialVars[$name]); + } elseif ($this->getAttribute('always_defined')) { + $compiler + ->raw('$context[') + ->string($name) + ->raw(']') + ; + } else { + if (PHP_VERSION_ID >= 70000) { + // use PHP 7 null coalescing operator + $compiler + ->raw('($context[') + ->string($name) + ->raw('] ?? ') + ; + + if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) { + $compiler->raw('null)'); + } else { + $compiler->raw('$this->getContext($context, ')->string($name)->raw('))'); + } + } elseif (PHP_VERSION_ID >= 50400) { + // PHP 5.4 ternary operator performance was optimized + $compiler + ->raw('(isset($context[') + ->string($name) + ->raw(']) ? $context[') + ->string($name) + ->raw('] : ') + ; + + if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) { + $compiler->raw('null)'); + } else { + $compiler->raw('$this->getContext($context, ')->string($name)->raw('))'); + } + } else { + $compiler + ->raw('$this->getContext($context, ') + ->string($name) + ; + + if ($this->getAttribute('ignore_strict_check')) { + $compiler->raw(', true'); + } + + $compiler + ->raw(')') + ; + } + } + } + + public function isSpecial() + { + return isset($this->specialVars[$this->getAttribute('name')]); + } + + public function isSimple() + { + return !$this->isSpecial() && !$this->getAttribute('is_defined_test'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/NullCoalesce.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/NullCoalesce.php new file mode 100644 index 0000000..14f6358 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/NullCoalesce.php @@ -0,0 +1,46 @@ +getTemplateLine()), + new Twig_Node_Expression_Unary_Not(new Twig_Node_Expression_Test_Null($left, 'null', new Twig_Node(), $left->getTemplateLine()), $left->getTemplateLine()), + $left->getTemplateLine() + ); + + parent::__construct($test, $left, $right, $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + /* + * This optimizes only one case. PHP 7 also supports more complex expressions + * that can return null. So, for instance, if log is defined, log("foo") ?? "..." works, + * but log($a["foo"]) ?? "..." does not if $a["foo"] is not defined. More advanced + * cases might be implemented as an optimizer node visitor, but has not been done + * as benefits are probably not worth the added complexity. + */ + if (PHP_VERSION_ID >= 70000 && $this->getNode('expr2') instanceof Twig_Node_Expression_Name) { + $this->getNode('expr2')->setAttribute('always_defined', true); + $compiler + ->raw('((') + ->subcompile($this->getNode('expr2')) + ->raw(') ?? (') + ->subcompile($this->getNode('expr3')) + ->raw('))') + ; + } else { + parent::compile($compiler); + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Parent.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Parent.php new file mode 100644 index 0000000..694c080 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Parent.php @@ -0,0 +1,42 @@ + + */ +class Twig_Node_Expression_Parent extends Twig_Node_Expression +{ + public function __construct($name, $lineno, $tag = null) + { + parent::__construct(array(), array('output' => false, 'name' => $name), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + if ($this->getAttribute('output')) { + $compiler + ->addDebugInfo($this) + ->write('$this->displayParentBlock(') + ->string($this->getAttribute('name')) + ->raw(", \$context, \$blocks);\n") + ; + } else { + $compiler + ->raw('$this->renderParentBlock(') + ->string($this->getAttribute('name')) + ->raw(', $context, $blocks)') + ; + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/TempName.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/TempName.php new file mode 100644 index 0000000..e6b058e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/TempName.php @@ -0,0 +1,26 @@ + $name), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('$_') + ->raw($this->getAttribute('name')) + ->raw('_') + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Test.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test.php new file mode 100644 index 0000000..125d381 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test.php @@ -0,0 +1,40 @@ + $node); + if (null !== $arguments) { + $nodes['arguments'] = $arguments; + } + + parent::__construct($nodes, array('name' => $name), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $name = $this->getAttribute('name'); + $test = $compiler->getEnvironment()->getTest($name); + + $this->setAttribute('name', $name); + $this->setAttribute('type', 'test'); + $this->setAttribute('thing', $test); + if ($test instanceof Twig_TestCallableInterface || $test instanceof Twig_SimpleTest) { + $this->setAttribute('callable', $test->getCallable()); + } + if ($test instanceof Twig_SimpleTest) { + $this->setAttribute('is_variadic', $test->isVariadic()); + } + + $this->compileCallable($compiler); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Constant.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Constant.php new file mode 100644 index 0000000..de55f5f --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Constant.php @@ -0,0 +1,46 @@ + + * {% if post.status is constant('Post::PUBLISHED') %} + * the status attribute is exactly the same as Post::PUBLISHED + * {% endif %} + * + * + * @author Fabien Potencier + */ +class Twig_Node_Expression_Test_Constant extends Twig_Node_Expression_Test +{ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' === constant(') + ; + + if ($this->getNode('arguments')->hasNode(1)) { + $compiler + ->raw('get_class(') + ->subcompile($this->getNode('arguments')->getNode(1)) + ->raw(')."::".') + ; + } + + $compiler + ->subcompile($this->getNode('arguments')->getNode(0)) + ->raw('))') + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Defined.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Defined.php new file mode 100644 index 0000000..9d2d7ca --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Defined.php @@ -0,0 +1,59 @@ + + * {# defined works with variable names and variable attributes #} + * {% if foo is defined %} + * {# ... #} + * {% endif %} + * + * + * @author Fabien Potencier + */ +class Twig_Node_Expression_Test_Defined extends Twig_Node_Expression_Test +{ + public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno) + { + if ($node instanceof Twig_Node_Expression_Name) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof Twig_Node_Expression_GetAttr) { + $node->setAttribute('is_defined_test', true); + $this->changeIgnoreStrictCheck($node); + } elseif ($node instanceof Twig_Node_Expression_BlockReference) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof Twig_Node_Expression_Function && 'constant' === $node->getAttribute('name')) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array) { + $node = new Twig_Node_Expression_Constant(true, $node->getTemplateLine()); + } else { + throw new Twig_Error_Syntax('The "defined" test only works with simple variables.', $this->getTemplateLine()); + } + + parent::__construct($node, $name, $arguments, $lineno); + } + + protected function changeIgnoreStrictCheck(Twig_Node_Expression_GetAttr $node) + { + $node->setAttribute('ignore_strict_check', true); + + if ($node->getNode('node') instanceof Twig_Node_Expression_GetAttr) { + $this->changeIgnoreStrictCheck($node->getNode('node')); + } + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->subcompile($this->getNode('node')); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php new file mode 100644 index 0000000..d5bed23 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php @@ -0,0 +1,33 @@ + + * {% if loop.index is divisible by(3) %} + * + * + * @author Fabien Potencier + */ +class Twig_Node_Expression_Test_Divisibleby extends Twig_Node_Expression_Test +{ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('(0 == ') + ->subcompile($this->getNode('node')) + ->raw(' % ') + ->subcompile($this->getNode('arguments')->getNode(0)) + ->raw(')') + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Even.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Even.php new file mode 100644 index 0000000..d7853e8 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Even.php @@ -0,0 +1,32 @@ + + * {{ var is even }} + * + * + * @author Fabien Potencier + */ +class Twig_Node_Expression_Test_Even extends Twig_Node_Expression_Test +{ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' % 2 == 0') + ->raw(')') + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Null.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Null.php new file mode 100644 index 0000000..1c83825 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Null.php @@ -0,0 +1,31 @@ + + * {{ var is none }} + * + * + * @author Fabien Potencier + */ +class Twig_Node_Expression_Test_Null extends Twig_Node_Expression_Test +{ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('(null === ') + ->subcompile($this->getNode('node')) + ->raw(')') + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Odd.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Odd.php new file mode 100644 index 0000000..421c19e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Odd.php @@ -0,0 +1,32 @@ + + * {{ var is odd }} + * + * + * @author Fabien Potencier + */ +class Twig_Node_Expression_Test_Odd extends Twig_Node_Expression_Test +{ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' % 2 == 1') + ->raw(')') + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Sameas.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Sameas.php new file mode 100644 index 0000000..b48905e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Test/Sameas.php @@ -0,0 +1,29 @@ + + */ +class Twig_Node_Expression_Test_Sameas extends Twig_Node_Expression_Test +{ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' === ') + ->subcompile($this->getNode('arguments')->getNode(0)) + ->raw(')') + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Unary.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Unary.php new file mode 100644 index 0000000..1cf54c3 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Unary.php @@ -0,0 +1,27 @@ + $node), array(), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->raw(' '); + $this->operator($compiler); + $compiler->subcompile($this->getNode('node')); + } + + abstract public function operator(Twig_Compiler $compiler); +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Unary/Neg.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Unary/Neg.php new file mode 100644 index 0000000..2a3937e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Unary/Neg.php @@ -0,0 +1,18 @@ +raw('-'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Unary/Not.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Unary/Not.php new file mode 100644 index 0000000..f94073c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Unary/Not.php @@ -0,0 +1,18 @@ +raw('!'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Expression/Unary/Pos.php b/src/vendor/twig/twig/lib/Twig/Node/Expression/Unary/Pos.php new file mode 100644 index 0000000..04edb52 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Expression/Unary/Pos.php @@ -0,0 +1,18 @@ +raw('+'); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Flush.php b/src/vendor/twig/twig/lib/Twig/Node/Flush.php new file mode 100644 index 0000000..2af17a4 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Flush.php @@ -0,0 +1,31 @@ + + */ +class Twig_Node_Flush extends Twig_Node +{ + public function __construct($lineno, $tag) + { + parent::__construct(array(), array(), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write("flush();\n") + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/For.php b/src/vendor/twig/twig/lib/Twig/Node/For.php new file mode 100644 index 0000000..5498a88 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/For.php @@ -0,0 +1,111 @@ + + */ +class Twig_Node_For extends Twig_Node +{ + protected $loop; + + public function __construct(Twig_Node_Expression_AssignName $keyTarget, Twig_Node_Expression_AssignName $valueTarget, Twig_Node_Expression $seq, Twig_Node_Expression $ifexpr = null, Twig_NodeInterface $body, Twig_NodeInterface $else = null, $lineno, $tag = null) + { + $body = new Twig_Node(array($body, $this->loop = new Twig_Node_ForLoop($lineno, $tag))); + + if (null !== $ifexpr) { + $body = new Twig_Node_If(new Twig_Node(array($ifexpr, $body)), null, $lineno, $tag); + } + + $nodes = array('key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body); + if (null !== $else) { + $nodes['else'] = $else; + } + + parent::__construct($nodes, array('with_loop' => true, 'ifexpr' => null !== $ifexpr), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write("\$context['_parent'] = \$context;\n") + ->write("\$context['_seq'] = twig_ensure_traversable(") + ->subcompile($this->getNode('seq')) + ->raw(");\n") + ; + + if ($this->hasNode('else')) { + $compiler->write("\$context['_iterated'] = false;\n"); + } + + if ($this->getAttribute('with_loop')) { + $compiler + ->write("\$context['loop'] = array(\n") + ->write(" 'parent' => \$context['_parent'],\n") + ->write(" 'index0' => 0,\n") + ->write(" 'index' => 1,\n") + ->write(" 'first' => true,\n") + ->write(");\n") + ; + + if (!$this->getAttribute('ifexpr')) { + $compiler + ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {\n") + ->indent() + ->write("\$length = count(\$context['_seq']);\n") + ->write("\$context['loop']['revindex0'] = \$length - 1;\n") + ->write("\$context['loop']['revindex'] = \$length;\n") + ->write("\$context['loop']['length'] = \$length;\n") + ->write("\$context['loop']['last'] = 1 === \$length;\n") + ->outdent() + ->write("}\n") + ; + } + } + + $this->loop->setAttribute('else', $this->hasNode('else')); + $this->loop->setAttribute('with_loop', $this->getAttribute('with_loop')); + $this->loop->setAttribute('ifexpr', $this->getAttribute('ifexpr')); + + $compiler + ->write("foreach (\$context['_seq'] as ") + ->subcompile($this->getNode('key_target')) + ->raw(' => ') + ->subcompile($this->getNode('value_target')) + ->raw(") {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->outdent() + ->write("}\n") + ; + + if ($this->hasNode('else')) { + $compiler + ->write("if (!\$context['_iterated']) {\n") + ->indent() + ->subcompile($this->getNode('else')) + ->outdent() + ->write("}\n") + ; + } + + $compiler->write("\$_parent = \$context['_parent'];\n"); + + // remove some "private" loop variables (needed for nested loops) + $compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n"); + + // keep the values set in the inner context for variables defined in the outer context + $compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n"); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/ForLoop.php b/src/vendor/twig/twig/lib/Twig/Node/ForLoop.php new file mode 100644 index 0000000..2554d48 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/ForLoop.php @@ -0,0 +1,50 @@ + + */ +class Twig_Node_ForLoop extends Twig_Node +{ + public function __construct($lineno, $tag = null) + { + parent::__construct(array(), array('with_loop' => false, 'ifexpr' => false, 'else' => false), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + if ($this->getAttribute('else')) { + $compiler->write("\$context['_iterated'] = true;\n"); + } + + if ($this->getAttribute('with_loop')) { + $compiler + ->write("++\$context['loop']['index0'];\n") + ->write("++\$context['loop']['index'];\n") + ->write("\$context['loop']['first'] = false;\n") + ; + + if (!$this->getAttribute('ifexpr')) { + $compiler + ->write("if (isset(\$context['loop']['length'])) {\n") + ->indent() + ->write("--\$context['loop']['revindex0'];\n") + ->write("--\$context['loop']['revindex'];\n") + ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n") + ->outdent() + ->write("}\n") + ; + } + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/If.php b/src/vendor/twig/twig/lib/Twig/Node/If.php new file mode 100644 index 0000000..0aa9e70 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/If.php @@ -0,0 +1,66 @@ + + */ +class Twig_Node_If extends Twig_Node +{ + public function __construct(Twig_NodeInterface $tests, Twig_NodeInterface $else = null, $lineno, $tag = null) + { + $nodes = array('tests' => $tests); + if (null !== $else) { + $nodes['else'] = $else; + } + + parent::__construct($nodes, array(), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + for ($i = 0, $count = count($this->getNode('tests')); $i < $count; $i += 2) { + if ($i > 0) { + $compiler + ->outdent() + ->write('} elseif (') + ; + } else { + $compiler + ->write('if (') + ; + } + + $compiler + ->subcompile($this->getNode('tests')->getNode($i)) + ->raw(") {\n") + ->indent() + ->subcompile($this->getNode('tests')->getNode($i + 1)) + ; + } + + if ($this->hasNode('else')) { + $compiler + ->outdent() + ->write("} else {\n") + ->indent() + ->subcompile($this->getNode('else')) + ; + } + + $compiler + ->outdent() + ->write("}\n"); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Import.php b/src/vendor/twig/twig/lib/Twig/Node/Import.php new file mode 100644 index 0000000..507fb66 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Import.php @@ -0,0 +1,49 @@ + + */ +class Twig_Node_Import extends Twig_Node +{ + public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $var, $lineno, $tag = null) + { + parent::__construct(array('expr' => $expr, 'var' => $var), array(), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('') + ->subcompile($this->getNode('var')) + ->raw(' = ') + ; + + if ($this->getNode('expr') instanceof Twig_Node_Expression_Name && '_self' === $this->getNode('expr')->getAttribute('name')) { + $compiler->raw('$this'); + } else { + $compiler + ->raw('$this->loadTemplate(') + ->subcompile($this->getNode('expr')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; + } + + $compiler->raw(";\n"); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Include.php b/src/vendor/twig/twig/lib/Twig/Node/Include.php new file mode 100644 index 0000000..714c991 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Include.php @@ -0,0 +1,88 @@ + + */ +class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface +{ + public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) + { + $nodes = array('expr' => $expr); + if (null !== $variables) { + $nodes['variables'] = $variables; + } + + parent::__construct($nodes, array('only' => (bool) $only, 'ignore_missing' => (bool) $ignoreMissing), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + if ($this->getAttribute('ignore_missing')) { + $compiler + ->write("try {\n") + ->indent() + ; + } + + $this->addGetTemplate($compiler); + + $compiler->raw('->display('); + + $this->addTemplateArguments($compiler); + + $compiler->raw(");\n"); + + if ($this->getAttribute('ignore_missing')) { + $compiler + ->outdent() + ->write("} catch (Twig_Error_Loader \$e) {\n") + ->indent() + ->write("// ignore missing template\n") + ->outdent() + ->write("}\n\n") + ; + } + } + + protected function addGetTemplate(Twig_Compiler $compiler) + { + $compiler + ->write('$this->loadTemplate(') + ->subcompile($this->getNode('expr')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; + } + + protected function addTemplateArguments(Twig_Compiler $compiler) + { + if (!$this->hasNode('variables')) { + $compiler->raw(false === $this->getAttribute('only') ? '$context' : 'array()'); + } elseif (false === $this->getAttribute('only')) { + $compiler + ->raw('array_merge($context, ') + ->subcompile($this->getNode('variables')) + ->raw(')') + ; + } else { + $compiler->subcompile($this->getNode('variables')); + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Macro.php b/src/vendor/twig/twig/lib/Twig/Node/Macro.php new file mode 100644 index 0000000..2d5226a --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Macro.php @@ -0,0 +1,123 @@ + + */ +class Twig_Node_Macro extends Twig_Node +{ + const VARARGS_NAME = 'varargs'; + + public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null) + { + foreach ($arguments as $argumentName => $argument) { + if (self::VARARGS_NAME === $argumentName) { + throw new Twig_Error_Syntax(sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getTemplateLine()); + } + } + + parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write(sprintf('public function get%s(', $this->getAttribute('name'))) + ; + + $count = count($this->getNode('arguments')); + $pos = 0; + foreach ($this->getNode('arguments') as $name => $default) { + $compiler + ->raw('$__'.$name.'__ = ') + ->subcompile($default) + ; + + if (++$pos < $count) { + $compiler->raw(', '); + } + } + + if (PHP_VERSION_ID >= 50600) { + if ($count) { + $compiler->raw(', '); + } + + $compiler->raw('...$__varargs__'); + } + + $compiler + ->raw(")\n") + ->write("{\n") + ->indent() + ; + + $compiler + ->write("\$context = \$this->env->mergeGlobals(array(\n") + ->indent() + ; + + foreach ($this->getNode('arguments') as $name => $default) { + $compiler + ->write('') + ->string($name) + ->raw(' => $__'.$name.'__') + ->raw(",\n") + ; + } + + $compiler + ->write('') + ->string(self::VARARGS_NAME) + ->raw(' => ') + ; + + if (PHP_VERSION_ID >= 50600) { + $compiler->raw("\$__varargs__,\n"); + } else { + $compiler + ->raw('func_num_args() > ') + ->repr($count) + ->raw(' ? array_slice(func_get_args(), ') + ->repr($count) + ->raw(") : array(),\n") + ; + } + + $compiler + ->outdent() + ->write("));\n\n") + ->write("\$blocks = array();\n\n") + ->write("ob_start();\n") + ->write("try {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->outdent() + ->write("} catch (Exception \$e) {\n") + ->indent() + ->write("ob_end_clean();\n\n") + ->write("throw \$e;\n") + ->outdent() + ->write("} catch (Throwable \$e) {\n") + ->indent() + ->write("ob_end_clean();\n\n") + ->write("throw \$e;\n") + ->outdent() + ->write("}\n\n") + ->write("return ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset());\n") + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Module.php b/src/vendor/twig/twig/lib/Twig/Node/Module.php new file mode 100644 index 0000000..fe7da34 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Module.php @@ -0,0 +1,459 @@ + + */ +class Twig_Node_Module extends Twig_Node +{ + private $source; + + public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $embeddedTemplates, $name, $source = '') + { + if (!$name instanceof Twig_Source) { + @trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); + $this->source = new Twig_Source($source, $name); + } else { + $this->source = $name; + } + + $nodes = array( + 'body' => $body, + 'blocks' => $blocks, + 'macros' => $macros, + 'traits' => $traits, + 'display_start' => new Twig_Node(), + 'display_end' => new Twig_Node(), + 'constructor_start' => new Twig_Node(), + 'constructor_end' => new Twig_Node(), + 'class_end' => new Twig_Node(), + ); + if (null !== $parent) { + $nodes['parent'] = $parent; + } + + // embedded templates are set as attributes so that they are only visited once by the visitors + parent::__construct($nodes, array( + // source to be remove in 2.0 + 'source' => $this->source->getCode(), + // filename to be remove in 2.0 (use getTemplateName() instead) + 'filename' => $this->source->getName(), + 'index' => null, + 'embedded_templates' => $embeddedTemplates, + ), 1); + + // populate the template name of all node children + $this->setTemplateName($this->source->getName()); + } + + public function setIndex($index) + { + $this->setAttribute('index', $index); + } + + public function compile(Twig_Compiler $compiler) + { + $this->compileTemplate($compiler); + + foreach ($this->getAttribute('embedded_templates') as $template) { + $compiler->subcompile($template); + } + } + + protected function compileTemplate(Twig_Compiler $compiler) + { + if (!$this->getAttribute('index')) { + $compiler->write('compileClassHeader($compiler); + + if ( + count($this->getNode('blocks')) + || count($this->getNode('traits')) + || !$this->hasNode('parent') + || $this->getNode('parent') instanceof Twig_Node_Expression_Constant + || count($this->getNode('constructor_start')) + || count($this->getNode('constructor_end')) + ) { + $this->compileConstructor($compiler); + } + + $this->compileGetParent($compiler); + + $this->compileDisplay($compiler); + + $compiler->subcompile($this->getNode('blocks')); + + $this->compileMacros($compiler); + + $this->compileGetTemplateName($compiler); + + $this->compileIsTraitable($compiler); + + $this->compileDebugInfo($compiler); + + $this->compileGetSource($compiler); + + $this->compileGetSourceContext($compiler); + + $this->compileClassFooter($compiler); + } + + protected function compileGetParent(Twig_Compiler $compiler) + { + if (!$this->hasNode('parent')) { + return; + } + $parent = $this->getNode('parent'); + + $compiler + ->write("protected function doGetParent(array \$context)\n", "{\n") + ->indent() + ->addDebugInfo($parent) + ->write('return ') + ; + + if ($parent instanceof Twig_Node_Expression_Constant) { + $compiler->subcompile($parent); + } else { + $compiler + ->raw('$this->loadTemplate(') + ->subcompile($parent) + ->raw(', ') + ->repr($this->source->getName()) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(')') + ; + } + + $compiler + ->raw(";\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileClassHeader(Twig_Compiler $compiler) + { + $compiler + ->write("\n\n") + // if the template name contains */, add a blank to avoid a PHP parse error + ->write('/* '.str_replace('*/', '* /', $this->source->getName())." */\n") + ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->source->getName(), $this->getAttribute('index'))) + ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass())) + ->write("{\n") + ->indent() + ; + } + + protected function compileConstructor(Twig_Compiler $compiler) + { + $compiler + ->write("public function __construct(Twig_Environment \$env)\n", "{\n") + ->indent() + ->subcompile($this->getNode('constructor_start')) + ->write("parent::__construct(\$env);\n\n") + ; + + // parent + if (!$this->hasNode('parent')) { + $compiler->write("\$this->parent = false;\n\n"); + } elseif (($parent = $this->getNode('parent')) && $parent instanceof Twig_Node_Expression_Constant) { + $compiler + ->addDebugInfo($parent) + ->write('$this->parent = $this->loadTemplate(') + ->subcompile($parent) + ->raw(', ') + ->repr($this->source->getName()) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(");\n") + ; + } + + $countTraits = count($this->getNode('traits')); + if ($countTraits) { + // traits + foreach ($this->getNode('traits') as $i => $trait) { + $this->compileLoadTemplate($compiler, $trait->getNode('template'), sprintf('$_trait_%s', $i)); + + $compiler + ->addDebugInfo($trait->getNode('template')) + ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i)) + ->indent() + ->write("throw new Twig_Error_Runtime('Template \"'.") + ->subcompile($trait->getNode('template')) + ->raw(".'\" cannot be used as a trait.');\n") + ->outdent() + ->write("}\n") + ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i)) + ; + + foreach ($trait->getNode('targets') as $key => $value) { + $compiler + ->write(sprintf('if (!isset($_trait_%s_blocks[', $i)) + ->string($key) + ->raw("])) {\n") + ->indent() + ->write("throw new Twig_Error_Runtime(sprintf('Block ") + ->string($key) + ->raw(' is not defined in trait ') + ->subcompile($trait->getNode('template')) + ->raw(".'));\n") + ->outdent() + ->write("}\n\n") + + ->write(sprintf('$_trait_%s_blocks[', $i)) + ->subcompile($value) + ->raw(sprintf('] = $_trait_%s_blocks[', $i)) + ->string($key) + ->raw(sprintf(']; unset($_trait_%s_blocks[', $i)) + ->string($key) + ->raw("]);\n\n") + ; + } + } + + if ($countTraits > 1) { + $compiler + ->write("\$this->traits = array_merge(\n") + ->indent() + ; + + for ($i = 0; $i < $countTraits; ++$i) { + $compiler + ->write(sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i)) + ; + } + + $compiler + ->outdent() + ->write(");\n\n") + ; + } else { + $compiler + ->write("\$this->traits = \$_trait_0_blocks;\n\n") + ; + } + + $compiler + ->write("\$this->blocks = array_merge(\n") + ->indent() + ->write("\$this->traits,\n") + ->write("array(\n") + ; + } else { + $compiler + ->write("\$this->blocks = array(\n") + ; + } + + // blocks + $compiler + ->indent() + ; + + foreach ($this->getNode('blocks') as $name => $node) { + $compiler + ->write(sprintf("'%s' => array(\$this, 'block_%s'),\n", $name, $name)) + ; + } + + if ($countTraits) { + $compiler + ->outdent() + ->write(")\n") + ; + } + + $compiler + ->outdent() + ->write(");\n") + ->outdent() + ->subcompile($this->getNode('constructor_end')) + ->write("}\n\n") + ; + } + + protected function compileDisplay(Twig_Compiler $compiler) + { + $compiler + ->write("protected function doDisplay(array \$context, array \$blocks = array())\n", "{\n") + ->indent() + ->subcompile($this->getNode('display_start')) + ->subcompile($this->getNode('body')) + ; + + if ($this->hasNode('parent')) { + $parent = $this->getNode('parent'); + $compiler->addDebugInfo($parent); + if ($parent instanceof Twig_Node_Expression_Constant) { + $compiler->write('$this->parent'); + } else { + $compiler->write('$this->getParent($context)'); + } + $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n"); + } + + $compiler + ->subcompile($this->getNode('display_end')) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileClassFooter(Twig_Compiler $compiler) + { + $compiler + ->subcompile($this->getNode('class_end')) + ->outdent() + ->write("}\n") + ; + } + + protected function compileMacros(Twig_Compiler $compiler) + { + $compiler->subcompile($this->getNode('macros')); + } + + protected function compileGetTemplateName(Twig_Compiler $compiler) + { + $compiler + ->write("public function getTemplateName()\n", "{\n") + ->indent() + ->write('return ') + ->repr($this->source->getName()) + ->raw(";\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileIsTraitable(Twig_Compiler $compiler) + { + // A template can be used as a trait if: + // * it has no parent + // * it has no macros + // * it has no body + // + // Put another way, a template can be used as a trait if it + // only contains blocks and use statements. + $traitable = !$this->hasNode('parent') && 0 === count($this->getNode('macros')); + if ($traitable) { + if ($this->getNode('body') instanceof Twig_Node_Body) { + $nodes = $this->getNode('body')->getNode(0); + } else { + $nodes = $this->getNode('body'); + } + + if (!count($nodes)) { + $nodes = new Twig_Node(array($nodes)); + } + + foreach ($nodes as $node) { + if (!count($node)) { + continue; + } + + if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) { + continue; + } + + if ($node instanceof Twig_Node_BlockReference) { + continue; + } + + $traitable = false; + break; + } + } + + if ($traitable) { + return; + } + + $compiler + ->write("public function isTraitable()\n", "{\n") + ->indent() + ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false')) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileDebugInfo(Twig_Compiler $compiler) + { + $compiler + ->write("public function getDebugInfo()\n", "{\n") + ->indent() + ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true)))) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileGetSource(Twig_Compiler $compiler) + { + $compiler + ->write("/** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */\n") + ->write("public function getSource()\n", "{\n") + ->indent() + ->write("@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED);\n\n") + ->write('return $this->getSourceContext()->getCode();') + ->raw("\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileGetSourceContext(Twig_Compiler $compiler) + { + $compiler + ->write("public function getSourceContext()\n", "{\n") + ->indent() + ->write('return new Twig_Source(') + ->string($compiler->getEnvironment()->isDebug() ? $this->source->getCode() : '') + ->raw(', ') + ->string($this->source->getName()) + ->raw(', ') + ->string($this->source->getPath()) + ->raw(");\n") + ->outdent() + ->write("}\n") + ; + } + + protected function compileLoadTemplate(Twig_Compiler $compiler, $node, $var) + { + if ($node instanceof Twig_Node_Expression_Constant) { + $compiler + ->write(sprintf('%s = $this->loadTemplate(', $var)) + ->subcompile($node) + ->raw(', ') + ->repr($node->getTemplateName()) + ->raw(', ') + ->repr($node->getTemplateLine()) + ->raw(");\n") + ; + } else { + throw new LogicException('Trait templates can only be constant nodes.'); + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Print.php b/src/vendor/twig/twig/lib/Twig/Node/Print.php new file mode 100644 index 0000000..7b69ee8 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Print.php @@ -0,0 +1,34 @@ + + */ +class Twig_Node_Print extends Twig_Node implements Twig_NodeOutputInterface +{ + public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null) + { + parent::__construct(array('expr' => $expr), array(), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('echo ') + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Sandbox.php b/src/vendor/twig/twig/lib/Twig/Node/Sandbox.php new file mode 100644 index 0000000..4c8f8fa --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Sandbox.php @@ -0,0 +1,42 @@ + + */ +class Twig_Node_Sandbox extends Twig_Node +{ + public function __construct(Twig_NodeInterface $body, $lineno, $tag = null) + { + parent::__construct(array('body' => $body), array(), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write("\$sandbox = \$this->env->getExtension('Twig_Extension_Sandbox');\n") + ->write("if (!\$alreadySandboxed = \$sandbox->isSandboxed()) {\n") + ->indent() + ->write("\$sandbox->enableSandbox();\n") + ->outdent() + ->write("}\n") + ->subcompile($this->getNode('body')) + ->write("if (!\$alreadySandboxed) {\n") + ->indent() + ->write("\$sandbox->disableSandbox();\n") + ->outdent() + ->write("}\n") + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/SandboxedPrint.php b/src/vendor/twig/twig/lib/Twig/Node/SandboxedPrint.php new file mode 100644 index 0000000..78cc45a --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/SandboxedPrint.php @@ -0,0 +1,49 @@ + + */ +class Twig_Node_SandboxedPrint extends Twig_Node_Print +{ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('echo $this->env->getExtension(\'Twig_Extension_Sandbox\')->ensureToStringAllowed(') + ->subcompile($this->getNode('expr')) + ->raw(");\n") + ; + } + + /** + * Removes node filters. + * + * This is mostly needed when another visitor adds filters (like the escaper one). + * + * @return Twig_Node + */ + protected function removeNodeFilter(Twig_Node $node) + { + if ($node instanceof Twig_Node_Expression_Filter) { + return $this->removeNodeFilter($node->getNode('node')); + } + + return $node; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Set.php b/src/vendor/twig/twig/lib/Twig/Node/Set.php new file mode 100644 index 0000000..b77b8b6 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Set.php @@ -0,0 +1,96 @@ + + */ +class Twig_Node_Set extends Twig_Node +{ + public function __construct($capture, Twig_NodeInterface $names, Twig_NodeInterface $values, $lineno, $tag = null) + { + parent::__construct(array('names' => $names, 'values' => $values), array('capture' => $capture, 'safe' => false), $lineno, $tag); + + /* + * Optimizes the node when capture is used for a large block of text. + * + * {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig_Markup("foo"); + */ + if ($this->getAttribute('capture')) { + $this->setAttribute('safe', true); + + $values = $this->getNode('values'); + if ($values instanceof Twig_Node_Text) { + $this->setNode('values', new Twig_Node_Expression_Constant($values->getAttribute('data'), $values->getTemplateLine())); + $this->setAttribute('capture', false); + } + } + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + if (count($this->getNode('names')) > 1) { + $compiler->write('list('); + foreach ($this->getNode('names') as $idx => $node) { + if ($idx) { + $compiler->raw(', '); + } + + $compiler->subcompile($node); + } + $compiler->raw(')'); + } else { + if ($this->getAttribute('capture')) { + $compiler + ->write("ob_start();\n") + ->subcompile($this->getNode('values')) + ; + } + + $compiler->subcompile($this->getNode('names'), false); + + if ($this->getAttribute('capture')) { + $compiler->raw(" = ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset())"); + } + } + + if (!$this->getAttribute('capture')) { + $compiler->raw(' = '); + + if (count($this->getNode('names')) > 1) { + $compiler->write('array('); + foreach ($this->getNode('values') as $idx => $value) { + if ($idx) { + $compiler->raw(', '); + } + + $compiler->subcompile($value); + } + $compiler->raw(')'); + } else { + if ($this->getAttribute('safe')) { + $compiler + ->raw("('' === \$tmp = ") + ->subcompile($this->getNode('values')) + ->raw(") ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset())") + ; + } else { + $compiler->subcompile($this->getNode('values')); + } + } + } + + $compiler->raw(";\n"); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/SetTemp.php b/src/vendor/twig/twig/lib/Twig/Node/SetTemp.php new file mode 100644 index 0000000..3bdd1cb --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/SetTemp.php @@ -0,0 +1,35 @@ + $name), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $name = $this->getAttribute('name'); + $compiler + ->addDebugInfo($this) + ->write('if (isset($context[') + ->string($name) + ->raw('])) { $_') + ->raw($name) + ->raw('_ = $context[') + ->repr($name) + ->raw(']; } else { $_') + ->raw($name) + ->raw("_ = null; }\n") + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Spaceless.php b/src/vendor/twig/twig/lib/Twig/Node/Spaceless.php new file mode 100644 index 0000000..486e461 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Spaceless.php @@ -0,0 +1,35 @@ + + */ +class Twig_Node_Spaceless extends Twig_Node +{ + public function __construct(Twig_NodeInterface $body, $lineno, $tag = 'spaceless') + { + parent::__construct(array('body' => $body), array(), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write("ob_start();\n") + ->subcompile($this->getNode('body')) + ->write("echo trim(preg_replace('/>\s+<', ob_get_clean()));\n") + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/Text.php b/src/vendor/twig/twig/lib/Twig/Node/Text.php new file mode 100644 index 0000000..39879bb --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/Text.php @@ -0,0 +1,34 @@ + + */ +class Twig_Node_Text extends Twig_Node implements Twig_NodeOutputInterface +{ + public function __construct($data, $lineno) + { + parent::__construct(array(), array('data' => $data), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('echo ') + ->string($this->getAttribute('data')) + ->raw(";\n") + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Node/With.php b/src/vendor/twig/twig/lib/Twig/Node/With.php new file mode 100644 index 0000000..17953e8 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Node/With.php @@ -0,0 +1,62 @@ + + */ +class Twig_Node_With extends Twig_Node +{ + public function __construct(Twig_Node $body, Twig_Node $variables = null, $only = false, $lineno, $tag = null) + { + $nodes = array('body' => $body); + if (null !== $variables) { + $nodes['variables'] = $variables; + } + + parent::__construct($nodes, array('only' => (bool) $only), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + if ($this->hasNode('variables')) { + $varsName = $compiler->getVarName(); + $compiler + ->write(sprintf('$%s = ', $varsName)) + ->subcompile($this->getNode('variables')) + ->raw(";\n") + ->write(sprintf("if (!is_array(\$%s)) {\n", $varsName)) + ->indent() + ->write("throw new Twig_Error_Runtime('Variables passed to the \"with\" tag must be a hash.');\n") + ->outdent() + ->write("}\n") + ; + + if ($this->getAttribute('only')) { + $compiler->write("\$context = array('_parent' => \$context);\n"); + } else { + $compiler->write("\$context['_parent'] = \$context;\n"); + } + + $compiler->write(sprintf("\$context = array_merge(\$context, \$%s);\n", $varsName)); + } else { + $compiler->write("\$context['_parent'] = \$context;\n"); + } + + $compiler + ->subcompile($this->getNode('body')) + ->write("\$context = \$context['_parent'];\n") + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/NodeInterface.php b/src/vendor/twig/twig/lib/Twig/NodeInterface.php new file mode 100644 index 0000000..80d9993 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/NodeInterface.php @@ -0,0 +1,32 @@ + + * + * @deprecated since 1.12 (to be removed in 3.0) + */ +interface Twig_NodeInterface extends Countable, IteratorAggregate +{ + /** + * Compiles the node to PHP. + */ + public function compile(Twig_Compiler $compiler); + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function getLine(); + + public function getNodeTag(); +} diff --git a/src/vendor/twig/twig/lib/Twig/NodeOutputInterface.php b/src/vendor/twig/twig/lib/Twig/NodeOutputInterface.php new file mode 100644 index 0000000..22172c0 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/NodeOutputInterface.php @@ -0,0 +1,19 @@ + + */ +interface Twig_NodeOutputInterface +{ +} diff --git a/src/vendor/twig/twig/lib/Twig/NodeTraverser.php b/src/vendor/twig/twig/lib/Twig/NodeTraverser.php new file mode 100644 index 0000000..5b05cd6 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/NodeTraverser.php @@ -0,0 +1,82 @@ + + */ +class Twig_NodeTraverser +{ + protected $env; + protected $visitors = array(); + + /** + * @param Twig_Environment $env + * @param Twig_NodeVisitorInterface[] $visitors + */ + public function __construct(Twig_Environment $env, array $visitors = array()) + { + $this->env = $env; + foreach ($visitors as $visitor) { + $this->addVisitor($visitor); + } + } + + public function addVisitor(Twig_NodeVisitorInterface $visitor) + { + if (!isset($this->visitors[$visitor->getPriority()])) { + $this->visitors[$visitor->getPriority()] = array(); + } + + $this->visitors[$visitor->getPriority()][] = $visitor; + } + + /** + * Traverses a node and calls the registered visitors. + * + * @return Twig_NodeInterface + */ + public function traverse(Twig_NodeInterface $node) + { + ksort($this->visitors); + foreach ($this->visitors as $visitors) { + foreach ($visitors as $visitor) { + $node = $this->traverseForVisitor($visitor, $node); + } + } + + return $node; + } + + protected function traverseForVisitor(Twig_NodeVisitorInterface $visitor, Twig_NodeInterface $node = null) + { + if (null === $node) { + return; + } + + $node = $visitor->enterNode($node, $this->env); + + foreach ($node as $k => $n) { + if (false !== $n = $this->traverseForVisitor($visitor, $n)) { + $node->setNode($k, $n); + } else { + $node->removeNode($k); + } + } + + return $visitor->leaveNode($node, $this->env); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/NodeVisitor/Escaper.php b/src/vendor/twig/twig/lib/Twig/NodeVisitor/Escaper.php new file mode 100644 index 0000000..78572b7 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/NodeVisitor/Escaper.php @@ -0,0 +1,161 @@ + + */ +class Twig_NodeVisitor_Escaper extends Twig_BaseNodeVisitor +{ + protected $statusStack = array(); + protected $blocks = array(); + protected $safeAnalysis; + protected $traverser; + protected $defaultStrategy = false; + protected $safeVars = array(); + + public function __construct() + { + $this->safeAnalysis = new Twig_NodeVisitor_SafeAnalysis(); + } + + /** + * {@inheritdoc} + */ + protected function doEnterNode(Twig_Node $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Module) { + if ($env->hasExtension('Twig_Extension_Escaper') && $defaultStrategy = $env->getExtension('Twig_Extension_Escaper')->getDefaultStrategy($node->getTemplateName())) { + $this->defaultStrategy = $defaultStrategy; + } + $this->safeVars = array(); + $this->blocks = array(); + } elseif ($node instanceof Twig_Node_AutoEscape) { + $this->statusStack[] = $node->getAttribute('value'); + } elseif ($node instanceof Twig_Node_Block) { + $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env); + } elseif ($node instanceof Twig_Node_Import) { + $this->safeVars[] = $node->getNode('var')->getAttribute('name'); + } + + return $node; + } + + /** + * {@inheritdoc} + */ + protected function doLeaveNode(Twig_Node $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Module) { + $this->defaultStrategy = false; + $this->safeVars = array(); + $this->blocks = array(); + } elseif ($node instanceof Twig_Node_Expression_Filter) { + return $this->preEscapeFilterNode($node, $env); + } elseif ($node instanceof Twig_Node_Print) { + return $this->escapePrintNode($node, $env, $this->needEscaping($env)); + } + + if ($node instanceof Twig_Node_AutoEscape || $node instanceof Twig_Node_Block) { + array_pop($this->statusStack); + } elseif ($node instanceof Twig_Node_BlockReference) { + $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env); + } + + return $node; + } + + protected function escapePrintNode(Twig_Node_Print $node, Twig_Environment $env, $type) + { + if (false === $type) { + return $node; + } + + $expression = $node->getNode('expr'); + + if ($this->isSafeFor($type, $expression, $env)) { + return $node; + } + + $class = get_class($node); + + return new $class( + $this->getEscaperFilter($type, $expression), + $node->getTemplateLine() + ); + } + + protected function preEscapeFilterNode(Twig_Node_Expression_Filter $filter, Twig_Environment $env) + { + $name = $filter->getNode('filter')->getAttribute('value'); + + $type = $env->getFilter($name)->getPreEscape(); + if (null === $type) { + return $filter; + } + + $node = $filter->getNode('node'); + if ($this->isSafeFor($type, $node, $env)) { + return $filter; + } + + $filter->setNode('node', $this->getEscaperFilter($type, $node)); + + return $filter; + } + + protected function isSafeFor($type, Twig_NodeInterface $expression, $env) + { + $safe = $this->safeAnalysis->getSafe($expression); + + if (null === $safe) { + if (null === $this->traverser) { + $this->traverser = new Twig_NodeTraverser($env, array($this->safeAnalysis)); + } + + $this->safeAnalysis->setSafeVars($this->safeVars); + + $this->traverser->traverse($expression); + $safe = $this->safeAnalysis->getSafe($expression); + } + + return in_array($type, $safe) || in_array('all', $safe); + } + + protected function needEscaping(Twig_Environment $env) + { + if (count($this->statusStack)) { + return $this->statusStack[count($this->statusStack) - 1]; + } + + return $this->defaultStrategy ? $this->defaultStrategy : false; + } + + protected function getEscaperFilter($type, Twig_NodeInterface $node) + { + $line = $node->getTemplateLine(); + $name = new Twig_Node_Expression_Constant('escape', $line); + $args = new Twig_Node(array(new Twig_Node_Expression_Constant((string) $type, $line), new Twig_Node_Expression_Constant(null, $line), new Twig_Node_Expression_Constant(true, $line))); + + return new Twig_Node_Expression_Filter($node, $name, $args, $line); + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 0; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/NodeVisitor/Optimizer.php b/src/vendor/twig/twig/lib/Twig/NodeVisitor/Optimizer.php new file mode 100644 index 0000000..4ac9a4d --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/NodeVisitor/Optimizer.php @@ -0,0 +1,260 @@ + + */ +class Twig_NodeVisitor_Optimizer extends Twig_BaseNodeVisitor +{ + const OPTIMIZE_ALL = -1; + const OPTIMIZE_NONE = 0; + const OPTIMIZE_FOR = 2; + const OPTIMIZE_RAW_FILTER = 4; + const OPTIMIZE_VAR_ACCESS = 8; + + protected $loops = array(); + protected $loopsTargets = array(); + protected $optimizers; + protected $prependedNodes = array(); + protected $inABody = false; + + /** + * @param int $optimizers The optimizer mode + */ + public function __construct($optimizers = -1) + { + if (!is_int($optimizers) || $optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_VAR_ACCESS)) { + throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); + } + + $this->optimizers = $optimizers; + } + + /** + * {@inheritdoc} + */ + protected function doEnterNode(Twig_Node $node, Twig_Environment $env) + { + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->enterOptimizeFor($node, $env); + } + + if (PHP_VERSION_ID < 50400 && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('Twig_Extension_Sandbox')) { + if ($this->inABody) { + if (!$node instanceof Twig_Node_Expression) { + if (get_class($node) !== 'Twig_Node') { + array_unshift($this->prependedNodes, array()); + } + } else { + $node = $this->optimizeVariables($node, $env); + } + } elseif ($node instanceof Twig_Node_Body) { + $this->inABody = true; + } + } + + return $node; + } + + /** + * {@inheritdoc} + */ + protected function doLeaveNode(Twig_Node $node, Twig_Environment $env) + { + $expression = $node instanceof Twig_Node_Expression; + + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->leaveOptimizeFor($node, $env); + } + + if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) { + $node = $this->optimizeRawFilter($node, $env); + } + + $node = $this->optimizePrintNode($node, $env); + + if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('Twig_Extension_Sandbox')) { + if ($node instanceof Twig_Node_Body) { + $this->inABody = false; + } elseif ($this->inABody) { + if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes)) { + $nodes = array(); + foreach (array_unique($prependedNodes) as $name) { + $nodes[] = new Twig_Node_SetTemp($name, $node->getTemplateLine()); + } + + $nodes[] = $node; + $node = new Twig_Node($nodes); + } + } + } + + return $node; + } + + protected function optimizeVariables(Twig_NodeInterface $node, Twig_Environment $env) + { + if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) { + $this->prependedNodes[0][] = $node->getAttribute('name'); + + return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getTemplateLine()); + } + + return $node; + } + + /** + * Optimizes print nodes. + * + * It replaces: + * + * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" + * + * @return Twig_NodeInterface + */ + protected function optimizePrintNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if (!$node instanceof Twig_Node_Print) { + return $node; + } + + $exprNode = $node->getNode('expr'); + if ( + $exprNode instanceof Twig_Node_Expression_BlockReference || + $exprNode instanceof Twig_Node_Expression_Parent + ) { + $exprNode->setAttribute('output', true); + + return $exprNode; + } + + return $node; + } + + /** + * Removes "raw" filters. + * + * @return Twig_NodeInterface + */ + protected function optimizeRawFilter(Twig_NodeInterface $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) { + return $node->getNode('node'); + } + + return $node; + } + + /** + * Optimizes "for" tag by removing the "loop" variable creation whenever possible. + */ + protected function enterOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_For) { + // disable the loop variable by default + $node->setAttribute('with_loop', false); + array_unshift($this->loops, $node); + array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name')); + array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name')); + } elseif (!$this->loops) { + // we are outside a loop + return; + } + + // when do we need to add the loop variable back? + + // the loop variable is referenced for the current loop + elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) { + $node->setAttribute('always_defined', true); + $this->addLoopToCurrent(); + } + + // optimize access to loop targets + elseif ($node instanceof Twig_Node_Expression_Name && in_array($node->getAttribute('name'), $this->loopsTargets)) { + $node->setAttribute('always_defined', true); + } + + // block reference + elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) { + $this->addLoopToCurrent(); + } + + // include without the only attribute + elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) { + $this->addLoopToAll(); + } + + // include function without the with_context=false parameter + elseif ($node instanceof Twig_Node_Expression_Function + && 'include' === $node->getAttribute('name') + && (!$node->getNode('arguments')->hasNode('with_context') + || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value') + ) + ) { + $this->addLoopToAll(); + } + + // the loop variable is referenced via an attribute + elseif ($node instanceof Twig_Node_Expression_GetAttr + && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant + || 'parent' === $node->getNode('attribute')->getAttribute('value') + ) + && (true === $this->loops[0]->getAttribute('with_loop') + || ($node->getNode('node') instanceof Twig_Node_Expression_Name + && 'loop' === $node->getNode('node')->getAttribute('name') + ) + ) + ) { + $this->addLoopToAll(); + } + } + + /** + * Optimizes "for" tag by removing the "loop" variable creation whenever possible. + */ + protected function leaveOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_For) { + array_shift($this->loops); + array_shift($this->loopsTargets); + array_shift($this->loopsTargets); + } + } + + protected function addLoopToCurrent() + { + $this->loops[0]->setAttribute('with_loop', true); + } + + protected function addLoopToAll() + { + foreach ($this->loops as $loop) { + $loop->setAttribute('with_loop', true); + } + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 255; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php b/src/vendor/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php new file mode 100644 index 0000000..80821ed --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php @@ -0,0 +1,157 @@ +safeVars = $safeVars; + } + + public function getSafe(Twig_NodeInterface $node) + { + $hash = spl_object_hash($node); + if (!isset($this->data[$hash])) { + return; + } + + foreach ($this->data[$hash] as $bucket) { + if ($bucket['key'] !== $node) { + continue; + } + + if (in_array('html_attr', $bucket['value'])) { + $bucket['value'][] = 'html'; + } + + return $bucket['value']; + } + } + + protected function setSafe(Twig_NodeInterface $node, array $safe) + { + $hash = spl_object_hash($node); + if (isset($this->data[$hash])) { + foreach ($this->data[$hash] as &$bucket) { + if ($bucket['key'] === $node) { + $bucket['value'] = $safe; + + return; + } + } + } + $this->data[$hash][] = array( + 'key' => $node, + 'value' => $safe, + ); + } + + /** + * {@inheritdoc} + */ + protected function doEnterNode(Twig_Node $node, Twig_Environment $env) + { + return $node; + } + + /** + * {@inheritdoc} + */ + protected function doLeaveNode(Twig_Node $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Expression_Constant) { + // constants are marked safe for all + $this->setSafe($node, array('all')); + } elseif ($node instanceof Twig_Node_Expression_BlockReference) { + // blocks are safe by definition + $this->setSafe($node, array('all')); + } elseif ($node instanceof Twig_Node_Expression_Parent) { + // parent block is safe by definition + $this->setSafe($node, array('all')); + } elseif ($node instanceof Twig_Node_Expression_Conditional) { + // intersect safeness of both operands + $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3'))); + $this->setSafe($node, $safe); + } elseif ($node instanceof Twig_Node_Expression_Filter) { + // filter expression is safe when the filter is safe + $name = $node->getNode('filter')->getAttribute('value'); + $args = $node->getNode('arguments'); + if (false !== $filter = $env->getFilter($name)) { + $safe = $filter->getSafe($args); + if (null === $safe) { + $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety()); + } + $this->setSafe($node, $safe); + } else { + $this->setSafe($node, array()); + } + } elseif ($node instanceof Twig_Node_Expression_Function) { + // function expression is safe when the function is safe + $name = $node->getAttribute('name'); + $args = $node->getNode('arguments'); + $function = $env->getFunction($name); + if (false !== $function) { + $this->setSafe($node, $function->getSafe($args)); + } else { + $this->setSafe($node, array()); + } + } elseif ($node instanceof Twig_Node_Expression_MethodCall) { + if ($node->getAttribute('safe')) { + $this->setSafe($node, array('all')); + } else { + $this->setSafe($node, array()); + } + } elseif ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name) { + $name = $node->getNode('node')->getAttribute('name'); + // attributes on template instances are safe + if ('_self' == $name || in_array($name, $this->safeVars)) { + $this->setSafe($node, array('all')); + } else { + $this->setSafe($node, array()); + } + } else { + $this->setSafe($node, array()); + } + + return $node; + } + + protected function intersectSafe(array $a = null, array $b = null) + { + if (null === $a || null === $b) { + return array(); + } + + if (in_array('all', $a)) { + return $b; + } + + if (in_array('all', $b)) { + return $a; + } + + return array_intersect($a, $b); + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 0; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/NodeVisitor/Sandbox.php b/src/vendor/twig/twig/lib/Twig/NodeVisitor/Sandbox.php new file mode 100644 index 0000000..d3258b4 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/NodeVisitor/Sandbox.php @@ -0,0 +1,84 @@ + + */ +class Twig_NodeVisitor_Sandbox extends Twig_BaseNodeVisitor +{ + protected $inAModule = false; + protected $tags; + protected $filters; + protected $functions; + + /** + * {@inheritdoc} + */ + protected function doEnterNode(Twig_Node $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Module) { + $this->inAModule = true; + $this->tags = array(); + $this->filters = array(); + $this->functions = array(); + + return $node; + } elseif ($this->inAModule) { + // look for tags + if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()])) { + $this->tags[$node->getNodeTag()] = $node; + } + + // look for filters + if ($node instanceof Twig_Node_Expression_Filter && !isset($this->filters[$node->getNode('filter')->getAttribute('value')])) { + $this->filters[$node->getNode('filter')->getAttribute('value')] = $node; + } + + // look for functions + if ($node instanceof Twig_Node_Expression_Function && !isset($this->functions[$node->getAttribute('name')])) { + $this->functions[$node->getAttribute('name')] = $node; + } + + // wrap print to check __toString() calls + if ($node instanceof Twig_Node_Print) { + return new Twig_Node_SandboxedPrint($node->getNode('expr'), $node->getTemplateLine(), $node->getNodeTag()); + } + } + + return $node; + } + + /** + * {@inheritdoc} + */ + protected function doLeaveNode(Twig_Node $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Module) { + $this->inAModule = false; + + $node->setNode('display_start', new Twig_Node(array(new Twig_Node_CheckSecurity($this->filters, $this->tags, $this->functions), $node->getNode('display_start')))); + } + + return $node; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 0; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/NodeVisitorInterface.php b/src/vendor/twig/twig/lib/Twig/NodeVisitorInterface.php new file mode 100644 index 0000000..a12de77 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/NodeVisitorInterface.php @@ -0,0 +1,41 @@ + + */ +interface Twig_NodeVisitorInterface +{ + /** + * Called before child nodes are visited. + * + * @return Twig_NodeInterface The modified node + */ + public function enterNode(Twig_NodeInterface $node, Twig_Environment $env); + + /** + * Called after child nodes are visited. + * + * @return Twig_NodeInterface|false The modified node or false if the node must be removed + */ + public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env); + + /** + * Returns the priority for this visitor. + * + * Priority should be between -10 and 10 (0 is the default). + * + * @return int The priority level + */ + public function getPriority(); +} diff --git a/src/vendor/twig/twig/lib/Twig/Parser.php b/src/vendor/twig/twig/lib/Twig/Parser.php new file mode 100644 index 0000000..53ae38f --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Parser.php @@ -0,0 +1,409 @@ + + */ +class Twig_Parser implements Twig_ParserInterface +{ + protected $stack = array(); + protected $stream; + protected $parent; + protected $handlers; + protected $visitors; + protected $expressionParser; + protected $blocks; + protected $blockStack; + protected $macros; + protected $env; + protected $reservedMacroNames; + protected $importedSymbols; + protected $traits; + protected $embeddedTemplates = array(); + + public function __construct(Twig_Environment $env) + { + $this->env = $env; + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function getEnvironment() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); + + return $this->env; + } + + public function getVarName() + { + return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false)); + } + + /** + * @deprecated since 1.27 (to be removed in 2.0). Use $parser->getStream()->getSourceContext()->getPath() instead. + */ + public function getFilename() + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use $parser->getStream()->getSourceContext()->getPath() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->stream->getSourceContext()->getName(); + } + + /** + * {@inheritdoc} + */ + public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = false) + { + // push all variables into the stack to keep the current state of the parser + // using get_object_vars() instead of foreach would lead to https://bugs.php.net/71336 + // This hack can be removed when min version if PHP 7.0 + $vars = array(); + foreach ($this as $k => $v) { + $vars[$k] = $v; + } + + unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames']); + $this->stack[] = $vars; + + // tag handlers + if (null === $this->handlers) { + $this->handlers = $this->env->getTokenParsers(); + $this->handlers->setParser($this); + } + + // node visitors + if (null === $this->visitors) { + $this->visitors = $this->env->getNodeVisitors(); + } + + if (null === $this->expressionParser) { + $this->expressionParser = new Twig_ExpressionParser($this, $this->env); + } + + $this->stream = $stream; + $this->parent = null; + $this->blocks = array(); + $this->macros = array(); + $this->traits = array(); + $this->blockStack = array(); + $this->importedSymbols = array(array()); + $this->embeddedTemplates = array(); + + try { + $body = $this->subparse($test, $dropNeedle); + + if (null !== $this->parent && null === $body = $this->filterBodyNodes($body)) { + $body = new Twig_Node(); + } + } catch (Twig_Error_Syntax $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($this->stream->getSourceContext()); + } + + if (!$e->getTemplateLine()) { + $e->setTemplateLine($this->stream->getCurrent()->getLine()); + } + + throw $e; + } + + $node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->embeddedTemplates, $stream->getSourceContext()); + + $traverser = new Twig_NodeTraverser($this->env, $this->visitors); + + $node = $traverser->traverse($node); + + // restore previous stack so previous parse() call can resume working + foreach (array_pop($this->stack) as $key => $val) { + $this->$key = $val; + } + + return $node; + } + + public function subparse($test, $dropNeedle = false) + { + $lineno = $this->getCurrentToken()->getLine(); + $rv = array(); + while (!$this->stream->isEOF()) { + switch ($this->getCurrentToken()->getType()) { + case Twig_Token::TEXT_TYPE: + $token = $this->stream->next(); + $rv[] = new Twig_Node_Text($token->getValue(), $token->getLine()); + break; + + case Twig_Token::VAR_START_TYPE: + $token = $this->stream->next(); + $expr = $this->expressionParser->parseExpression(); + $this->stream->expect(Twig_Token::VAR_END_TYPE); + $rv[] = new Twig_Node_Print($expr, $token->getLine()); + break; + + case Twig_Token::BLOCK_START_TYPE: + $this->stream->next(); + $token = $this->getCurrentToken(); + + if ($token->getType() !== Twig_Token::NAME_TYPE) { + throw new Twig_Error_Syntax('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()); + } + + if (null !== $test && call_user_func($test, $token)) { + if ($dropNeedle) { + $this->stream->next(); + } + + if (1 === count($rv)) { + return $rv[0]; + } + + return new Twig_Node($rv, array(), $lineno); + } + + $subparser = $this->handlers->getTokenParser($token->getValue()); + if (null === $subparser) { + if (null !== $test) { + $e = new Twig_Error_Syntax(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + + if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) { + $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno)); + } + } else { + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + $e->addSuggestions($token->getValue(), array_keys($this->env->getTags())); + } + + throw $e; + } + + $this->stream->next(); + + $node = $subparser->parse($token); + if (null !== $node) { + $rv[] = $node; + } + break; + + default: + throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext()); + } + } + + if (1 === count($rv)) { + return $rv[0]; + } + + return new Twig_Node($rv, array(), $lineno); + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function addHandler($name, $class) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); + + $this->handlers[$name] = $class; + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); + + $this->visitors[] = $visitor; + } + + public function getBlockStack() + { + return $this->blockStack; + } + + public function peekBlockStack() + { + return $this->blockStack[count($this->blockStack) - 1]; + } + + public function popBlockStack() + { + array_pop($this->blockStack); + } + + public function pushBlockStack($name) + { + $this->blockStack[] = $name; + } + + public function hasBlock($name) + { + return isset($this->blocks[$name]); + } + + public function getBlock($name) + { + return $this->blocks[$name]; + } + + public function setBlock($name, Twig_Node_Block $value) + { + $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getTemplateLine()); + } + + public function hasMacro($name) + { + return isset($this->macros[$name]); + } + + public function setMacro($name, Twig_Node_Macro $node) + { + if ($this->isReservedMacroName($name)) { + throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword.', $name), $node->getTemplateLine(), $this->stream->getSourceContext()); + } + + $this->macros[$name] = $node; + } + + public function isReservedMacroName($name) + { + if (null === $this->reservedMacroNames) { + $this->reservedMacroNames = array(); + $r = new ReflectionClass($this->env->getBaseTemplateClass()); + foreach ($r->getMethods() as $method) { + $methodName = strtolower($method->getName()); + + if ('get' === substr($methodName, 0, 3) && isset($methodName[3])) { + $this->reservedMacroNames[] = substr($methodName, 3); + } + } + } + + return in_array(strtolower($name), $this->reservedMacroNames); + } + + public function addTrait($trait) + { + $this->traits[] = $trait; + } + + public function hasTraits() + { + return count($this->traits) > 0; + } + + public function embedTemplate(Twig_Node_Module $template) + { + $template->setIndex(mt_rand()); + + $this->embeddedTemplates[] = $template; + } + + public function addImportedSymbol($type, $alias, $name = null, Twig_Node_Expression $node = null) + { + $this->importedSymbols[0][$type][$alias] = array('name' => $name, 'node' => $node); + } + + public function getImportedSymbol($type, $alias) + { + foreach ($this->importedSymbols as $functions) { + if (isset($functions[$type][$alias])) { + return $functions[$type][$alias]; + } + } + } + + public function isMainScope() + { + return 1 === count($this->importedSymbols); + } + + public function pushLocalScope() + { + array_unshift($this->importedSymbols, array()); + } + + public function popLocalScope() + { + array_shift($this->importedSymbols); + } + + /** + * @return Twig_ExpressionParser + */ + public function getExpressionParser() + { + return $this->expressionParser; + } + + public function getParent() + { + return $this->parent; + } + + public function setParent($parent) + { + $this->parent = $parent; + } + + /** + * @return Twig_TokenStream + */ + public function getStream() + { + return $this->stream; + } + + /** + * @return Twig_Token + */ + public function getCurrentToken() + { + return $this->stream->getCurrent(); + } + + protected function filterBodyNodes(Twig_NodeInterface $node) + { + // check that the body does not contain non-empty output nodes + if ( + ($node instanceof Twig_Node_Text && !ctype_space($node->getAttribute('data'))) + || + (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface) + ) { + if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) { + throw new Twig_Error_Syntax('A template that extends another one cannot start with a byte order mark (BOM); it must be removed.', $node->getTemplateLine(), $this->stream->getSourceContext()); + } + + throw new Twig_Error_Syntax('A template that extends another one cannot include contents outside Twig blocks. Did you forget to put the contents inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext()); + } + + // bypass "set" nodes as they "capture" the output + if ($node instanceof Twig_Node_Set) { + return $node; + } + + if ($node instanceof Twig_NodeOutputInterface) { + return; + } + + foreach ($node as $k => $n) { + if (null !== $n && null === $this->filterBodyNodes($n)) { + $node->removeNode($k); + } + } + + return $node; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/ParserInterface.php b/src/vendor/twig/twig/lib/Twig/ParserInterface.php new file mode 100644 index 0000000..73da61b --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/ParserInterface.php @@ -0,0 +1,29 @@ + + * + * @deprecated since 1.12 (to be removed in 3.0) + */ +interface Twig_ParserInterface +{ + /** + * Converts a token stream to a node tree. + * + * @return Twig_Node_Module + * + * @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong + */ + public function parse(Twig_TokenStream $stream); +} diff --git a/src/vendor/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php b/src/vendor/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php new file mode 100644 index 0000000..03df629 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php @@ -0,0 +1,70 @@ + + * + * @final + */ +class Twig_Profiler_Dumper_Blackfire +{ + public function dump(Twig_Profiler_Profile $profile) + { + $data = array(); + $this->dumpProfile('main()', $profile, $data); + $this->dumpChildren('main()', $profile, $data); + + $start = microtime(true); + $str = << $values) { + $str .= "{$name}//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\n"; + } + + return $str; + } + + private function dumpChildren($parent, Twig_Profiler_Profile $profile, &$data) + { + foreach ($profile as $p) { + if ($p->isTemplate()) { + $name = $p->getTemplate(); + } else { + $name = sprintf('%s::%s(%s)', $p->getTemplate(), $p->getType(), $p->getName()); + } + $this->dumpProfile(sprintf('%s==>%s', $parent, $name), $p, $data); + $this->dumpChildren($name, $p, $data); + } + } + + private function dumpProfile($edge, Twig_Profiler_Profile $profile, &$data) + { + if (isset($data[$edge])) { + $data[$edge]['ct'] += 1; + $data[$edge]['wt'] += floor($profile->getDuration() * 1000000); + $data[$edge]['mu'] += $profile->getMemoryUsage(); + $data[$edge]['pmu'] += $profile->getPeakMemoryUsage(); + } else { + $data[$edge] = array( + 'ct' => 1, + 'wt' => floor($profile->getDuration() * 1000000), + 'mu' => $profile->getMemoryUsage(), + 'pmu' => $profile->getPeakMemoryUsage(), + ); + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Profiler/Dumper/Html.php b/src/vendor/twig/twig/lib/Twig/Profiler/Dumper/Html.php new file mode 100644 index 0000000..d192af3 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Profiler/Dumper/Html.php @@ -0,0 +1,45 @@ + + * + * @final + */ +class Twig_Profiler_Dumper_Html extends Twig_Profiler_Dumper_Text +{ + private static $colors = array( + 'block' => '#dfd', + 'macro' => '#ddf', + 'template' => '#ffd', + 'big' => '#d44', + ); + + public function dump(Twig_Profiler_Profile $profile) + { + return '
    '.parent::dump($profile).'
    '; + } + + protected function formatTemplate(Twig_Profiler_Profile $profile, $prefix) + { + return sprintf('%s└ %s', $prefix, self::$colors['template'], $profile->getTemplate()); + } + + protected function formatNonTemplate(Twig_Profiler_Profile $profile, $prefix) + { + return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), isset(self::$colors[$profile->getType()]) ? self::$colors[$profile->getType()] : 'auto', $profile->getName()); + } + + protected function formatTime(Twig_Profiler_Profile $profile, $percent) + { + return sprintf('%.2fms/%.0f%%', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Profiler/Dumper/Text.php b/src/vendor/twig/twig/lib/Twig/Profiler/Dumper/Text.php new file mode 100644 index 0000000..d8c0f9f --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Profiler/Dumper/Text.php @@ -0,0 +1,70 @@ + + * + * @final + */ +class Twig_Profiler_Dumper_Text +{ + private $root; + + public function dump(Twig_Profiler_Profile $profile) + { + return $this->dumpProfile($profile); + } + + protected function formatTemplate(Twig_Profiler_Profile $profile, $prefix) + { + return sprintf('%s└ %s', $prefix, $profile->getTemplate()); + } + + protected function formatNonTemplate(Twig_Profiler_Profile $profile, $prefix) + { + return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName()); + } + + protected function formatTime(Twig_Profiler_Profile $profile, $percent) + { + return sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent); + } + + private function dumpProfile(Twig_Profiler_Profile $profile, $prefix = '', $sibling = false) + { + if ($profile->isRoot()) { + $this->root = $profile->getDuration(); + $start = $profile->getName(); + } else { + if ($profile->isTemplate()) { + $start = $this->formatTemplate($profile, $prefix); + } else { + $start = $this->formatNonTemplate($profile, $prefix); + } + $prefix .= $sibling ? '│ ' : ' '; + } + + $percent = $this->root ? $profile->getDuration() / $this->root * 100 : 0; + + if ($profile->getDuration() * 1000 < 1) { + $str = $start."\n"; + } else { + $str = sprintf("%s %s\n", $start, $this->formatTime($profile, $percent)); + } + + $nCount = count($profile->getProfiles()); + foreach ($profile as $i => $p) { + $str .= $this->dumpProfile($p, $prefix, $i + 1 !== $nCount); + } + + return $str; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php b/src/vendor/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php new file mode 100644 index 0000000..2f97214 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php @@ -0,0 +1,40 @@ + + */ +class Twig_Profiler_Node_EnterProfile extends Twig_Node +{ + public function __construct($extensionName, $type, $name, $varName) + { + parent::__construct(array(), array('extension_name' => $extensionName, 'name' => $name, 'type' => $type, 'var_name' => $varName)); + } + + /** + * {@inheritdoc} + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->write(sprintf('$%s = $this->env->getExtension(', $this->getAttribute('var_name'))) + ->repr($this->getAttribute('extension_name')) + ->raw(");\n") + ->write(sprintf('$%s->enter($%s = new Twig_Profiler_Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ->repr($this->getAttribute('type')) + ->raw(', ') + ->repr($this->getAttribute('name')) + ->raw("));\n\n") + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Profiler/Node/LeaveProfile.php b/src/vendor/twig/twig/lib/Twig/Profiler/Node/LeaveProfile.php new file mode 100644 index 0000000..88074c2 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Profiler/Node/LeaveProfile.php @@ -0,0 +1,34 @@ + + */ +class Twig_Profiler_Node_LeaveProfile extends Twig_Node +{ + public function __construct($varName) + { + parent::__construct(array(), array('var_name' => $varName)); + } + + /** + * {@inheritdoc} + */ + public function compile(Twig_Compiler $compiler) + { + $compiler + ->write("\n") + ->write(sprintf("\$%s->leave(\$%s);\n\n", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php b/src/vendor/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php new file mode 100644 index 0000000..60df054 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php @@ -0,0 +1,74 @@ + + * + * @final + */ +class Twig_Profiler_NodeVisitor_Profiler extends Twig_BaseNodeVisitor +{ + private $extensionName; + + public function __construct($extensionName) + { + $this->extensionName = $extensionName; + } + + /** + * {@inheritdoc} + */ + protected function doEnterNode(Twig_Node $node, Twig_Environment $env) + { + return $node; + } + + /** + * {@inheritdoc} + */ + protected function doLeaveNode(Twig_Node $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Module) { + $varName = $this->getVarName(); + $node->setNode('display_start', new Twig_Node(array(new Twig_Profiler_Node_EnterProfile($this->extensionName, Twig_Profiler_Profile::TEMPLATE, $node->getTemplateName(), $varName), $node->getNode('display_start')))); + $node->setNode('display_end', new Twig_Node(array(new Twig_Profiler_Node_LeaveProfile($varName), $node->getNode('display_end')))); + } elseif ($node instanceof Twig_Node_Block) { + $varName = $this->getVarName(); + $node->setNode('body', new Twig_Node_Body(array( + new Twig_Profiler_Node_EnterProfile($this->extensionName, Twig_Profiler_Profile::BLOCK, $node->getAttribute('name'), $varName), + $node->getNode('body'), + new Twig_Profiler_Node_LeaveProfile($varName), + ))); + } elseif ($node instanceof Twig_Node_Macro) { + $varName = $this->getVarName(); + $node->setNode('body', new Twig_Node_Body(array( + new Twig_Profiler_Node_EnterProfile($this->extensionName, Twig_Profiler_Profile::MACRO, $node->getAttribute('name'), $varName), + $node->getNode('body'), + new Twig_Profiler_Node_LeaveProfile($varName), + ))); + } + + return $node; + } + + private function getVarName() + { + return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false)); + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 0; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Profiler/Profile.php b/src/vendor/twig/twig/lib/Twig/Profiler/Profile.php new file mode 100644 index 0000000..93bdbb6 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Profiler/Profile.php @@ -0,0 +1,162 @@ + + * + * @final + */ +class Twig_Profiler_Profile implements IteratorAggregate, Serializable +{ + const ROOT = 'ROOT'; + const BLOCK = 'block'; + const TEMPLATE = 'template'; + const MACRO = 'macro'; + + private $template; + private $name; + private $type; + private $starts = array(); + private $ends = array(); + private $profiles = array(); + + public function __construct($template = 'main', $type = self::ROOT, $name = 'main') + { + $this->template = $template; + $this->type = $type; + $this->name = 0 === strpos($name, '__internal_') ? 'INTERNAL' : $name; + $this->enter(); + } + + public function getTemplate() + { + return $this->template; + } + + public function getType() + { + return $this->type; + } + + public function getName() + { + return $this->name; + } + + public function isRoot() + { + return self::ROOT === $this->type; + } + + public function isTemplate() + { + return self::TEMPLATE === $this->type; + } + + public function isBlock() + { + return self::BLOCK === $this->type; + } + + public function isMacro() + { + return self::MACRO === $this->type; + } + + public function getProfiles() + { + return $this->profiles; + } + + public function addProfile(Twig_Profiler_Profile $profile) + { + $this->profiles[] = $profile; + } + + /** + * Returns the duration in microseconds. + * + * @return int + */ + public function getDuration() + { + if ($this->isRoot() && $this->profiles) { + // for the root node with children, duration is the sum of all child durations + $duration = 0; + foreach ($this->profiles as $profile) { + $duration += $profile->getDuration(); + } + + return $duration; + } + + return isset($this->ends['wt']) && isset($this->starts['wt']) ? $this->ends['wt'] - $this->starts['wt'] : 0; + } + + /** + * Returns the memory usage in bytes. + * + * @return int + */ + public function getMemoryUsage() + { + return isset($this->ends['mu']) && isset($this->starts['mu']) ? $this->ends['mu'] - $this->starts['mu'] : 0; + } + + /** + * Returns the peak memory usage in bytes. + * + * @return int + */ + public function getPeakMemoryUsage() + { + return isset($this->ends['pmu']) && isset($this->starts['pmu']) ? $this->ends['pmu'] - $this->starts['pmu'] : 0; + } + + /** + * Starts the profiling. + */ + public function enter() + { + $this->starts = array( + 'wt' => microtime(true), + 'mu' => memory_get_usage(), + 'pmu' => memory_get_peak_usage(), + ); + } + + /** + * Stops the profiling. + */ + public function leave() + { + $this->ends = array( + 'wt' => microtime(true), + 'mu' => memory_get_usage(), + 'pmu' => memory_get_peak_usage(), + ); + } + + public function getIterator() + { + return new ArrayIterator($this->profiles); + } + + public function serialize() + { + return serialize(array($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles)); + } + + public function unserialize($data) + { + list($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles) = unserialize($data); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/RuntimeLoaderInterface.php b/src/vendor/twig/twig/lib/Twig/RuntimeLoaderInterface.php new file mode 100644 index 0000000..c41f44a --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/RuntimeLoaderInterface.php @@ -0,0 +1,27 @@ + + */ +interface Twig_RuntimeLoaderInterface +{ + /** + * Creates the runtime implementation of a Twig element (filter/function/test). + * + * @param string $class A runtime class + * + * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class + */ + public function load($class); +} diff --git a/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityError.php b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityError.php new file mode 100644 index 0000000..015bfae --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityError.php @@ -0,0 +1,19 @@ + + */ +class Twig_Sandbox_SecurityError extends Twig_Error +{ +} diff --git a/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php new file mode 100644 index 0000000..99faba9 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php @@ -0,0 +1,31 @@ + + */ +class Twig_Sandbox_SecurityNotAllowedFilterError extends Twig_Sandbox_SecurityError +{ + private $filterName; + + public function __construct($message, $functionName, $lineno = -1, $filename = null, Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->filterName = $functionName; + } + + public function getFilterName() + { + return $this->filterName; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php new file mode 100644 index 0000000..05cf488 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php @@ -0,0 +1,31 @@ + + */ +class Twig_Sandbox_SecurityNotAllowedFunctionError extends Twig_Sandbox_SecurityError +{ + private $functionName; + + public function __construct($message, $functionName, $lineno = -1, $filename = null, Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->functionName = $functionName; + } + + public function getFunctionName() + { + return $this->functionName; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php new file mode 100644 index 0000000..5b352d9 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php @@ -0,0 +1,38 @@ + + */ +class Twig_Sandbox_SecurityNotAllowedMethodError extends Twig_Sandbox_SecurityError +{ + private $className; + private $methodName; + + public function __construct($message, $className, $methodName, $lineno = -1, $filename = null, Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->className = $className; + $this->methodName = $methodName; + } + + public function getClassName() + { + return $this->className; + } + + public function getMethodName() + { + return $this->methodName; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php new file mode 100644 index 0000000..8b4cbc3 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php @@ -0,0 +1,38 @@ + + */ +class Twig_Sandbox_SecurityNotAllowedPropertyError extends Twig_Sandbox_SecurityError +{ + private $className; + private $propertyName; + + public function __construct($message, $className, $propertyName, $lineno = -1, $filename = null, Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->className = $className; + $this->propertyName = $propertyName; + } + + public function getClassName() + { + return $this->className; + } + + public function getPropertyName() + { + return $this->propertyName; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedTagError.php b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedTagError.php new file mode 100644 index 0000000..b3bb5e8 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedTagError.php @@ -0,0 +1,31 @@ + + */ +class Twig_Sandbox_SecurityNotAllowedTagError extends Twig_Sandbox_SecurityError +{ + private $tagName; + + public function __construct($message, $tagName, $lineno = -1, $filename = null, Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->tagName = $tagName; + } + + public function getTagName() + { + return $this->tagName; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php new file mode 100644 index 0000000..121fc30 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php @@ -0,0 +1,123 @@ + + */ +class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterface +{ + protected $allowedTags; + protected $allowedFilters; + protected $allowedMethods; + protected $allowedProperties; + protected $allowedFunctions; + + public function __construct(array $allowedTags = array(), array $allowedFilters = array(), array $allowedMethods = array(), array $allowedProperties = array(), array $allowedFunctions = array()) + { + $this->allowedTags = $allowedTags; + $this->allowedFilters = $allowedFilters; + $this->setAllowedMethods($allowedMethods); + $this->allowedProperties = $allowedProperties; + $this->allowedFunctions = $allowedFunctions; + } + + public function setAllowedTags(array $tags) + { + $this->allowedTags = $tags; + } + + public function setAllowedFilters(array $filters) + { + $this->allowedFilters = $filters; + } + + public function setAllowedMethods(array $methods) + { + $this->allowedMethods = array(); + foreach ($methods as $class => $m) { + $this->allowedMethods[$class] = array_map('strtolower', is_array($m) ? $m : array($m)); + } + } + + public function setAllowedProperties(array $properties) + { + $this->allowedProperties = $properties; + } + + public function setAllowedFunctions(array $functions) + { + $this->allowedFunctions = $functions; + } + + public function checkSecurity($tags, $filters, $functions) + { + foreach ($tags as $tag) { + if (!in_array($tag, $this->allowedTags)) { + throw new Twig_Sandbox_SecurityNotAllowedTagError(sprintf('Tag "%s" is not allowed.', $tag), $tag); + } + } + + foreach ($filters as $filter) { + if (!in_array($filter, $this->allowedFilters)) { + throw new Twig_Sandbox_SecurityNotAllowedFilterError(sprintf('Filter "%s" is not allowed.', $filter), $filter); + } + } + + foreach ($functions as $function) { + if (!in_array($function, $this->allowedFunctions)) { + throw new Twig_Sandbox_SecurityNotAllowedFunctionError(sprintf('Function "%s" is not allowed.', $function), $function); + } + } + } + + public function checkMethodAllowed($obj, $method) + { + if ($obj instanceof Twig_TemplateInterface || $obj instanceof Twig_Markup) { + return true; + } + + $allowed = false; + $method = strtolower($method); + foreach ($this->allowedMethods as $class => $methods) { + if ($obj instanceof $class) { + $allowed = in_array($method, $methods); + + break; + } + } + + if (!$allowed) { + $class = get_class($obj); + throw new Twig_Sandbox_SecurityNotAllowedMethodError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method); + } + } + + public function checkPropertyAllowed($obj, $property) + { + $allowed = false; + foreach ($this->allowedProperties as $class => $properties) { + if ($obj instanceof $class) { + $allowed = in_array($property, is_array($properties) ? $properties : array($properties)); + + break; + } + } + + if (!$allowed) { + $class = get_class($obj); + throw new Twig_Sandbox_SecurityNotAllowedPropertyError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property); + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicyInterface.php b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicyInterface.php new file mode 100644 index 0000000..6ab48e3 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicyInterface.php @@ -0,0 +1,24 @@ + + */ +interface Twig_Sandbox_SecurityPolicyInterface +{ + public function checkSecurity($tags, $filters, $functions); + + public function checkMethodAllowed($obj, $method); + + public function checkPropertyAllowed($obj, $method); +} diff --git a/src/vendor/twig/twig/lib/Twig/SimpleFilter.php b/src/vendor/twig/twig/lib/Twig/SimpleFilter.php new file mode 100644 index 0000000..0c1e092 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/SimpleFilter.php @@ -0,0 +1,117 @@ + + */ +class Twig_SimpleFilter +{ + protected $name; + protected $callable; + protected $options; + protected $arguments = array(); + + public function __construct($name, $callable, array $options = array()) + { + $this->name = $name; + $this->callable = $callable; + $this->options = array_merge(array( + 'needs_environment' => false, + 'needs_context' => false, + 'is_variadic' => false, + 'is_safe' => null, + 'is_safe_callback' => null, + 'pre_escape' => null, + 'preserves_safety' => null, + 'node_class' => 'Twig_Node_Expression_Filter', + 'deprecated' => false, + 'alternative' => null, + ), $options); + } + + public function getName() + { + return $this->name; + } + + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass() + { + return $this->options['node_class']; + } + + public function setArguments($arguments) + { + $this->arguments = $arguments; + } + + public function getArguments() + { + return $this->arguments; + } + + public function needsEnvironment() + { + return $this->options['needs_environment']; + } + + public function needsContext() + { + return $this->options['needs_context']; + } + + public function getSafe(Twig_Node $filterArgs) + { + if (null !== $this->options['is_safe']) { + return $this->options['is_safe']; + } + + if (null !== $this->options['is_safe_callback']) { + return call_user_func($this->options['is_safe_callback'], $filterArgs); + } + } + + public function getPreservesSafety() + { + return $this->options['preserves_safety']; + } + + public function getPreEscape() + { + return $this->options['pre_escape']; + } + + public function isVariadic() + { + return $this->options['is_variadic']; + } + + public function isDeprecated() + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion() + { + return $this->options['deprecated']; + } + + public function getAlternative() + { + return $this->options['alternative']; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/SimpleFunction.php b/src/vendor/twig/twig/lib/Twig/SimpleFunction.php new file mode 100644 index 0000000..63003d6 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/SimpleFunction.php @@ -0,0 +1,107 @@ + + */ +class Twig_SimpleFunction +{ + protected $name; + protected $callable; + protected $options; + protected $arguments = array(); + + public function __construct($name, $callable, array $options = array()) + { + $this->name = $name; + $this->callable = $callable; + $this->options = array_merge(array( + 'needs_environment' => false, + 'needs_context' => false, + 'is_variadic' => false, + 'is_safe' => null, + 'is_safe_callback' => null, + 'node_class' => 'Twig_Node_Expression_Function', + 'deprecated' => false, + 'alternative' => null, + ), $options); + } + + public function getName() + { + return $this->name; + } + + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass() + { + return $this->options['node_class']; + } + + public function setArguments($arguments) + { + $this->arguments = $arguments; + } + + public function getArguments() + { + return $this->arguments; + } + + public function needsEnvironment() + { + return $this->options['needs_environment']; + } + + public function needsContext() + { + return $this->options['needs_context']; + } + + public function getSafe(Twig_Node $functionArgs) + { + if (null !== $this->options['is_safe']) { + return $this->options['is_safe']; + } + + if (null !== $this->options['is_safe_callback']) { + return call_user_func($this->options['is_safe_callback'], $functionArgs); + } + + return array(); + } + + public function isVariadic() + { + return $this->options['is_variadic']; + } + + public function isDeprecated() + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion() + { + return $this->options['deprecated']; + } + + public function getAlternative() + { + return $this->options['alternative']; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/SimpleTest.php b/src/vendor/twig/twig/lib/Twig/SimpleTest.php new file mode 100644 index 0000000..cff3ae7 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/SimpleTest.php @@ -0,0 +1,69 @@ + + */ +class Twig_SimpleTest +{ + protected $name; + protected $callable; + protected $options; + + public function __construct($name, $callable, array $options = array()) + { + $this->name = $name; + $this->callable = $callable; + $this->options = array_merge(array( + 'is_variadic' => false, + 'node_class' => 'Twig_Node_Expression_Test', + 'deprecated' => false, + 'alternative' => null, + ), $options); + } + + public function getName() + { + return $this->name; + } + + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass() + { + return $this->options['node_class']; + } + + public function isVariadic() + { + return $this->options['is_variadic']; + } + + public function isDeprecated() + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion() + { + return $this->options['deprecated']; + } + + public function getAlternative() + { + return $this->options['alternative']; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Source.php b/src/vendor/twig/twig/lib/Twig/Source.php new file mode 100644 index 0000000..efe0f14 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Source.php @@ -0,0 +1,51 @@ + + */ +class Twig_Source +{ + private $code; + private $name; + private $path; + + /** + * @param string $code The template source code + * @param string $name The template logical name + * @param string $path The filesystem path of the template if any + */ + public function __construct($code, $name, $path = '') + { + $this->code = $code; + $this->name = $name; + $this->path = $path; + } + + public function getCode() + { + return $this->code; + } + + public function getName() + { + return $this->name; + } + + public function getPath() + { + return $this->path; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/SourceContextLoaderInterface.php b/src/vendor/twig/twig/lib/Twig/SourceContextLoaderInterface.php new file mode 100644 index 0000000..bb4115e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/SourceContextLoaderInterface.php @@ -0,0 +1,31 @@ + + * + * @deprecated since 1.27 (to be removed in 3.0) + */ +interface Twig_SourceContextLoaderInterface +{ + /** + * Returns the source context for a given template logical name. + * + * @param string $name The template logical name + * + * @return Twig_Source + * + * @throws Twig_Error_Loader When $name is not found + */ + public function getSourceContext($name); +} diff --git a/src/vendor/twig/twig/lib/Twig/Template.php b/src/vendor/twig/twig/lib/Twig/Template.php new file mode 100644 index 0000000..c09ab7d --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Template.php @@ -0,0 +1,707 @@ +load() + * instead, which returns an instance of Twig_TemplateWrapper. + * + * @author Fabien Potencier + * + * @internal + */ +abstract class Twig_Template implements Twig_TemplateInterface +{ + /** + * @internal + */ + protected static $cache = array(); + + protected $parent; + protected $parents = array(); + protected $env; + protected $blocks = array(); + protected $traits = array(); + + public function __construct(Twig_Environment $env) + { + $this->env = $env; + } + + /** + * @internal this method will be removed in 2.0 and is only used internally to provide an upgrade path from 1.x to 2.0 + */ + public function __toString() + { + return $this->getTemplateName(); + } + + /** + * Returns the template name. + * + * @return string The template name + */ + abstract public function getTemplateName(); + + /** + * Returns debug information about the template. + * + * @return array Debug information + * + * @internal + */ + public function getDebugInfo() + { + return array(); + } + + /** + * Returns the template source code. + * + * @return string The template source code + * + * @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead + */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return ''; + } + + /** + * Returns information about the original template source code. + * + * @return Twig_Source + */ + public function getSourceContext() + { + return new Twig_Source('', $this->getTemplateName()); + } + + /** + * @deprecated since 1.20 (to be removed in 2.0) + */ + public function getEnvironment() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.20 and will be removed in 2.0.', E_USER_DEPRECATED); + + return $this->env; + } + + /** + * Returns the parent template. + * + * This method is for internal use only and should never be called + * directly. + * + * @param array $context + * + * @return Twig_TemplateInterface|false The parent template or false if there is no parent + * + * @internal + */ + public function getParent(array $context) + { + if (null !== $this->parent) { + return $this->parent; + } + + try { + $parent = $this->doGetParent($context); + + if (false === $parent) { + return false; + } + + if ($parent instanceof self) { + return $this->parents[$parent->getTemplateName()] = $parent; + } + + if (!isset($this->parents[$parent])) { + $this->parents[$parent] = $this->loadTemplate($parent); + } + } catch (Twig_Error_Loader $e) { + $e->setSourceContext(null); + $e->guess(); + + throw $e; + } + + return $this->parents[$parent]; + } + + protected function doGetParent(array $context) + { + return false; + } + + public function isTraitable() + { + return true; + } + + /** + * Displays a parent block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to display from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @internal + */ + public function displayParentBlock($name, array $context, array $blocks = array()) + { + $name = (string) $name; + + if (isset($this->traits[$name])) { + $this->traits[$name][0]->displayBlock($name, $context, $blocks, false); + } elseif (false !== $parent = $this->getParent($context)) { + $parent->displayBlock($name, $context, $blocks, false); + } else { + throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext()); + } + } + + /** + * Displays a block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to display + * @param array $context The context + * @param array $blocks The current set of blocks + * @param bool $useBlocks Whether to use the current set of blocks + * + * @internal + */ + public function displayBlock($name, array $context, array $blocks = array(), $useBlocks = true) + { + $name = (string) $name; + + if ($useBlocks && isset($blocks[$name])) { + $template = $blocks[$name][0]; + $block = $blocks[$name][1]; + } elseif (isset($this->blocks[$name])) { + $template = $this->blocks[$name][0]; + $block = $this->blocks[$name][1]; + } else { + $template = null; + $block = null; + } + + // avoid RCEs when sandbox is enabled + if (null !== $template && !$template instanceof self) { + throw new LogicException('A block must be a method on a Twig_Template instance.'); + } + + if (null !== $template) { + try { + $template->$block($context, $blocks); + } catch (Twig_Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($template->getSourceContext()); + } + + // this is mostly useful for Twig_Error_Loader exceptions + // see Twig_Error_Loader + if (false === $e->getTemplateLine()) { + $e->setTemplateLine(-1); + $e->guess(); + } + + throw $e; + } catch (Exception $e) { + throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e); + } + } elseif (false !== $parent = $this->getParent($context)) { + $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false); + } else { + @trigger_error(sprintf('Silent display of undefined block "%s" in template "%s" is deprecated since version 1.29 and will throw an exception in 2.0.', $name, $this->getTemplateName()), E_USER_DEPRECATED); + } + } + + /** + * Renders a parent block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to render from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return string The rendered block + * + * @internal + */ + public function renderParentBlock($name, array $context, array $blocks = array()) + { + ob_start(); + $this->displayParentBlock($name, $context, $blocks); + + return ob_get_clean(); + } + + /** + * Renders a block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to render + * @param array $context The context + * @param array $blocks The current set of blocks + * @param bool $useBlocks Whether to use the current set of blocks + * + * @return string The rendered block + * + * @internal + */ + public function renderBlock($name, array $context, array $blocks = array(), $useBlocks = true) + { + ob_start(); + $this->displayBlock($name, $context, $blocks, $useBlocks); + + return ob_get_clean(); + } + + /** + * Returns whether a block exists or not in the current context of the template. + * + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. + * + * @param string $name The block name + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return bool true if the block exists, false otherwise + * + * @internal + */ + public function hasBlock($name, array $context = null, array $blocks = array()) + { + if (null === $context) { + @trigger_error('The '.__METHOD__.' method is internal and should never be called; calling it directly is deprecated since version 1.28 and won\'t be possible anymore in 2.0.', E_USER_DEPRECATED); + + return isset($this->blocks[(string) $name]); + } + + if (isset($blocks[$name])) { + return $blocks[$name][0] instanceof self; + } + + if (isset($this->blocks[$name])) { + return true; + } + + if (false !== $parent = $this->getParent($context)) { + return $parent->hasBlock($name, $context); + } + + return false; + } + + /** + * Returns all block names in the current context of the template. + * + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. + * + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return array An array of block names + * + * @internal + */ + public function getBlockNames(array $context = null, array $blocks = array()) + { + if (null === $context) { + @trigger_error('The '.__METHOD__.' method is internal and should never be called; calling it directly is deprecated since version 1.28 and won\'t be possible anymore in 2.0.', E_USER_DEPRECATED); + + return array_keys($this->blocks); + } + + $names = array_merge(array_keys($blocks), array_keys($this->blocks)); + + if (false !== $parent = $this->getParent($context)) { + $names = array_merge($names, $parent->getBlockNames($context)); + } + + return array_unique($names); + } + + protected function loadTemplate($template, $templateName = null, $line = null, $index = null) + { + try { + if (is_array($template)) { + return $this->env->resolveTemplate($template); + } + + if ($template instanceof self) { + return $template; + } + + if ($template instanceof Twig_TemplateWrapper) { + return $template; + } + + return $this->env->loadTemplate($template, $index); + } catch (Twig_Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($templateName ? new Twig_Source('', $templateName) : $this->getSourceContext()); + } + + if ($e->getTemplateLine()) { + throw $e; + } + + if (!$line) { + $e->guess(); + } else { + $e->setTemplateLine($line); + } + + throw $e; + } + } + + /** + * Returns all blocks. + * + * This method is for internal use only and should never be called + * directly. + * + * @return array An array of blocks + * + * @internal + */ + public function getBlocks() + { + return $this->blocks; + } + + /** + * {@inheritdoc} + */ + public function display(array $context, array $blocks = array()) + { + $this->displayWithErrorHandling($this->env->mergeGlobals($context), array_merge($this->blocks, $blocks)); + } + + /** + * {@inheritdoc} + */ + public function render(array $context) + { + $level = ob_get_level(); + ob_start(); + try { + $this->display($context); + } catch (Exception $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } catch (Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } + + return ob_get_clean(); + } + + protected function displayWithErrorHandling(array $context, array $blocks = array()) + { + try { + $this->doDisplay($context, $blocks); + } catch (Twig_Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($this->getSourceContext()); + } + + // this is mostly useful for Twig_Error_Loader exceptions + // see Twig_Error_Loader + if (false === $e->getTemplateLine()) { + $e->setTemplateLine(-1); + $e->guess(); + } + + throw $e; + } catch (Exception $e) { + throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e); + } + } + + /** + * Auto-generated method to display the template with the given context. + * + * @param array $context An array of parameters to pass to the template + * @param array $blocks An array of blocks to pass to the template + */ + abstract protected function doDisplay(array $context, array $blocks = array()); + + /** + * Returns a variable from the context. + * + * This method is for internal use only and should never be called + * directly. + * + * This method should not be overridden in a sub-class as this is an + * implementation detail that has been introduced to optimize variable + * access for versions of PHP before 5.4. This is not a way to override + * the way to get a variable value. + * + * @param array $context The context + * @param string $item The variable to return from the context + * @param bool $ignoreStrictCheck Whether to ignore the strict variable check or not + * + * @return mixed The content of the context variable + * + * @throws Twig_Error_Runtime if the variable does not exist and Twig is running in strict mode + * + * @internal + */ + final protected function getContext($context, $item, $ignoreStrictCheck = false) + { + if (!array_key_exists($item, $context)) { + if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { + return; + } + + throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist.', $item), -1, $this->getSourceContext()); + } + + return $context[$item]; + } + + /** + * Returns the attribute value for a given array/object. + * + * @param mixed $object The object or array from where to get the item + * @param mixed $item The item to get from the array or object + * @param array $arguments An array of arguments to pass if the item is an object method + * @param string $type The type of attribute (@see Twig_Template constants) + * @param bool $isDefinedTest Whether this is only a defined check + * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not + * + * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true + * + * @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false + * + * @internal + */ + protected function getAttribute($object, $item, array $arguments = array(), $type = self::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) + { + // array + if (self::METHOD_CALL !== $type) { + $arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item; + + if ((is_array($object) && (isset($object[$arrayItem]) || array_key_exists($arrayItem, $object))) + || ($object instanceof ArrayAccess && isset($object[$arrayItem])) + ) { + if ($isDefinedTest) { + return true; + } + + return $object[$arrayItem]; + } + + if (self::ARRAY_CALL === $type || !is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { + return; + } + + if ($object instanceof ArrayAccess) { + $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, get_class($object)); + } elseif (is_object($object)) { + $message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, get_class($object)); + } elseif (is_array($object)) { + if (empty($object)) { + $message = sprintf('Key "%s" does not exist as the array is empty.', $arrayItem); + } else { + $message = sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); + } + } elseif (self::ARRAY_CALL === $type) { + if (null === $object) { + $message = sprintf('Impossible to access a key ("%s") on a null variable.', $item); + } else { + $message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, gettype($object), $object); + } + } elseif (null === $object) { + $message = sprintf('Impossible to access an attribute ("%s") on a null variable.', $item); + } else { + $message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, gettype($object), $object); + } + + throw new Twig_Error_Runtime($message, -1, $this->getSourceContext()); + } + } + + if (!is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { + return; + } + + if (null === $object) { + $message = sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); + } else { + $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, gettype($object), $object); + } + + throw new Twig_Error_Runtime($message, -1, $this->getSourceContext()); + } + + // object property + if (self::METHOD_CALL !== $type && !$object instanceof self) { // Twig_Template does not have public properties, and we don't want to allow access to internal ones + if (isset($object->$item) || array_key_exists((string) $item, $object)) { + if ($isDefinedTest) { + return true; + } + + if ($this->env->hasExtension('Twig_Extension_Sandbox')) { + $this->env->getExtension('Twig_Extension_Sandbox')->checkPropertyAllowed($object, $item); + } + + return $object->$item; + } + } + + $class = get_class($object); + + // object method + if (!isset(self::$cache[$class])) { + // get_class_methods returns all methods accessible in the scope, but we only want public ones to be accessible in templates + if ($object instanceof self) { + $ref = new ReflectionClass($class); + $methods = array(); + + foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) { + // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment + if ('getenvironment' !== strtolower($refMethod->name)) { + $methods[] = $refMethod->name; + } + } + } else { + $methods = get_class_methods($object); + } + // sort values to have consistent behavior, so that "get" methods win precedence over "is" methods + sort($methods); + + $cache = array(); + + foreach ($methods as $method) { + $cache[$method] = $method; + $cache[$lcName = strtolower($method)] = $method; + + if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + } elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) { + $name = substr($method, 2); + $lcName = substr($lcName, 2); + } else { + continue; + } + + if (!isset($cache[$name])) { + $cache[$name] = $method; + } + if (!isset($cache[$lcName])) { + $cache[$lcName] = $method; + } + } + self::$cache[$class] = $cache; + } + + $call = false; + if (isset(self::$cache[$class][$item])) { + $method = self::$cache[$class][$item]; + } elseif (isset(self::$cache[$class][$lcItem = strtolower($item)])) { + $method = self::$cache[$class][$lcItem]; + } elseif (isset(self::$cache[$class]['__call'])) { + $method = $item; + $call = true; + } else { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { + return; + } + + throw new Twig_Error_Runtime(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), -1, $this->getSourceContext()); + } + + if ($isDefinedTest) { + return true; + } + + if ($this->env->hasExtension('Twig_Extension_Sandbox')) { + $this->env->getExtension('Twig_Extension_Sandbox')->checkMethodAllowed($object, $method); + } + + // Some objects throw exceptions when they have __call, and the method we try + // to call is not supported. If ignoreStrictCheck is true, we should return null. + try { + if (!$arguments) { + $ret = $object->$method(); + } else { + $ret = call_user_func_array(array($object, $method), $arguments); + } + } catch (BadMethodCallException $e) { + if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) { + return; + } + throw $e; + } + + // @deprecated in 1.28 + if ($object instanceof Twig_TemplateInterface) { + $self = $object->getTemplateName() === $this->getTemplateName(); + $message = sprintf('Calling "%s" on template "%s" from template "%s" is deprecated since version 1.28 and won\'t be supported anymore in 2.0.', $method, $object->getTemplateName(), $this->getTemplateName()); + if ('renderBlock' === $method || 'displayBlock' === $method) { + $message .= sprintf(' Use block("%s"%s) instead).', $arguments[0], $self ? '' : ', template'); + } elseif ('hasBlock' === $method) { + $message .= sprintf(' Use "block("%s"%s) is defined" instead).', $arguments[0], $self ? '' : ', template'); + } elseif ('render' === $method || 'display' === $method) { + $message .= sprintf(' Use include("%s") instead).', $object->getTemplateName()); + } + @trigger_error($message, E_USER_DEPRECATED); + + return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset()); + } + + return $ret; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TemplateInterface.php b/src/vendor/twig/twig/lib/Twig/TemplateInterface.php new file mode 100644 index 0000000..207b54d --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TemplateInterface.php @@ -0,0 +1,48 @@ + + * + * @deprecated since 1.12 (to be removed in 3.0) + */ +interface Twig_TemplateInterface +{ + const ANY_CALL = 'any'; + const ARRAY_CALL = 'array'; + const METHOD_CALL = 'method'; + + /** + * Renders the template with the given context and returns it as string. + * + * @param array $context An array of parameters to pass to the template + * + * @return string The rendered template + */ + public function render(array $context); + + /** + * Displays the template with the given context. + * + * @param array $context An array of parameters to pass to the template + * @param array $blocks An array of blocks to pass to the template + */ + public function display(array $context, array $blocks = array()); + + /** + * Returns the bound environment for this template. + * + * @return Twig_Environment + */ + public function getEnvironment(); +} diff --git a/src/vendor/twig/twig/lib/Twig/TemplateWrapper.php b/src/vendor/twig/twig/lib/Twig/TemplateWrapper.php new file mode 100644 index 0000000..2db61cd --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TemplateWrapper.php @@ -0,0 +1,131 @@ + + */ +final class Twig_TemplateWrapper +{ + private $env; + private $template; + + /** + * This method is for internal use only and should never be called + * directly (use Twig_Environment::load() instead). + * + * @internal + */ + public function __construct(Twig_Environment $env, Twig_Template $template) + { + $this->env = $env; + $this->template = $template; + } + + /** + * Renders the template. + * + * @param array $context An array of parameters to pass to the template + * + * @return string The rendered template + */ + public function render($context = array()) + { + return $this->template->render($context); + } + + /** + * Displays the template. + * + * @param array $context An array of parameters to pass to the template + */ + public function display($context = array()) + { + $this->template->display($context); + } + + /** + * Checks if a block is defined. + * + * @param string $name The block name + * @param array $context An array of parameters to pass to the template + * + * @return bool + */ + public function hasBlock($name, $context = array()) + { + return $this->template->hasBlock($name, $context); + } + + /** + * Returns defined block names in the template. + * + * @param array $context An array of parameters to pass to the template + * + * @return string[] An array of defined template block names + */ + public function getBlockNames($context = array()) + { + return $this->template->getBlockNames($context); + } + + /** + * Renders a template block. + * + * @param string $name The block name to render + * @param array $context An array of parameters to pass to the template + * + * @return string The rendered block + */ + public function renderBlock($name, $context = array()) + { + $context = $this->env->mergeGlobals($context); + $level = ob_get_level(); + ob_start(); + try { + $this->template->displayBlock($name, $context); + } catch (Exception $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } catch (Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } + + return ob_get_clean(); + } + + /** + * Displays a template block. + * + * @param string $name The block name to render + * @param array $context An array of parameters to pass to the template + */ + public function displayBlock($name, $context = array()) + { + $this->template->displayBlock($name, $this->env->mergeGlobals($context)); + } + + /** + * @return Twig_Source + */ + public function getSourceContext() + { + return $this->template->getSourceContext(); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Test.php b/src/vendor/twig/twig/lib/Twig/Test.php new file mode 100644 index 0000000..3c2d859 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Test.php @@ -0,0 +1,37 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +abstract class Twig_Test implements Twig_TestInterface, Twig_TestCallableInterface +{ + protected $options; + protected $arguments = array(); + + public function __construct(array $options = array()) + { + $this->options = array_merge(array( + 'callable' => null, + ), $options); + } + + public function getCallable() + { + return $this->options['callable']; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Test/Function.php b/src/vendor/twig/twig/lib/Twig/Test/Function.php new file mode 100644 index 0000000..5e76c71 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Test/Function.php @@ -0,0 +1,38 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Test_Function extends Twig_Test +{ + protected $function; + + public function __construct($function, array $options = array()) + { + $options['callable'] = $function; + + parent::__construct($options); + + $this->function = $function; + } + + public function compile() + { + return $this->function; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Test/IntegrationTestCase.php b/src/vendor/twig/twig/lib/Twig/Test/IntegrationTestCase.php new file mode 100644 index 0000000..93314ea --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Test/IntegrationTestCase.php @@ -0,0 +1,228 @@ + + * @author Karma Dordrak + */ +abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase +{ + /** + * @return string + */ + abstract protected function getFixturesDir(); + + /** + * @return Twig_ExtensionInterface[] + */ + protected function getExtensions() + { + return array(); + } + + /** + * @return Twig_SimpleFilter[] + */ + protected function getTwigFilters() + { + return array(); + } + + /** + * @return Twig_SimpleFunction[] + */ + protected function getTwigFunctions() + { + return array(); + } + + /** + * @return Twig_SimpleTest[] + */ + protected function getTwigTests() + { + return array(); + } + + /** + * @dataProvider getTests + */ + public function testIntegration($file, $message, $condition, $templates, $exception, $outputs) + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs); + } + + /** + * @dataProvider getLegacyTests + * @group legacy + */ + public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs) + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs); + } + + public function getTests($name, $legacyTests = false) + { + $fixturesDir = realpath($this->getFixturesDir()); + $tests = array(); + + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fixturesDir), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if (!preg_match('/\.test$/', $file)) { + continue; + } + + if ($legacyTests xor false !== strpos($file->getRealpath(), '.legacy.test')) { + continue; + } + + $test = file_get_contents($file->getRealpath()); + + if (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*(?:--DATA--\s*(.*))?\s*--EXCEPTION--\s*(.*)/sx', $test, $match)) { + $message = $match[1]; + $condition = $match[2]; + $templates = self::parseTemplates($match[3]); + $exception = $match[5]; + $outputs = array(array(null, $match[4], null, '')); + } elseif (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) { + $message = $match[1]; + $condition = $match[2]; + $templates = self::parseTemplates($match[3]); + $exception = false; + preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, PREG_SET_ORDER); + } else { + throw new InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file))); + } + + $tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs); + } + + if ($legacyTests && empty($tests)) { + // add a dummy test to avoid a PHPUnit message + return array(array('not', '-', '', array(), '', array())); + } + + return $tests; + } + + public function getLegacyTests() + { + return $this->getTests('testLegacyIntegration', true); + } + + protected function doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs) + { + if ($condition) { + eval('$ret = '.$condition.';'); + if (!$ret) { + $this->markTestSkipped($condition); + } + } + + $loader = new Twig_Loader_Array($templates); + + foreach ($outputs as $i => $match) { + $config = array_merge(array( + 'cache' => false, + 'strict_variables' => true, + ), $match[2] ? eval($match[2].';') : array()); + $twig = new Twig_Environment($loader, $config); + $twig->addGlobal('global', 'global'); + foreach ($this->getExtensions() as $extension) { + $twig->addExtension($extension); + } + + foreach ($this->getTwigFilters() as $filter) { + $twig->addFilter($filter); + } + + foreach ($this->getTwigTests() as $test) { + $twig->addTest($test); + } + + foreach ($this->getTwigFunctions() as $function) { + $twig->addFunction($function); + } + + // avoid using the same PHP class name for different cases + // only for PHP 5.2+ + if (PHP_VERSION_ID >= 50300) { + $p = new ReflectionProperty($twig, 'templateClassPrefix'); + $p->setAccessible(true); + $p->setValue($twig, '__TwigTemplate_'.hash('sha256', uniqid(mt_rand(), true), false).'_'); + } + + try { + $template = $twig->loadTemplate('index.twig'); + } catch (Exception $e) { + if (false !== $exception) { + $message = $e->getMessage(); + $this->assertSame(trim($exception), trim(sprintf('%s: %s', get_class($e), $message))); + $last = substr($message, strlen($message) - 1); + $this->assertTrue('.' === $last || '?' === $last, $message, 'Exception message must end with a dot or a question mark.'); + + return; + } + + throw new Twig_Error(sprintf('%s: %s', get_class($e), $e->getMessage()), -1, $file, $e); + } + + try { + $output = trim($template->render(eval($match[1].';')), "\n "); + } catch (Exception $e) { + if (false !== $exception) { + $this->assertSame(trim($exception), trim(sprintf('%s: %s', get_class($e), $e->getMessage()))); + + return; + } + + $e = new Twig_Error(sprintf('%s: %s', get_class($e), $e->getMessage()), -1, $file, $e); + + $output = trim(sprintf('%s: %s', get_class($e), $e->getMessage())); + } + + if (false !== $exception) { + list($class) = explode(':', $exception); + $this->assertThat(null, new PHPUnit_Framework_Constraint_Exception($class)); + } + + $expected = trim($match[3], "\n "); + + if ($expected !== $output) { + printf("Compiled templates that failed on case %d:\n", $i + 1); + + foreach (array_keys($templates) as $name) { + echo "Template: $name\n"; + $loader = $twig->getLoader(); + if (!$loader instanceof Twig_SourceContextLoaderInterface) { + $source = new Twig_Source($loader->getSource($name), $name); + } else { + $source = $loader->getSourceContext($name); + } + echo $twig->compile($twig->parse($twig->tokenize($source))); + } + } + $this->assertEquals($expected, $output, $message.' (in '.$file.')'); + } + } + + protected static function parseTemplates($test) + { + $templates = array(); + preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $templates[($match[1] ? $match[1] : 'index.twig')] = $match[2]; + } + + return $templates; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Test/Method.php b/src/vendor/twig/twig/lib/Twig/Test/Method.php new file mode 100644 index 0000000..69144bf --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Test/Method.php @@ -0,0 +1,40 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Test_Method extends Twig_Test +{ + protected $extension; + protected $method; + + public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array()) + { + $options['callable'] = array($extension, $method); + + parent::__construct($options); + + $this->extension = $extension; + $this->method = $method; + } + + public function compile() + { + return sprintf('$this->env->getExtension(\'%s\')->%s', get_class($this->extension), $this->method); + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Test/Node.php b/src/vendor/twig/twig/lib/Twig/Test/Node.php new file mode 100644 index 0000000..baef49c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Test/Node.php @@ -0,0 +1,40 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Test_Node extends Twig_Test +{ + protected $class; + + public function __construct($class, array $options = array()) + { + parent::__construct($options); + + $this->class = $class; + } + + public function getClass() + { + return $this->class; + } + + public function compile() + { + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Test/NodeTestCase.php b/src/vendor/twig/twig/lib/Twig/Test/NodeTestCase.php new file mode 100644 index 0000000..a6b550c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Test/NodeTestCase.php @@ -0,0 +1,68 @@ +assertNodeCompilation($source, $node, $environment, $isPattern); + } + + public function assertNodeCompilation($source, Twig_Node $node, Twig_Environment $environment = null, $isPattern = false) + { + $compiler = $this->getCompiler($environment); + $compiler->compile($node); + + if ($isPattern) { + $this->assertStringMatchesFormat($source, trim($compiler->getSource())); + } else { + $this->assertEquals($source, trim($compiler->getSource())); + } + } + + protected function getCompiler(Twig_Environment $environment = null) + { + return new Twig_Compiler(null === $environment ? $this->getEnvironment() : $environment); + } + + protected function getEnvironment() + { + return new Twig_Environment(new Twig_Loader_Array(array())); + } + + protected function getVariableGetter($name, $line = false) + { + $line = $line > 0 ? "// line {$line}\n" : ''; + + if (PHP_VERSION_ID >= 70000) { + return sprintf('%s($context["%s"] ?? null)', $line, $name, $name); + } + + if (PHP_VERSION_ID >= 50400) { + return sprintf('%s(isset($context["%s"]) ? $context["%s"] : null)', $line, $name, $name); + } + + return sprintf('%s$this->getContext($context, "%s")', $line, $name); + } + + protected function getAttributeGetter() + { + if (function_exists('twig_template_get_attributes')) { + return 'twig_template_get_attributes($this, '; + } + + return '$this->getAttribute('; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TestCallableInterface.php b/src/vendor/twig/twig/lib/Twig/TestCallableInterface.php new file mode 100644 index 0000000..98d3457 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TestCallableInterface.php @@ -0,0 +1,22 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_TestCallableInterface +{ + public function getCallable(); +} diff --git a/src/vendor/twig/twig/lib/Twig/TestInterface.php b/src/vendor/twig/twig/lib/Twig/TestInterface.php new file mode 100644 index 0000000..2fa821c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TestInterface.php @@ -0,0 +1,27 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_TestInterface +{ + /** + * Compiles a test. + * + * @return string The PHP code for the test + */ + public function compile(); +} diff --git a/src/vendor/twig/twig/lib/Twig/Token.php b/src/vendor/twig/twig/lib/Twig/Token.php new file mode 100644 index 0000000..67b5787 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Token.php @@ -0,0 +1,205 @@ + + * + * @final + */ +class Twig_Token +{ + protected $value; + protected $type; + protected $lineno; + + const EOF_TYPE = -1; + const TEXT_TYPE = 0; + const BLOCK_START_TYPE = 1; + const VAR_START_TYPE = 2; + const BLOCK_END_TYPE = 3; + const VAR_END_TYPE = 4; + const NAME_TYPE = 5; + const NUMBER_TYPE = 6; + const STRING_TYPE = 7; + const OPERATOR_TYPE = 8; + const PUNCTUATION_TYPE = 9; + const INTERPOLATION_START_TYPE = 10; + const INTERPOLATION_END_TYPE = 11; + + /** + * @param int $type The type of the token + * @param string $value The token value + * @param int $lineno The line position in the source + */ + public function __construct($type, $value, $lineno) + { + $this->type = $type; + $this->value = $value; + $this->lineno = $lineno; + } + + public function __toString() + { + return sprintf('%s(%s)', self::typeToString($this->type, true), $this->value); + } + + /** + * Tests the current token for a type and/or a value. + * + * Parameters may be: + * * just type + * * type and value (or array of possible values) + * * just value (or array of possible values) (NAME_TYPE is used as type) + * + * @param array|int $type The type to test + * @param array|string|null $values The token value + * + * @return bool + */ + public function test($type, $values = null) + { + if (null === $values && !is_int($type)) { + $values = $type; + $type = self::NAME_TYPE; + } + + return ($this->type === $type) && ( + null === $values || + (is_array($values) && in_array($this->value, $values)) || + $this->value == $values + ); + } + + /** + * @return int + */ + public function getLine() + { + return $this->lineno; + } + + /** + * @return int + */ + public function getType() + { + return $this->type; + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the constant representation (internal) of a given type. + * + * @param int $type The type as an integer + * @param bool $short Whether to return a short representation or not + * + * @return string The string representation + */ + public static function typeToString($type, $short = false) + { + switch ($type) { + case self::EOF_TYPE: + $name = 'EOF_TYPE'; + break; + case self::TEXT_TYPE: + $name = 'TEXT_TYPE'; + break; + case self::BLOCK_START_TYPE: + $name = 'BLOCK_START_TYPE'; + break; + case self::VAR_START_TYPE: + $name = 'VAR_START_TYPE'; + break; + case self::BLOCK_END_TYPE: + $name = 'BLOCK_END_TYPE'; + break; + case self::VAR_END_TYPE: + $name = 'VAR_END_TYPE'; + break; + case self::NAME_TYPE: + $name = 'NAME_TYPE'; + break; + case self::NUMBER_TYPE: + $name = 'NUMBER_TYPE'; + break; + case self::STRING_TYPE: + $name = 'STRING_TYPE'; + break; + case self::OPERATOR_TYPE: + $name = 'OPERATOR_TYPE'; + break; + case self::PUNCTUATION_TYPE: + $name = 'PUNCTUATION_TYPE'; + break; + case self::INTERPOLATION_START_TYPE: + $name = 'INTERPOLATION_START_TYPE'; + break; + case self::INTERPOLATION_END_TYPE: + $name = 'INTERPOLATION_END_TYPE'; + break; + default: + throw new LogicException(sprintf('Token of type "%s" does not exist.', $type)); + } + + return $short ? $name : 'Twig_Token::'.$name; + } + + /** + * Returns the English representation of a given type. + * + * @param int $type The type as an integer + * + * @return string The string representation + */ + public static function typeToEnglish($type) + { + switch ($type) { + case self::EOF_TYPE: + return 'end of template'; + case self::TEXT_TYPE: + return 'text'; + case self::BLOCK_START_TYPE: + return 'begin of statement block'; + case self::VAR_START_TYPE: + return 'begin of print statement'; + case self::BLOCK_END_TYPE: + return 'end of statement block'; + case self::VAR_END_TYPE: + return 'end of print statement'; + case self::NAME_TYPE: + return 'name'; + case self::NUMBER_TYPE: + return 'number'; + case self::STRING_TYPE: + return 'string'; + case self::OPERATOR_TYPE: + return 'operator'; + case self::PUNCTUATION_TYPE: + return 'punctuation'; + case self::INTERPOLATION_START_TYPE: + return 'begin of string interpolation'; + case self::INTERPOLATION_END_TYPE: + return 'end of string interpolation'; + default: + throw new LogicException(sprintf('Token of type "%s" does not exist.', $type)); + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser.php b/src/vendor/twig/twig/lib/Twig/TokenParser.php new file mode 100644 index 0000000..56d074b --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser.php @@ -0,0 +1,31 @@ + + */ +abstract class Twig_TokenParser implements Twig_TokenParserInterface +{ + /** + * @var Twig_Parser + */ + protected $parser; + + /** + * Sets the parser associated with this token parser. + */ + public function setParser(Twig_Parser $parser) + { + $this->parser = $parser; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/AutoEscape.php b/src/vendor/twig/twig/lib/Twig/TokenParser/AutoEscape.php new file mode 100644 index 0000000..066bf03 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/AutoEscape.php @@ -0,0 +1,81 @@ + + * {% autoescape true %} + * Everything will be automatically escaped in this block + * {% endautoescape %} + * + * {% autoescape false %} + * Everything will be outputed as is in this block + * {% endautoescape %} + * + * {% autoescape true js %} + * Everything will be automatically escaped in this block + * using the js escaping strategy + * {% endautoescape %} + * + * + * @final + */ +class Twig_TokenParser_AutoEscape extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + + if ($stream->test(Twig_Token::BLOCK_END_TYPE)) { + $value = 'html'; + } else { + $expr = $this->parser->getExpressionParser()->parseExpression(); + if (!$expr instanceof Twig_Node_Expression_Constant) { + throw new Twig_Error_Syntax('An escaping strategy must be a string or a bool.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + $value = $expr->getAttribute('value'); + + $compat = true === $value || false === $value; + + if (true === $value) { + $value = 'html'; + } + + if ($compat && $stream->test(Twig_Token::NAME_TYPE)) { + @trigger_error('Using the autoescape tag with "true" or "false" before the strategy name is deprecated since version 1.21.', E_USER_DEPRECATED); + + if (false === $value) { + throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $value = $stream->next()->getValue(); + } + } + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_AutoEscape($value, $body, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Twig_Token $token) + { + return $token->test('endautoescape'); + } + + public function getTag() + { + return 'autoescape'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Block.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Block.php new file mode 100644 index 0000000..dbb9511 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Block.php @@ -0,0 +1,71 @@ + + * {% block head %} + * + * {% block title %}{% endblock %} - My Webpage + * {% endblock %} + * + * + * @final + */ +class Twig_TokenParser_Block extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); + if ($this->parser->hasBlock($name)) { + throw new Twig_Error_Syntax(sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getTemplateLine()), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + $this->parser->setBlock($name, $block = new Twig_Node_Block($name, new Twig_Node(array()), $lineno)); + $this->parser->pushLocalScope(); + $this->parser->pushBlockStack($name); + + if ($stream->nextIf(Twig_Token::BLOCK_END_TYPE)) { + $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + if ($token = $stream->nextIf(Twig_Token::NAME_TYPE)) { + $value = $token->getValue(); + + if ($value != $name) { + throw new Twig_Error_Syntax(sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + } else { + $body = new Twig_Node(array( + new Twig_Node_Print($this->parser->getExpressionParser()->parseExpression(), $lineno), + )); + } + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $block->setNode('body', $body); + $this->parser->popBlockStack(); + $this->parser->popLocalScope(); + + return new Twig_Node_BlockReference($name, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Twig_Token $token) + { + return $token->test('endblock'); + } + + public function getTag() + { + return 'block'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Do.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Do.php new file mode 100644 index 0000000..b7c0bca --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Do.php @@ -0,0 +1,32 @@ +parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_Do($expr, $token->getLine(), $this->getTag()); + } + + public function getTag() + { + return 'do'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Embed.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Embed.php new file mode 100644 index 0000000..95f3e6d --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Embed.php @@ -0,0 +1,65 @@ +parser->getStream(); + + $parent = $this->parser->getExpressionParser()->parseExpression(); + + list($variables, $only, $ignoreMissing) = $this->parseArguments(); + + $parentToken = $fakeParentToken = new Twig_Token(Twig_Token::STRING_TYPE, '__parent__', $token->getLine()); + if ($parent instanceof Twig_Node_Expression_Constant) { + $parentToken = new Twig_Token(Twig_Token::STRING_TYPE, $parent->getAttribute('value'), $token->getLine()); + } elseif ($parent instanceof Twig_Node_Expression_Name) { + $parentToken = new Twig_Token(Twig_Token::NAME_TYPE, $parent->getAttribute('name'), $token->getLine()); + } + + // inject a fake parent to make the parent() function work + $stream->injectTokens(array( + new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', $token->getLine()), + new Twig_Token(Twig_Token::NAME_TYPE, 'extends', $token->getLine()), + $parentToken, + new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', $token->getLine()), + )); + + $module = $this->parser->parse($stream, array($this, 'decideBlockEnd'), true); + + // override the parent with the correct one + if ($fakeParentToken === $parentToken) { + $module->setNode('parent', $parent); + } + + $this->parser->embedTemplate($module); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_Embed($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + } + + public function decideBlockEnd(Twig_Token $token) + { + return $token->test('endembed'); + } + + public function getTag() + { + return 'embed'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Extends.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Extends.php new file mode 100644 index 0000000..fd0fc2e --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Extends.php @@ -0,0 +1,44 @@ + + * {% extends "base.html" %} + * + * + * @final + */ +class Twig_TokenParser_Extends extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $stream = $this->parser->getStream(); + + if (!$this->parser->isMainScope()) { + throw new Twig_Error_Syntax('Cannot extend from a block.', $token->getLine(), $stream->getSourceContext()); + } + + if (null !== $this->parser->getParent()) { + throw new Twig_Error_Syntax('Multiple extends tags are forbidden.', $token->getLine(), $stream->getSourceContext()); + } + $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + } + + public function getTag() + { + return 'extends'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Filter.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Filter.php new file mode 100644 index 0000000..e72676b --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Filter.php @@ -0,0 +1,51 @@ + + * {% filter upper %} + * This text becomes uppercase + * {% endfilter %} + * + * + * @final + */ +class Twig_TokenParser_Filter extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $name = $this->parser->getVarName(); + $ref = new Twig_Node_Expression_BlockReference(new Twig_Node_Expression_Constant($name, $token->getLine()), null, $token->getLine(), $this->getTag()); + + $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag()); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + $block = new Twig_Node_Block($name, $body, $token->getLine()); + $this->parser->setBlock($name, $block); + + return new Twig_Node_Print($filter, $token->getLine(), $this->getTag()); + } + + public function decideBlockEnd(Twig_Token $token) + { + return $token->test('endfilter'); + } + + public function getTag() + { + return 'filter'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Flush.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Flush.php new file mode 100644 index 0000000..f0ec4b2 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Flush.php @@ -0,0 +1,32 @@ +parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_Flush($token->getLine(), $this->getTag()); + } + + public function getTag() + { + return 'flush'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/For.php b/src/vendor/twig/twig/lib/Twig/TokenParser/For.php new file mode 100644 index 0000000..771ebf5 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/For.php @@ -0,0 +1,125 @@ + + *
      + * {% for user in users %} + *
    • {{ user.username|e }}
    • + * {% endfor %} + *
    + * + * + * @final + */ +class Twig_TokenParser_For extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $targets = $this->parser->getExpressionParser()->parseAssignmentExpression(); + $stream->expect(Twig_Token::OPERATOR_TYPE, 'in'); + $seq = $this->parser->getExpressionParser()->parseExpression(); + + $ifexpr = null; + if ($stream->nextIf(Twig_Token::NAME_TYPE, 'if')) { + $ifexpr = $this->parser->getExpressionParser()->parseExpression(); + } + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideForFork')); + if ($stream->next()->getValue() == 'else') { + $stream->expect(Twig_Token::BLOCK_END_TYPE); + $else = $this->parser->subparse(array($this, 'decideForEnd'), true); + } else { + $else = null; + } + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + if (count($targets) > 1) { + $keyTarget = $targets->getNode(0); + $keyTarget = new Twig_Node_Expression_AssignName($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine()); + $valueTarget = $targets->getNode(1); + $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); + } else { + $keyTarget = new Twig_Node_Expression_AssignName('_key', $lineno); + $valueTarget = $targets->getNode(0); + $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); + } + + if ($ifexpr) { + $this->checkLoopUsageCondition($stream, $ifexpr); + $this->checkLoopUsageBody($stream, $body); + } + + return new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, $lineno, $this->getTag()); + } + + public function decideForFork(Twig_Token $token) + { + return $token->test(array('else', 'endfor')); + } + + public function decideForEnd(Twig_Token $token) + { + return $token->test('endfor'); + } + + // the loop variable cannot be used in the condition + protected function checkLoopUsageCondition(Twig_TokenStream $stream, Twig_NodeInterface $node) + { + if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) { + throw new Twig_Error_Syntax('The "loop" variable cannot be used in a looping condition.', $node->getTemplateLine(), $stream->getSourceContext()); + } + + foreach ($node as $n) { + if (!$n) { + continue; + } + + $this->checkLoopUsageCondition($stream, $n); + } + } + + // check usage of non-defined loop-items + // it does not catch all problems (for instance when a for is included into another or when the variable is used in an include) + protected function checkLoopUsageBody(Twig_TokenStream $stream, Twig_NodeInterface $node) + { + if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) { + $attribute = $node->getNode('attribute'); + if ($attribute instanceof Twig_Node_Expression_Constant && in_array($attribute->getAttribute('value'), array('length', 'revindex0', 'revindex', 'last'))) { + throw new Twig_Error_Syntax(sprintf('The "loop.%s" variable is not defined when looping with a condition.', $attribute->getAttribute('value')), $node->getTemplateLine(), $stream->getSourceContext()); + } + } + + // should check for parent.loop.XXX usage + if ($node instanceof Twig_Node_For) { + return; + } + + foreach ($node as $n) { + if (!$n) { + continue; + } + + $this->checkLoopUsageBody($stream, $n); + } + } + + public function getTag() + { + return 'for'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/From.php b/src/vendor/twig/twig/lib/Twig/TokenParser/From.php new file mode 100644 index 0000000..59b3a4b --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/From.php @@ -0,0 +1,64 @@ + + * {% from 'forms.html' import forms %} + * + * + * @final + */ +class Twig_TokenParser_From extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $macro = $this->parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + $stream->expect('import'); + + $targets = array(); + do { + $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); + + $alias = $name; + if ($stream->nextIf('as')) { + $alias = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); + } + + $targets[$name] = $alias; + + if (!$stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) { + break; + } + } while (true); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $node = new Twig_Node_Import($macro, new Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag()); + + foreach ($targets as $name => $alias) { + if ($this->parser->isReservedMacroName($name)) { + throw new Twig_Error_Syntax(sprintf('"%s" cannot be an imported macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()); + } + + $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var')); + } + + return $node; + } + + public function getTag() + { + return 'from'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/If.php b/src/vendor/twig/twig/lib/Twig/TokenParser/If.php new file mode 100644 index 0000000..e249828 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/If.php @@ -0,0 +1,84 @@ + + * {% if users %} + *
      + * {% for user in users %} + *
    • {{ user.username|e }}
    • + * {% endfor %} + *
    + * {% endif %} + * + * + * @final + */ +class Twig_TokenParser_If extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $lineno = $token->getLine(); + $expr = $this->parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideIfFork')); + $tests = array($expr, $body); + $else = null; + + $end = false; + while (!$end) { + switch ($stream->next()->getValue()) { + case 'else': + $stream->expect(Twig_Token::BLOCK_END_TYPE); + $else = $this->parser->subparse(array($this, 'decideIfEnd')); + break; + + case 'elseif': + $expr = $this->parser->getExpressionParser()->parseExpression(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideIfFork')); + $tests[] = $expr; + $tests[] = $body; + break; + + case 'endif': + $end = true; + break; + + default: + throw new Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d).', $lineno), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_If(new Twig_Node($tests), $else, $lineno, $this->getTag()); + } + + public function decideIfFork(Twig_Token $token) + { + return $token->test(array('elseif', 'else', 'endif')); + } + + public function decideIfEnd(Twig_Token $token) + { + return $token->test(array('endif')); + } + + public function getTag() + { + return 'if'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Import.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Import.php new file mode 100644 index 0000000..0de977f --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Import.php @@ -0,0 +1,39 @@ + + * {% import 'forms.html' as forms %} + * + * + * @final + */ +class Twig_TokenParser_Import extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $macro = $this->parser->getExpressionParser()->parseExpression(); + $this->parser->getStream()->expect('as'); + $var = new Twig_Node_Expression_AssignName($this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue(), $token->getLine()); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + $this->parser->addImportedSymbol('template', $var->getAttribute('name')); + + return new Twig_Node_Import($macro, $var, $token->getLine(), $this->getTag()); + } + + public function getTag() + { + return 'import'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Include.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Include.php new file mode 100644 index 0000000..7947082 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Include.php @@ -0,0 +1,65 @@ + + * {% include 'header.html' %} + * Body + * {% include 'footer.html' %} + * + * + * @final + */ +class Twig_TokenParser_Include extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + list($variables, $only, $ignoreMissing) = $this->parseArguments(); + + return new Twig_Node_Include($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + } + + protected function parseArguments() + { + $stream = $this->parser->getStream(); + + $ignoreMissing = false; + if ($stream->nextIf(Twig_Token::NAME_TYPE, 'ignore')) { + $stream->expect(Twig_Token::NAME_TYPE, 'missing'); + + $ignoreMissing = true; + } + + $variables = null; + if ($stream->nextIf(Twig_Token::NAME_TYPE, 'with')) { + $variables = $this->parser->getExpressionParser()->parseExpression(); + } + + $only = false; + if ($stream->nextIf(Twig_Token::NAME_TYPE, 'only')) { + $only = true; + } + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return array($variables, $only, $ignoreMissing); + } + + public function getTag() + { + return 'include'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Macro.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Macro.php new file mode 100644 index 0000000..026c993 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Macro.php @@ -0,0 +1,58 @@ + + * {% macro input(name, value, type, size) %} + * + * {% endmacro %} + * + * + * @final + */ +class Twig_TokenParser_Macro extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); + + $arguments = $this->parser->getExpressionParser()->parseArguments(true, true); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + $this->parser->pushLocalScope(); + $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + if ($token = $stream->nextIf(Twig_Token::NAME_TYPE)) { + $value = $token->getValue(); + + if ($value != $name) { + throw new Twig_Error_Syntax(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + $this->parser->popLocalScope(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $this->parser->setMacro($name, new Twig_Node_Macro($name, new Twig_Node_Body(array($body)), $arguments, $lineno, $this->getTag())); + } + + public function decideBlockEnd(Twig_Token $token) + { + return $token->test('endmacro'); + } + + public function getTag() + { + return 'macro'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Sandbox.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Sandbox.php new file mode 100644 index 0000000..2277f20 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Sandbox.php @@ -0,0 +1,59 @@ + + * {% sandbox %} + * {% include 'user.html' %} + * {% endsandbox %} + * + * + * @see http://www.twig-project.org/doc/api.html#sandbox-extension for details + * + * @final + */ +class Twig_TokenParser_Sandbox extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + // in a sandbox tag, only include tags are allowed + if (!$body instanceof Twig_Node_Include) { + foreach ($body as $node) { + if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) { + continue; + } + + if (!$node instanceof Twig_Node_Include) { + throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section.', $node->getTemplateLine(), $stream->getSourceContext()); + } + } + } + + return new Twig_Node_Sandbox($body, $token->getLine(), $this->getTag()); + } + + public function decideBlockEnd(Twig_Token $token) + { + return $token->test('endsandbox'); + } + + public function getTag() + { + return 'sandbox'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Set.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Set.php new file mode 100644 index 0000000..71d01d1 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Set.php @@ -0,0 +1,73 @@ + + * {% set foo = 'foo' %} + * + * {% set foo = [1, 2] %} + * + * {% set foo = {'foo': 'bar'} %} + * + * {% set foo = 'foo' ~ 'bar' %} + * + * {% set foo, bar = 'foo', 'bar' %} + * + * {% set foo %}Some content{% endset %} + * + * + * @final + */ +class Twig_TokenParser_Set extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $names = $this->parser->getExpressionParser()->parseAssignmentExpression(); + + $capture = false; + if ($stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) { + $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + if (count($names) !== count($values)) { + throw new Twig_Error_Syntax('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } else { + $capture = true; + + if (count($names) > 1) { + throw new Twig_Error_Syntax('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $values = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + } + + return new Twig_Node_Set($capture, $names, $values, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Twig_Token $token) + { + return $token->test('endset'); + } + + public function getTag() + { + return 'set'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Spaceless.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Spaceless.php new file mode 100644 index 0000000..ad315fd --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Spaceless.php @@ -0,0 +1,49 @@ + + * {% spaceless %} + *
    + * foo + *
    + * {% endspaceless %} + * + * {# output will be
    foo
    #} + * + * + * @final + */ +class Twig_TokenParser_Spaceless extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $lineno = $token->getLine(); + + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideSpacelessEnd'), true); + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_Spaceless($body, $lineno, $this->getTag()); + } + + public function decideSpacelessEnd(Twig_Token $token) + { + return $token->test('endspaceless'); + } + + public function getTag() + { + return 'spaceless'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/Use.php b/src/vendor/twig/twig/lib/Twig/TokenParser/Use.php new file mode 100644 index 0000000..87ad8cb --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/Use.php @@ -0,0 +1,66 @@ + + * {% extends "base.html" %} + * + * {% use "blocks.html" %} + * + * {% block title %}{% endblock %} + * {% block content %}{% endblock %} + * + * + * @see http://www.twig-project.org/doc/templates.html#horizontal-reuse for details. + * + * @final + */ +class Twig_TokenParser_Use extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $template = $this->parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + + if (!$template instanceof Twig_Node_Expression_Constant) { + throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $targets = array(); + if ($stream->nextIf('with')) { + do { + $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); + + $alias = $name; + if ($stream->nextIf('as')) { + $alias = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); + } + + $targets[$name] = new Twig_Node_Expression_Constant($alias, -1); + + if (!$stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) { + break; + } + } while (true); + } + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $this->parser->addTrait(new Twig_Node(array('template' => $template, 'targets' => new Twig_Node($targets)))); + } + + public function getTag() + { + return 'use'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParser/With.php b/src/vendor/twig/twig/lib/Twig/TokenParser/With.php new file mode 100644 index 0000000..8273133 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParser/With.php @@ -0,0 +1,50 @@ + + * + * @final + */ +class Twig_TokenParser_With extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $stream = $this->parser->getStream(); + + $variables = null; + $only = false; + if (!$stream->test(Twig_Token::BLOCK_END_TYPE)) { + $variables = $this->parser->getExpressionParser()->parseExpression(); + $only = $stream->nextIf(Twig_Token::NAME_TYPE, 'only'); + } + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $body = $this->parser->subparse(array($this, 'decideWithEnd'), true); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_With($body, $variables, $only, $token->getLine(), $this->getTag()); + } + + public function decideWithEnd(Twig_Token $token) + { + return $token->test('endwith'); + } + + public function getTag() + { + return 'with'; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParserBroker.php b/src/vendor/twig/twig/lib/Twig/TokenParserBroker.php new file mode 100644 index 0000000..0349a1c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParserBroker.php @@ -0,0 +1,120 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface +{ + protected $parser; + protected $parsers = array(); + protected $brokers = array(); + + /** + * @param array|Traversable $parsers A Traversable of Twig_TokenParserInterface instances + * @param array|Traversable $brokers A Traversable of Twig_TokenParserBrokerInterface instances + * @param bool $triggerDeprecationError + */ + public function __construct($parsers = array(), $brokers = array(), $triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__CLASS__.' class is deprecated since version 1.12 and will be removed in 2.0.', E_USER_DEPRECATED); + } + + foreach ($parsers as $parser) { + if (!$parser instanceof Twig_TokenParserInterface) { + throw new LogicException('$parsers must a an array of Twig_TokenParserInterface.'); + } + $this->parsers[$parser->getTag()] = $parser; + } + foreach ($brokers as $broker) { + if (!$broker instanceof Twig_TokenParserBrokerInterface) { + throw new LogicException('$brokers must a an array of Twig_TokenParserBrokerInterface.'); + } + $this->brokers[] = $broker; + } + } + + public function addTokenParser(Twig_TokenParserInterface $parser) + { + $this->parsers[$parser->getTag()] = $parser; + } + + public function removeTokenParser(Twig_TokenParserInterface $parser) + { + $name = $parser->getTag(); + if (isset($this->parsers[$name]) && $parser === $this->parsers[$name]) { + unset($this->parsers[$name]); + } + } + + public function addTokenParserBroker(Twig_TokenParserBroker $broker) + { + $this->brokers[] = $broker; + } + + public function removeTokenParserBroker(Twig_TokenParserBroker $broker) + { + if (false !== $pos = array_search($broker, $this->brokers)) { + unset($this->brokers[$pos]); + } + } + + /** + * Gets a suitable TokenParser for a tag. + * + * First looks in parsers, then in brokers. + * + * @param string $tag A tag name + * + * @return null|Twig_TokenParserInterface A Twig_TokenParserInterface or null if no suitable TokenParser was found + */ + public function getTokenParser($tag) + { + if (isset($this->parsers[$tag])) { + return $this->parsers[$tag]; + } + $broker = end($this->brokers); + while (false !== $broker) { + $parser = $broker->getTokenParser($tag); + if (null !== $parser) { + return $parser; + } + $broker = prev($this->brokers); + } + } + + public function getParsers() + { + return $this->parsers; + } + + public function getParser() + { + return $this->parser; + } + + public function setParser(Twig_ParserInterface $parser) + { + $this->parser = $parser; + foreach ($this->parsers as $tokenParser) { + $tokenParser->setParser($parser); + } + foreach ($this->brokers as $broker) { + $broker->setParser($parser); + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParserBrokerInterface.php b/src/vendor/twig/twig/lib/Twig/TokenParserBrokerInterface.php new file mode 100644 index 0000000..b84179c --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParserBrokerInterface.php @@ -0,0 +1,44 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_TokenParserBrokerInterface +{ + /** + * Gets a TokenParser suitable for a tag. + * + * @param string $tag A tag name + * + * @return Twig_TokenParserInterface|null A Twig_TokenParserInterface or null if no suitable TokenParser was found + */ + public function getTokenParser($tag); + + /** + * Calls Twig_TokenParserInterface::setParser on all parsers the implementation knows of. + */ + public function setParser(Twig_ParserInterface $parser); + + /** + * Gets the Twig_ParserInterface. + * + * @return null|Twig_ParserInterface A Twig_ParserInterface instance or null + */ + public function getParser(); +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenParserInterface.php b/src/vendor/twig/twig/lib/Twig/TokenParserInterface.php new file mode 100644 index 0000000..d35f8ff --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenParserInterface.php @@ -0,0 +1,39 @@ + + */ +interface Twig_TokenParserInterface +{ + /** + * Sets the parser associated with this token parser. + */ + public function setParser(Twig_Parser $parser); + + /** + * Parses a token and returns a node. + * + * @return Twig_NodeInterface + * + * @throws Twig_Error_Syntax + */ + public function parse(Twig_Token $token); + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag(); +} diff --git a/src/vendor/twig/twig/lib/Twig/TokenStream.php b/src/vendor/twig/twig/lib/Twig/TokenStream.php new file mode 100644 index 0000000..a1bcf44 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/TokenStream.php @@ -0,0 +1,194 @@ + + */ +class Twig_TokenStream +{ + protected $tokens; + protected $current = 0; + protected $filename; + + private $source; + + /** + * @param array $tokens An array of tokens + * @param string|null $name The name of the template which tokens are associated with + * @param string|null $source The source code associated with the tokens + */ + public function __construct(array $tokens, $name = null, $source = null) + { + if (!$name instanceof Twig_Source) { + if (null !== $name || null !== $source) { + @trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); + } + $this->source = new Twig_Source($source, $name); + } else { + $this->source = $name; + } + + $this->tokens = $tokens; + + // deprecated, not used anymore, to be removed in 2.0 + $this->filename = $this->source->getName(); + } + + public function __toString() + { + return implode("\n", $this->tokens); + } + + public function injectTokens(array $tokens) + { + $this->tokens = array_merge(array_slice($this->tokens, 0, $this->current), $tokens, array_slice($this->tokens, $this->current)); + } + + /** + * Sets the pointer to the next token and returns the old one. + * + * @return Twig_Token + */ + public function next() + { + if (!isset($this->tokens[++$this->current])) { + throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source); + } + + return $this->tokens[$this->current - 1]; + } + + /** + * Tests a token, sets the pointer to the next one and returns it or throws a syntax error. + * + * @return Twig_Token|null The next token if the condition is true, null otherwise + */ + public function nextIf($primary, $secondary = null) + { + if ($this->tokens[$this->current]->test($primary, $secondary)) { + return $this->next(); + } + } + + /** + * Tests a token and returns it or throws a syntax error. + * + * @return Twig_Token + */ + public function expect($type, $value = null, $message = null) + { + $token = $this->tokens[$this->current]; + if (!$token->test($type, $value)) { + $line = $token->getLine(); + throw new Twig_Error_Syntax(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s).', + $message ? $message.'. ' : '', + Twig_Token::typeToEnglish($token->getType()), $token->getValue(), + Twig_Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''), + $line, + $this->source + ); + } + $this->next(); + + return $token; + } + + /** + * Looks at the next token. + * + * @param int $number + * + * @return Twig_Token + */ + public function look($number = 1) + { + if (!isset($this->tokens[$this->current + $number])) { + throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source); + } + + return $this->tokens[$this->current + $number]; + } + + /** + * Tests the current token. + * + * @return bool + */ + public function test($primary, $secondary = null) + { + return $this->tokens[$this->current]->test($primary, $secondary); + } + + /** + * Checks if end of stream was reached. + * + * @return bool + */ + public function isEOF() + { + return $this->tokens[$this->current]->getType() === Twig_Token::EOF_TYPE; + } + + /** + * @return Twig_Token + */ + public function getCurrent() + { + return $this->tokens[$this->current]; + } + + /** + * Gets the name associated with this stream (null if not defined). + * + * @return string|null + * + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function getFilename() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->source->getName(); + } + + /** + * Gets the source code associated with this stream. + * + * @return string + * + * @internal Don't use this as it might be empty depending on the environment configuration + * + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function getSource() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->source->getCode(); + } + + /** + * Gets the source associated with this stream. + * + * @return Twig_Source + * + * @internal + */ + public function getSourceContext() + { + return $this->source; + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Util/DeprecationCollector.php b/src/vendor/twig/twig/lib/Twig/Util/DeprecationCollector.php new file mode 100644 index 0000000..1e517f5 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Util/DeprecationCollector.php @@ -0,0 +1,84 @@ + + * + * @final + */ +class Twig_Util_DeprecationCollector +{ + private $twig; + private $deprecations; + + public function __construct(Twig_Environment $twig) + { + $this->twig = $twig; + } + + /** + * Returns deprecations for templates contained in a directory. + * + * @param string $dir A directory where templates are stored + * @param string $ext Limit the loaded templates by extension + * + * @return array An array of deprecations + */ + public function collectDir($dir, $ext = '.twig') + { + $iterator = new RegexIterator( + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::LEAVES_ONLY + ), '{'.preg_quote($ext).'$}' + ); + + return $this->collect(new Twig_Util_TemplateDirIterator($iterator)); + } + + /** + * Returns deprecations for passed templates. + * + * @param Iterator $iterator An iterator of templates (where keys are template names and values the contents of the template) + * + * @return array An array of deprecations + */ + public function collect(Iterator $iterator) + { + $this->deprecations = array(); + + set_error_handler(array($this, 'errorHandler')); + + foreach ($iterator as $name => $contents) { + try { + $this->twig->parse($this->twig->tokenize($contents, $name)); + } catch (Twig_Error_Syntax $e) { + // ignore templates containing syntax errors + } + } + + restore_error_handler(); + + $deprecations = $this->deprecations; + $this->deprecations = array(); + + return $deprecations; + } + + /** + * @internal + */ + public function errorHandler($type, $msg) + { + if (E_USER_DEPRECATED === $type) { + $this->deprecations[] = $msg; + } + } +} diff --git a/src/vendor/twig/twig/lib/Twig/Util/TemplateDirIterator.php b/src/vendor/twig/twig/lib/Twig/Util/TemplateDirIterator.php new file mode 100644 index 0000000..3fb8932 --- /dev/null +++ b/src/vendor/twig/twig/lib/Twig/Util/TemplateDirIterator.php @@ -0,0 +1,26 @@ + + */ +class Twig_Util_TemplateDirIterator extends IteratorIterator +{ + public function current() + { + return file_get_contents(parent::current()); + } + + public function key() + { + return (string) parent::key(); + } +} diff --git a/src/vendor/twig/twig/phpunit.xml.dist b/src/vendor/twig/twig/phpunit.xml.dist new file mode 100644 index 0000000..ce77327 --- /dev/null +++ b/src/vendor/twig/twig/phpunit.xml.dist @@ -0,0 +1,33 @@ + + + + + + ./test/Twig/ + + + + + + + + + + + + + + ./lib/Twig/ + + + diff --git a/src/vendor/twig/twig/test/Twig/Tests/AutoloaderTest.php b/src/vendor/twig/twig/test/Twig/Tests/AutoloaderTest.php new file mode 100644 index 0000000..52107c0 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/AutoloaderTest.php @@ -0,0 +1,24 @@ +assertFalse(class_exists('FooBarFoo'), '->autoload() does not try to load classes that does not begin with Twig'); + + $autoloader = new Twig_Autoloader(); + $this->assertNull($autoloader->autoload('Foo'), '->autoload() returns false if it is not able to load a class'); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Cache/FilesystemTest.php b/src/vendor/twig/twig/test/Twig/Tests/Cache/FilesystemTest.php new file mode 100644 index 0000000..5103ef7 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Cache/FilesystemTest.php @@ -0,0 +1,193 @@ +classname = '__Twig_Tests_Cache_FilesystemTest_Template_'.$nonce; + $this->directory = sys_get_temp_dir().'/twig-test'; + $this->cache = new Twig_Cache_Filesystem($this->directory); + } + + protected function tearDown() + { + if (file_exists($this->directory)) { + Twig_Tests_FilesystemHelper::removeDir($this->directory); + } + } + + public function testLoad() + { + $key = $this->directory.'/cache/cachefile.php'; + + $dir = dirname($key); + @mkdir($dir, 0777, true); + $this->assertTrue(is_dir($dir)); + $this->assertFalse(class_exists($this->classname, false)); + + $content = $this->generateSource(); + file_put_contents($key, $content); + + $this->cache->load($key); + + $this->assertTrue(class_exists($this->classname, false)); + } + + public function testLoadMissing() + { + $key = $this->directory.'/cache/cachefile.php'; + + $this->assertFalse(class_exists($this->classname, false)); + + $this->cache->load($key); + + $this->assertFalse(class_exists($this->classname, false)); + } + + public function testWrite() + { + $key = $this->directory.'/cache/cachefile.php'; + $content = $this->generateSource(); + + $this->assertFalse(file_exists($key)); + $this->assertFalse(file_exists($this->directory)); + + $this->cache->write($key, $content); + + $this->assertTrue(file_exists($this->directory)); + $this->assertTrue(file_exists($key)); + $this->assertSame(file_get_contents($key), $content); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Unable to create the cache directory + */ + public function testWriteFailMkdir() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Read-only directories not possible on Windows.'); + } + + $key = $this->directory.'/cache/cachefile.php'; + $content = $this->generateSource(); + + $this->assertFileNotExists($key); + + // Create read-only root directory. + @mkdir($this->directory, 0555, true); + $this->assertTrue(is_dir($this->directory)); + + $this->cache->write($key, $content); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Unable to write in the cache directory + */ + public function testWriteFailDirWritable() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Read-only directories not possible on Windows.'); + } + + $key = $this->directory.'/cache/cachefile.php'; + $content = $this->generateSource(); + + $this->assertFileNotExists($key); + + // Create root directory. + @mkdir($this->directory, 0777, true); + // Create read-only subdirectory. + @mkdir($this->directory.'/cache', 0555); + $this->assertTrue(is_dir($this->directory.'/cache')); + + $this->cache->write($key, $content); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Failed to write cache file + */ + public function testWriteFailWriteFile() + { + $key = $this->directory.'/cache/cachefile.php'; + $content = $this->generateSource(); + + $this->assertFileNotExists($key); + + // Create a directory in the place of the cache file. + @mkdir($key, 0777, true); + $this->assertTrue(is_dir($key)); + + $this->cache->write($key, $content); + } + + public function testGetTimestamp() + { + $key = $this->directory.'/cache/cachefile.php'; + + $dir = dirname($key); + @mkdir($dir, 0777, true); + $this->assertTrue(is_dir($dir)); + + // Create the file with a specific modification time. + touch($key, 1234567890); + + $this->assertSame(1234567890, $this->cache->getTimestamp($key)); + } + + public function testGetTimestampMissingFile() + { + $key = $this->directory.'/cache/cachefile.php'; + $this->assertSame(0, $this->cache->getTimestamp($key)); + } + + /** + * Test file cache is tolerant towards trailing (back)slashes on the configured cache directory. + * + * @dataProvider provideDirectories + */ + public function testGenerateKey($expected, $input) + { + $cache = new Twig_Cache_Filesystem($input); + $this->assertRegExp($expected, $cache->generateKey('_test_', get_class($this))); + } + + public function provideDirectories() + { + $pattern = '#a/b/[a-zA-Z0-9]+/[a-zA-Z0-9]+.php$#'; + + return array( + array($pattern, 'a/b'), + array($pattern, 'a/b/'), + array($pattern, 'a/b\\'), + array($pattern, 'a/b\\/'), + array($pattern, 'a/b\\//'), + array('#/'.substr($pattern, 1), '/a/b'), + ); + } + + private function generateSource() + { + return strtr(' $this->classname, + )); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/CompilerTest.php b/src/vendor/twig/twig/test/Twig/Tests/CompilerTest.php new file mode 100644 index 0000000..da7f533 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/CompilerTest.php @@ -0,0 +1,33 @@ +getMockBuilder('Twig_LoaderInterface')->getMock())); + + $locale = setlocale(LC_NUMERIC, 0); + if (false === $locale) { + $this->markTestSkipped('Your platform does not support locales.'); + } + + $required_locales = array('fr_FR.UTF-8', 'fr_FR.UTF8', 'fr_FR.utf-8', 'fr_FR.utf8', 'French_France.1252'); + if (false === setlocale(LC_NUMERIC, $required_locales)) { + $this->markTestSkipped('Could not set any of required locales: '.implode(', ', $required_locales)); + } + + $this->assertEquals('1.2', $compiler->repr(1.2)->getSource()); + $this->assertContains('fr', strtolower(setlocale(LC_NUMERIC, 0))); + + setlocale(LC_NUMERIC, $locale); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/CustomExtensionTest.php b/src/vendor/twig/twig/test/Twig/Tests/CustomExtensionTest.php new file mode 100644 index 0000000..9c298f4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/CustomExtensionTest.php @@ -0,0 +1,88 @@ +setExpectedException('InvalidArgumentException', $expectedExceptionMessage); + + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $env->addExtension($extension); + $env->getUnaryOperators(); + } + + public function provideInvalidExtensions() + { + return array( + array(new InvalidOperatorExtension(new stdClass()), '"InvalidOperatorExtension::getOperators()" must return an array with operators, got "stdClass".'), + array(new InvalidOperatorExtension(array(1, 2, 3)), '"InvalidOperatorExtension::getOperators()" must return an array of 2 elements, got 3.'), + ); + } +} + +class InvalidOperatorExtension implements Twig_ExtensionInterface +{ + private $operators; + + public function __construct($operators) + { + $this->operators = $operators; + } + + public function initRuntime(Twig_Environment $environment) + { + } + + public function getTokenParsers() + { + return array(); + } + + public function getNodeVisitors() + { + return array(); + } + + public function getFilters() + { + return array(); + } + + public function getTests() + { + return array(); + } + + public function getFunctions() + { + return array(); + } + + public function getGlobals() + { + return array(); + } + + public function getOperators() + { + return $this->operators; + } + + public function getName() + { + return __CLASS__; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/EnvironmentTest.php b/src/vendor/twig/twig/test/Twig/Tests/EnvironmentTest.php new file mode 100644 index 0000000..b5dfd63 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/EnvironmentTest.php @@ -0,0 +1,652 @@ +tokenize('{{ foo }}', 'foo'); + $this->assertEquals('{{ foo }}', $stream->getSource()); + $this->assertEquals('foo', $stream->getFilename()); + } + + /** + * @group legacy + */ + public function testLegacyCompileSourceSignature() + { + $loader = new Twig_Loader_Array(array('foo' => '{{ foo }}')); + $env = new Twig_Environment($loader); + $this->assertContains('getTemplateName', $env->compileSource('{{ foo }}', 'foo')); + } + + /** + * @expectedException LogicException + * @expectedExceptionMessage You must set a loader first. + * @group legacy + */ + public function testRenderNoLoader() + { + $env = new Twig_Environment(); + $env->render('test'); + } + + public function testAutoescapeOption() + { + $loader = new Twig_Loader_Array(array( + 'html' => '{{ foo }} {{ foo }}', + 'js' => '{{ bar }} {{ bar }}', + )); + + $twig = new Twig_Environment($loader, array( + 'debug' => true, + 'cache' => false, + 'autoescape' => array($this, 'escapingStrategyCallback'), + )); + + $this->assertEquals('foo<br/ > foo<br/ >', $twig->render('html', array('foo' => 'foo
    '))); + $this->assertEquals('foo\x3Cbr\x2F\x20\x3E foo\x3Cbr\x2F\x20\x3E', $twig->render('js', array('bar' => 'foo
    '))); + } + + public function escapingStrategyCallback($name) + { + return $name; + } + + public function testGlobals() + { + // to be removed in 2.0 + $loader = $this->getMockBuilder('Twig_EnvironmentTestLoaderInterface')->getMock(); + //$loader = $this->getMockBuilder(array('Twig_LoaderInterface', 'Twig_SourceContextLoaderInterface'))->getMock(); + $loader->expects($this->any())->method('getSourceContext')->will($this->returnValue(new Twig_Source('', ''))); + + // globals can be added after calling getGlobals + + $twig = new Twig_Environment($loader); + $twig->addGlobal('foo', 'foo'); + $twig->getGlobals(); + $twig->addGlobal('foo', 'bar'); + $globals = $twig->getGlobals(); + $this->assertEquals('bar', $globals['foo']); + + // globals can be modified after a template has been loaded + $twig = new Twig_Environment($loader); + $twig->addGlobal('foo', 'foo'); + $twig->getGlobals(); + $twig->loadTemplate('index'); + $twig->addGlobal('foo', 'bar'); + $globals = $twig->getGlobals(); + $this->assertEquals('bar', $globals['foo']); + + // globals can be modified after extensions init + $twig = new Twig_Environment($loader); + $twig->addGlobal('foo', 'foo'); + $twig->getGlobals(); + $twig->getFunctions(); + $twig->addGlobal('foo', 'bar'); + $globals = $twig->getGlobals(); + $this->assertEquals('bar', $globals['foo']); + + // globals can be modified after extensions and a template has been loaded + $arrayLoader = new Twig_Loader_Array(array('index' => '{{foo}}')); + $twig = new Twig_Environment($arrayLoader); + $twig->addGlobal('foo', 'foo'); + $twig->getGlobals(); + $twig->getFunctions(); + $twig->loadTemplate('index'); + $twig->addGlobal('foo', 'bar'); + $globals = $twig->getGlobals(); + $this->assertEquals('bar', $globals['foo']); + + $twig = new Twig_Environment($arrayLoader); + $twig->getGlobals(); + $twig->addGlobal('foo', 'bar'); + $template = $twig->loadTemplate('index'); + $this->assertEquals('bar', $template->render(array())); + + /* to be uncomment in Twig 2.0 + // globals cannot be added after a template has been loaded + $twig = new Twig_Environment($loader); + $twig->addGlobal('foo', 'foo'); + $twig->getGlobals(); + $twig->loadTemplate('index'); + try { + $twig->addGlobal('bar', 'bar'); + $this->fail(); + } catch (LogicException $e) { + $this->assertFalse(array_key_exists('bar', $twig->getGlobals())); + } + + // globals cannot be added after extensions init + $twig = new Twig_Environment($loader); + $twig->addGlobal('foo', 'foo'); + $twig->getGlobals(); + $twig->getFunctions(); + try { + $twig->addGlobal('bar', 'bar'); + $this->fail(); + } catch (LogicException $e) { + $this->assertFalse(array_key_exists('bar', $twig->getGlobals())); + } + + // globals cannot be added after extensions and a template has been loaded + $twig = new Twig_Environment($loader); + $twig->addGlobal('foo', 'foo'); + $twig->getGlobals(); + $twig->getFunctions(); + $twig->loadTemplate('index'); + try { + $twig->addGlobal('bar', 'bar'); + $this->fail(); + } catch (LogicException $e) { + $this->assertFalse(array_key_exists('bar', $twig->getGlobals())); + } + + // test adding globals after a template has been loaded without call to getGlobals + $twig = new Twig_Environment($loader); + $twig->loadTemplate('index'); + try { + $twig->addGlobal('bar', 'bar'); + $this->fail(); + } catch (LogicException $e) { + $this->assertFalse(array_key_exists('bar', $twig->getGlobals())); + } + */ + } + + public function testExtensionsAreNotInitializedWhenRenderingACompiledTemplate() + { + $cache = new Twig_Cache_Filesystem($dir = sys_get_temp_dir().'/twig'); + $options = array('cache' => $cache, 'auto_reload' => false, 'debug' => false); + + // force compilation + $twig = new Twig_Environment($loader = new Twig_Loader_Array(array('index' => '{{ foo }}')), $options); + + $key = $cache->generateKey('index', $twig->getTemplateClass('index')); + $cache->write($key, $twig->compileSource(new Twig_Source('{{ foo }}', 'index'))); + + // check that extensions won't be initialized when rendering a template that is already in the cache + $twig = $this + ->getMockBuilder('Twig_Environment') + ->setConstructorArgs(array($loader, $options)) + ->setMethods(array('initExtensions')) + ->getMock() + ; + + $twig->expects($this->never())->method('initExtensions'); + + // render template + $output = $twig->render('index', array('foo' => 'bar')); + $this->assertEquals('bar', $output); + + Twig_Tests_FilesystemHelper::removeDir($dir); + } + + public function testAutoReloadCacheMiss() + { + $templateName = __FUNCTION__; + $templateContent = __FUNCTION__; + + $cache = $this->getMockBuilder('Twig_CacheInterface')->getMock(); + $loader = $this->getMockLoader($templateName, $templateContent); + $twig = new Twig_Environment($loader, array('cache' => $cache, 'auto_reload' => true, 'debug' => false)); + + // Cache miss: getTimestamp returns 0 and as a result the load() is + // skipped. + $cache->expects($this->once()) + ->method('generateKey') + ->will($this->returnValue('key')); + $cache->expects($this->once()) + ->method('getTimestamp') + ->will($this->returnValue(0)); + $loader->expects($this->never()) + ->method('isFresh'); + $cache->expects($this->once()) + ->method('write'); + $cache->expects($this->once()) + ->method('load'); + + $twig->loadTemplate($templateName); + } + + public function testAutoReloadCacheHit() + { + $templateName = __FUNCTION__; + $templateContent = __FUNCTION__; + + $cache = $this->getMockBuilder('Twig_CacheInterface')->getMock(); + $loader = $this->getMockLoader($templateName, $templateContent); + $twig = new Twig_Environment($loader, array('cache' => $cache, 'auto_reload' => true, 'debug' => false)); + + $now = time(); + + // Cache hit: getTimestamp returns something > extension timestamps and + // the loader returns true for isFresh(). + $cache->expects($this->once()) + ->method('generateKey') + ->will($this->returnValue('key')); + $cache->expects($this->once()) + ->method('getTimestamp') + ->will($this->returnValue($now)); + $loader->expects($this->once()) + ->method('isFresh') + ->will($this->returnValue(true)); + $cache->expects($this->atLeastOnce()) + ->method('load'); + + $twig->loadTemplate($templateName); + } + + public function testAutoReloadOutdatedCacheHit() + { + $templateName = __FUNCTION__; + $templateContent = __FUNCTION__; + + $cache = $this->getMockBuilder('Twig_CacheInterface')->getMock(); + $loader = $this->getMockLoader($templateName, $templateContent); + $twig = new Twig_Environment($loader, array('cache' => $cache, 'auto_reload' => true, 'debug' => false)); + + $now = time(); + + $cache->expects($this->once()) + ->method('generateKey') + ->will($this->returnValue('key')); + $cache->expects($this->once()) + ->method('getTimestamp') + ->will($this->returnValue($now)); + $loader->expects($this->once()) + ->method('isFresh') + ->will($this->returnValue(false)); + $cache->expects($this->once()) + ->method('write'); + $cache->expects($this->once()) + ->method('load'); + + $twig->loadTemplate($templateName); + } + + /** + * @group legacy + */ + public function testHasGetExtensionWithDynamicName() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + + $ext1 = new Twig_Tests_EnvironmentTest_Extension_DynamicWithDeprecatedName('ext1'); + $ext2 = new Twig_Tests_EnvironmentTest_Extension_DynamicWithDeprecatedName('ext2'); + $twig->addExtension($ext1); + $twig->addExtension($ext2); + + $this->assertTrue($twig->hasExtension('ext1')); + $this->assertTrue($twig->hasExtension('ext2')); + + $this->assertTrue($twig->hasExtension('Twig_Tests_EnvironmentTest_Extension_DynamicWithDeprecatedName')); + + $this->assertSame($ext1, $twig->getExtension('ext1')); + $this->assertSame($ext2, $twig->getExtension('ext2')); + } + + public function testHasGetExtensionByClassName() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig->addExtension($ext = new Twig_Tests_EnvironmentTest_Extension()); + $this->assertTrue($twig->hasExtension('Twig_Tests_EnvironmentTest_Extension')); + $this->assertTrue($twig->hasExtension('\Twig_Tests_EnvironmentTest_Extension')); + + $this->assertSame($ext, $twig->getExtension('Twig_Tests_EnvironmentTest_Extension')); + $this->assertSame($ext, $twig->getExtension('\Twig_Tests_EnvironmentTest_Extension')); + } + + public function testAddExtension() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig->addExtension(new Twig_Tests_EnvironmentTest_Extension()); + + $this->assertArrayHasKey('test', $twig->getTags()); + $this->assertArrayHasKey('foo_filter', $twig->getFilters()); + $this->assertArrayHasKey('foo_function', $twig->getFunctions()); + $this->assertArrayHasKey('foo_test', $twig->getTests()); + $this->assertArrayHasKey('foo_unary', $twig->getUnaryOperators()); + $this->assertArrayHasKey('foo_binary', $twig->getBinaryOperators()); + $this->assertArrayHasKey('foo_global', $twig->getGlobals()); + $visitors = $twig->getNodeVisitors(); + $found = false; + foreach ($visitors as $visitor) { + if ($visitor instanceof Twig_Tests_EnvironmentTest_NodeVisitor) { + $found = true; + } + } + $this->assertTrue($found); + } + + /** + * @requires PHP 5.3 + */ + public function testAddExtensionWithDeprecatedGetGlobals() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig->addExtension(new Twig_Tests_EnvironmentTest_Extension_WithGlobals()); + + $this->deprecations = array(); + set_error_handler(array($this, 'handleError')); + + $this->assertArrayHasKey('foo_global', $twig->getGlobals()); + + $this->assertCount(1, $this->deprecations); + $this->assertContains('Defining the getGlobals() method in the "Twig_Tests_EnvironmentTest_Extension_WithGlobals" extension ', $this->deprecations[0]); + + restore_error_handler(); + } + + /** + * @group legacy + */ + public function testRemoveExtension() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig->addExtension(new Twig_Tests_EnvironmentTest_Extension_WithDeprecatedName()); + $twig->removeExtension('environment_test'); + + $this->assertArrayNotHasKey('test', $twig->getTags()); + $this->assertArrayNotHasKey('foo_filter', $twig->getFilters()); + $this->assertArrayNotHasKey('foo_function', $twig->getFunctions()); + $this->assertArrayNotHasKey('foo_test', $twig->getTests()); + $this->assertArrayNotHasKey('foo_unary', $twig->getUnaryOperators()); + $this->assertArrayNotHasKey('foo_binary', $twig->getBinaryOperators()); + $this->assertArrayNotHasKey('foo_global', $twig->getGlobals()); + $this->assertCount(2, $twig->getNodeVisitors()); + } + + public function testAddMockExtension() + { + // should be replaced by the following in 2.0 (this current code is just to avoid a dep notice) + // $extension = $this->getMockBuilder('Twig_Extension')->getMock(); + $extension = eval(<< 'hey')); + + $twig = new Twig_Environment($loader); + $twig->addExtension($extension); + + $this->assertInstanceOf('Twig_ExtensionInterface', $twig->getExtension(get_class($extension))); + $this->assertTrue($twig->isTemplateFresh('page', time())); + } + + public function testInitRuntimeWithAnExtensionUsingInitRuntimeNoDeprecation() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig->addExtension(new Twig_Tests_EnvironmentTest_ExtensionWithoutDeprecationInitRuntime()); + + $twig->initRuntime(); + } + + /** + * @requires PHP 5.3 + */ + public function testInitRuntimeWithAnExtensionUsingInitRuntimeDeprecation() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig->addExtension(new Twig_Tests_EnvironmentTest_ExtensionWithDeprecationInitRuntime()); + + $this->deprecations = array(); + set_error_handler(array($this, 'handleError')); + + $twig->initRuntime(); + + $this->assertCount(1, $this->deprecations); + $this->assertContains('Defining the initRuntime() method in the "Twig_Tests_EnvironmentTest_ExtensionWithDeprecationInitRuntime" extension is deprecated since version 1.23.', $this->deprecations[0]); + + restore_error_handler(); + } + + public function handleError($type, $msg) + { + if (E_USER_DEPRECATED === $type) { + $this->deprecations[] = $msg; + } + } + + /** + * @requires PHP 5.3 + */ + public function testOverrideExtension() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig->addExtension(new Twig_Tests_EnvironmentTest_ExtensionWithDeprecationInitRuntime()); + + $this->deprecations = array(); + set_error_handler(array($this, 'handleError')); + + $twig->addExtension(new Twig_Tests_EnvironmentTest_Extension_WithDeprecatedName()); + $twig->addExtension(new Twig_Tests_EnvironmentTest_Extension_WithDeprecatedName()); + + $this->assertCount(1, $this->deprecations); + $this->assertContains('The possibility to register the same extension twice', $this->deprecations[0]); + + restore_error_handler(); + } + + public function testAddRuntimeLoader() + { + $runtimeLoader = $this->getMockBuilder('Twig_RuntimeLoaderInterface')->getMock(); + $runtimeLoader->expects($this->any())->method('load')->will($this->returnValue(new Twig_Tests_EnvironmentTest_Runtime())); + + $loader = new Twig_Loader_Array(array( + 'func_array' => '{{ from_runtime_array("foo") }}', + 'func_array_default' => '{{ from_runtime_array() }}', + 'func_array_named_args' => '{{ from_runtime_array(name="foo") }}', + 'func_string' => '{{ from_runtime_string("foo") }}', + 'func_string_default' => '{{ from_runtime_string() }}', + 'func_string_named_args' => '{{ from_runtime_string(name="foo") }}', + )); + + $twig = new Twig_Environment($loader); + $twig->addExtension(new Twig_Tests_EnvironmentTest_ExtensionWithoutRuntime()); + $twig->addRuntimeLoader($runtimeLoader); + + $this->assertEquals('foo', $twig->render('func_array')); + $this->assertEquals('bar', $twig->render('func_array_default')); + $this->assertEquals('foo', $twig->render('func_array_named_args')); + $this->assertEquals('foo', $twig->render('func_string')); + $this->assertEquals('bar', $twig->render('func_string_default')); + $this->assertEquals('foo', $twig->render('func_string_named_args')); + } + + protected function getMockLoader($templateName, $templateContent) + { + // to be removed in 2.0 + $loader = $this->getMockBuilder('Twig_EnvironmentTestLoaderInterface')->getMock(); + //$loader = $this->getMockBuilder(array('Twig_LoaderInterface', 'Twig_SourceContextLoaderInterface'))->getMock(); + $loader->expects($this->any()) + ->method('getSourceContext') + ->with($templateName) + ->will($this->returnValue(new Twig_Source($templateContent, $templateName))); + $loader->expects($this->any()) + ->method('getCacheKey') + ->with($templateName) + ->will($this->returnValue($templateName)); + + return $loader; + } +} + +class Twig_Tests_EnvironmentTest_Extension_WithGlobals extends Twig_Extension +{ + public function getGlobals() + { + return array( + 'foo_global' => 'foo_global', + ); + } +} + +class Twig_Tests_EnvironmentTest_Extension extends Twig_Extension implements Twig_Extension_GlobalsInterface +{ + public function getTokenParsers() + { + return array( + new Twig_Tests_EnvironmentTest_TokenParser(), + ); + } + + public function getNodeVisitors() + { + return array( + new Twig_Tests_EnvironmentTest_NodeVisitor(), + ); + } + + public function getFilters() + { + return array( + new Twig_SimpleFilter('foo_filter', 'foo_filter'), + ); + } + + public function getTests() + { + return array( + new Twig_SimpleTest('foo_test', 'foo_test'), + ); + } + + public function getFunctions() + { + return array( + new Twig_SimpleFunction('foo_function', 'foo_function'), + ); + } + + public function getOperators() + { + return array( + array('foo_unary' => array()), + array('foo_binary' => array()), + ); + } + + public function getGlobals() + { + return array( + 'foo_global' => 'foo_global', + ); + } +} + +class Twig_Tests_EnvironmentTest_Extension_WithDeprecatedName extends Twig_Extension +{ + public function getName() + { + return 'environment_test'; + } +} + +class Twig_Tests_EnvironmentTest_Extension_DynamicWithDeprecatedName extends Twig_Extension +{ + private $name; + + public function __construct($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } +} + +class Twig_Tests_EnvironmentTest_TokenParser extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + } + + public function getTag() + { + return 'test'; + } +} + +class Twig_Tests_EnvironmentTest_NodeVisitor implements Twig_NodeVisitorInterface +{ + public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + { + return $node; + } + + public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + { + return $node; + } + + public function getPriority() + { + return 0; + } +} + +class Twig_Tests_EnvironmentTest_ExtensionWithDeprecationInitRuntime extends Twig_Extension +{ + public function initRuntime(Twig_Environment $env) + { + } +} + +class Twig_Tests_EnvironmentTest_ExtensionWithoutDeprecationInitRuntime extends Twig_Extension implements Twig_Extension_InitRuntimeInterface +{ + public function initRuntime(Twig_Environment $env) + { + } +} + +class Twig_Tests_EnvironmentTest_ExtensionWithoutRuntime extends Twig_Extension +{ + public function getFunctions() + { + return array( + new Twig_SimpleFunction('from_runtime_array', array('Twig_Tests_EnvironmentTest_Runtime', 'fromRuntime')), + new Twig_SimpleFunction('from_runtime_string', 'Twig_Tests_EnvironmentTest_Runtime::fromRuntime'), + ); + } + + public function getName() + { + return 'from_runtime'; + } +} + +class Twig_Tests_EnvironmentTest_Runtime +{ + public function fromRuntime($name = 'bar') + { + return $name; + } +} + +// to be removed in 2.0 +interface Twig_EnvironmentTestLoaderInterface extends Twig_LoaderInterface, Twig_SourceContextLoaderInterface +{ +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/ErrorTest.php b/src/vendor/twig/twig/test/Twig/Tests/ErrorTest.php new file mode 100644 index 0000000..b0eeca2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/ErrorTest.php @@ -0,0 +1,211 @@ +setSourceContext(new Twig_Source('', new SplFileInfo(__FILE__))); + + $this->assertContains('test'.DIRECTORY_SEPARATOR.'Twig'.DIRECTORY_SEPARATOR.'Tests'.DIRECTORY_SEPARATOR.'ErrorTest.php', $error->getMessage()); + } + + public function testErrorWithArrayFilename() + { + $error = new Twig_Error('foo'); + $error->setSourceContext(new Twig_Source('', array('foo' => 'bar'))); + + $this->assertEquals('foo in {"foo":"bar"}', $error->getMessage()); + } + + public function testTwigExceptionGuessWithMissingVarAndArrayLoader() + { + $loader = new Twig_Loader_Array(array( + 'base.html' => '{% block content %}{% endblock %}', + 'index.html' => << true, 'debug' => true, 'cache' => false)); + + $template = $twig->loadTemplate('index.html'); + try { + $template->render(array()); + + $this->fail(); + } catch (Twig_Error_Runtime $e) { + $this->assertEquals('Variable "foo" does not exist in "index.html" at line 3.', $e->getMessage()); + $this->assertEquals(3, $e->getTemplateLine()); + $this->assertEquals('index.html', $e->getSourceContext()->getName()); + } + } + + public function testTwigExceptionGuessWithExceptionAndArrayLoader() + { + $loader = new Twig_Loader_Array(array( + 'base.html' => '{% block content %}{% endblock %}', + 'index.html' => << true, 'debug' => true, 'cache' => false)); + + $template = $twig->loadTemplate('index.html'); + try { + $template->render(array('foo' => new Twig_Tests_ErrorTest_Foo())); + + $this->fail(); + } catch (Twig_Error_Runtime $e) { + $this->assertEquals('An exception has been thrown during the rendering of a template ("Runtime error...") in "index.html" at line 3.', $e->getMessage()); + $this->assertEquals(3, $e->getTemplateLine()); + $this->assertEquals('index.html', $e->getSourceContext()->getName()); + } + } + + public function testTwigExceptionGuessWithMissingVarAndFilesystemLoader() + { + $loader = new Twig_Loader_Filesystem(dirname(__FILE__).'/Fixtures/errors'); + $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false)); + + $template = $twig->loadTemplate('index.html'); + try { + $template->render(array()); + + $this->fail(); + } catch (Twig_Error_Runtime $e) { + $this->assertEquals('Variable "foo" does not exist.', $e->getMessage()); + $this->assertEquals(3, $e->getTemplateLine()); + $this->assertEquals('index.html', $e->getSourceContext()->getName()); + $this->assertEquals(3, $e->getLine()); + $this->assertEquals(strtr(dirname(__FILE__).'/Fixtures/errors/index.html', '/', DIRECTORY_SEPARATOR), $e->getFile()); + } + } + + public function testTwigExceptionGuessWithExceptionAndFilesystemLoader() + { + $loader = new Twig_Loader_Filesystem(dirname(__FILE__).'/Fixtures/errors'); + $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false)); + + $template = $twig->loadTemplate('index.html'); + try { + $template->render(array('foo' => new Twig_Tests_ErrorTest_Foo())); + + $this->fail(); + } catch (Twig_Error_Runtime $e) { + $this->assertEquals('An exception has been thrown during the rendering of a template ("Runtime error...").', $e->getMessage()); + $this->assertEquals(3, $e->getTemplateLine()); + $this->assertEquals('index.html', $e->getSourceContext()->getName()); + $this->assertEquals(3, $e->getLine()); + $this->assertEquals(strtr(dirname(__FILE__).'/Fixtures/errors/index.html', '/', DIRECTORY_SEPARATOR), $e->getFile()); + } + } + + /** + * @dataProvider getErroredTemplates + */ + public function testTwigExceptionAddsFileAndLine($templates, $name, $line) + { + $loader = new Twig_Loader_Array($templates); + $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false)); + + $template = $twig->loadTemplate('index'); + + try { + $template->render(array()); + + $this->fail(); + } catch (Twig_Error_Runtime $e) { + $this->assertEquals(sprintf('Variable "foo" does not exist in "%s" at line %d.', $name, $line), $e->getMessage()); + $this->assertEquals($line, $e->getTemplateLine()); + $this->assertEquals($name, $e->getSourceContext()->getName()); + } + + try { + $template->render(array('foo' => new Twig_Tests_ErrorTest_Foo())); + + $this->fail(); + } catch (Twig_Error_Runtime $e) { + $this->assertEquals(sprintf('An exception has been thrown during the rendering of a template ("Runtime error...") in "%s" at line %d.', $name, $line), $e->getMessage()); + $this->assertEquals($line, $e->getTemplateLine()); + $this->assertEquals($name, $e->getSourceContext()->getName()); + } + } + + public function getErroredTemplates() + { + return array( + // error occurs in a template + array( + array( + 'index' => "\n\n{{ foo.bar }}\n\n\n{{ 'foo' }}", + ), + 'index', 3, + ), + + // error occurs in an included template + array( + array( + 'index' => "{% include 'partial' %}", + 'partial' => '{{ foo.bar }}', + ), + 'partial', 1, + ), + + // error occurs in a parent block when called via parent() + array( + array( + 'index' => "{% extends 'base' %} + {% block content %} + {{ parent() }} + {% endblock %}", + 'base' => '{% block content %}{{ foo.bar }}{% endblock %}', + ), + 'base', 1, + ), + + // error occurs in a block from the child + array( + array( + 'index' => "{% extends 'base' %} + {% block content %} + {{ foo.bar }} + {% endblock %} + {% block foo %} + {{ foo.bar }} + {% endblock %}", + 'base' => '{% block content %}{% endblock %}', + ), + 'index', 3, + ), + ); + } +} + +class Twig_Tests_ErrorTest_Foo +{ + public function bar() + { + throw new Exception('Runtime error...'); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/ExpressionParserTest.php b/src/vendor/twig/twig/test/Twig/Tests/ExpressionParserTest.php new file mode 100644 index 0000000..bcb75c2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/ExpressionParserTest.php @@ -0,0 +1,373 @@ +getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize(new Twig_Source($template, 'index'))); + } + + public function getFailingTestsForAssignment() + { + return array( + array('{% set false = "foo" %}'), + array('{% set FALSE = "foo" %}'), + array('{% set true = "foo" %}'), + array('{% set TRUE = "foo" %}'), + array('{% set none = "foo" %}'), + array('{% set NONE = "foo" %}'), + array('{% set null = "foo" %}'), + array('{% set NULL = "foo" %}'), + array('{% set 3 = "foo" %}'), + array('{% set 1 + 2 = "foo" %}'), + array('{% set "bar" = "foo" %}'), + array('{% set %}{% endset %}'), + ); + } + + /** + * @dataProvider getTestsForArray + */ + public function testArrayExpression($template, $expected) + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $stream = $env->tokenize(new Twig_Source($template, '')); + $parser = new Twig_Parser($env); + + $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr')); + } + + /** + * @expectedException Twig_Error_Syntax + * @dataProvider getFailingTestsForArray + */ + public function testArraySyntaxError($template) + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize(new Twig_Source($template, 'index'))); + } + + public function getFailingTestsForArray() + { + return array( + array('{{ [1, "a": "b"] }}'), + array('{{ {"a": "b", 2} }}'), + ); + } + + public function getTestsForArray() + { + return array( + // simple array + array('{{ [1, 2] }}', new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant(0, 1), + new Twig_Node_Expression_Constant(1, 1), + + new Twig_Node_Expression_Constant(1, 1), + new Twig_Node_Expression_Constant(2, 1), + ), 1), + ), + + // array with trailing , + array('{{ [1, 2, ] }}', new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant(0, 1), + new Twig_Node_Expression_Constant(1, 1), + + new Twig_Node_Expression_Constant(1, 1), + new Twig_Node_Expression_Constant(2, 1), + ), 1), + ), + + // simple hash + array('{{ {"a": "b", "b": "c"} }}', new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant('a', 1), + new Twig_Node_Expression_Constant('b', 1), + + new Twig_Node_Expression_Constant('b', 1), + new Twig_Node_Expression_Constant('c', 1), + ), 1), + ), + + // hash with trailing , + array('{{ {"a": "b", "b": "c", } }}', new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant('a', 1), + new Twig_Node_Expression_Constant('b', 1), + + new Twig_Node_Expression_Constant('b', 1), + new Twig_Node_Expression_Constant('c', 1), + ), 1), + ), + + // hash in an array + array('{{ [1, {"a": "b", "b": "c"}] }}', new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant(0, 1), + new Twig_Node_Expression_Constant(1, 1), + + new Twig_Node_Expression_Constant(1, 1), + new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant('a', 1), + new Twig_Node_Expression_Constant('b', 1), + + new Twig_Node_Expression_Constant('b', 1), + new Twig_Node_Expression_Constant('c', 1), + ), 1), + ), 1), + ), + + // array in a hash + array('{{ {"a": [1, 2], "b": "c"} }}', new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant('a', 1), + new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant(0, 1), + new Twig_Node_Expression_Constant(1, 1), + + new Twig_Node_Expression_Constant(1, 1), + new Twig_Node_Expression_Constant(2, 1), + ), 1), + new Twig_Node_Expression_Constant('b', 1), + new Twig_Node_Expression_Constant('c', 1), + ), 1), + ), + ); + } + + /** + * @expectedException Twig_Error_Syntax + */ + public function testStringExpressionDoesNotConcatenateTwoConsecutiveStrings() + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false, 'optimizations' => 0)); + $stream = $env->tokenize(new Twig_Source('{{ "a" "b" }}', 'index')); + $parser = new Twig_Parser($env); + + $parser->parse($stream); + } + + /** + * @dataProvider getTestsForString + */ + public function testStringExpression($template, $expected) + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false, 'optimizations' => 0)); + $stream = $env->tokenize(new Twig_Source($template, '')); + $parser = new Twig_Parser($env); + + $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr')); + } + + public function getTestsForString() + { + return array( + array( + '{{ "foo" }}', new Twig_Node_Expression_Constant('foo', 1), + ), + array( + '{{ "foo #{bar}" }}', new Twig_Node_Expression_Binary_Concat( + new Twig_Node_Expression_Constant('foo ', 1), + new Twig_Node_Expression_Name('bar', 1), + 1 + ), + ), + array( + '{{ "foo #{bar} baz" }}', new Twig_Node_Expression_Binary_Concat( + new Twig_Node_Expression_Binary_Concat( + new Twig_Node_Expression_Constant('foo ', 1), + new Twig_Node_Expression_Name('bar', 1), + 1 + ), + new Twig_Node_Expression_Constant(' baz', 1), + 1 + ), + ), + + array( + '{{ "foo #{"foo #{bar} baz"} baz" }}', new Twig_Node_Expression_Binary_Concat( + new Twig_Node_Expression_Binary_Concat( + new Twig_Node_Expression_Constant('foo ', 1), + new Twig_Node_Expression_Binary_Concat( + new Twig_Node_Expression_Binary_Concat( + new Twig_Node_Expression_Constant('foo ', 1), + new Twig_Node_Expression_Name('bar', 1), + 1 + ), + new Twig_Node_Expression_Constant(' baz', 1), + 1 + ), + 1 + ), + new Twig_Node_Expression_Constant(' baz', 1), + 1 + ), + ), + ); + } + + /** + * @expectedException Twig_Error_Syntax + */ + public function testAttributeCallDoesNotSupportNamedArguments() + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize(new Twig_Source('{{ foo.bar(name="Foo") }}', 'index'))); + } + + /** + * @expectedException Twig_Error_Syntax + */ + public function testMacroCallDoesNotSupportNamedArguments() + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize(new Twig_Source('{% from _self import foo %}{% macro foo() %}{% endmacro %}{{ foo(name="Foo") }}', 'index'))); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage An argument must be a name. Unexpected token "string" of value "a" ("name" expected) in "index" at line 1. + */ + public function testMacroDefinitionDoesNotSupportNonNameVariableName() + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize(new Twig_Source('{% macro foo("a") %}{% endmacro %}', 'index'))); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage A default value for an argument must be a constant (a boolean, a string, a number, or an array) in "index" at line 1 + * @dataProvider getMacroDefinitionDoesNotSupportNonConstantDefaultValues + */ + public function testMacroDefinitionDoesNotSupportNonConstantDefaultValues($template) + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize(new Twig_Source($template, 'index'))); + } + + public function getMacroDefinitionDoesNotSupportNonConstantDefaultValues() + { + return array( + array('{% macro foo(name = "a #{foo} a") %}{% endmacro %}'), + array('{% macro foo(name = [["b", "a #{foo} a"]]) %}{% endmacro %}'), + ); + } + + /** + * @dataProvider getMacroDefinitionSupportsConstantDefaultValues + */ + public function testMacroDefinitionSupportsConstantDefaultValues($template) + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize(new Twig_Source($template, 'index'))); + } + + public function getMacroDefinitionSupportsConstantDefaultValues() + { + return array( + array('{% macro foo(name = "aa") %}{% endmacro %}'), + array('{% macro foo(name = 12) %}{% endmacro %}'), + array('{% macro foo(name = true) %}{% endmacro %}'), + array('{% macro foo(name = ["a"]) %}{% endmacro %}'), + array('{% macro foo(name = [["a"]]) %}{% endmacro %}'), + array('{% macro foo(name = {a: "a"}) %}{% endmacro %}'), + array('{% macro foo(name = {a: {b: "a"}}) %}{% endmacro %}'), + ); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unknown "cycl" function. Did you mean "cycle" in "index" at line 1? + */ + public function testUnknownFunction() + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize(new Twig_Source('{{ cycl() }}', 'index'))); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unknown "foobar" function in "index" at line 1. + */ + public function testUnknownFunctionWithoutSuggestions() + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize(new Twig_Source('{{ foobar() }}', 'index'))); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unknown "lowe" filter. Did you mean "lower" in "index" at line 1? + */ + public function testUnknownFilter() + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize(new Twig_Source('{{ 1|lowe }}', 'index'))); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unknown "foobar" filter in "index" at line 1. + */ + public function testUnknownFilterWithoutSuggestions() + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize(new Twig_Source('{{ 1|foobar }}', 'index'))); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unknown "nul" test. Did you mean "null" in "index" at line 1 + */ + public function testUnknownTest() + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + $stream = $env->tokenize(new Twig_Source('{{ 1 is nul }}', 'index')); + $parser->parse($stream); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unknown "foobar" test in "index" at line 1. + */ + public function testUnknownTestWithoutSuggestions() + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize(new Twig_Source('{{ 1 is foobar }}', 'index'))); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Extension/CoreTest.php b/src/vendor/twig/twig/test/Twig/Tests/Extension/CoreTest.php new file mode 100644 index 0000000..89937b7 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Extension/CoreTest.php @@ -0,0 +1,158 @@ +getMockBuilder('Twig_LoaderInterface')->getMock()); + + for ($i = 0; $i < 100; ++$i) { + $this->assertTrue(in_array(twig_random($env, $value), $expectedInArray, true)); // assertContains() would not consider the type + } + } + + public function getRandomFunctionTestData() + { + return array( + array(// array + array('apple', 'orange', 'citrus'), + array('apple', 'orange', 'citrus'), + ), + array(// Traversable + new ArrayObject(array('apple', 'orange', 'citrus')), + array('apple', 'orange', 'citrus'), + ), + array(// unicode string + 'Ä€é', + array('Ä', '€', 'é'), + ), + array(// numeric but string + '123', + array('1', '2', '3'), + ), + array(// integer + 5, + range(0, 5, 1), + ), + array(// float + 5.9, + range(0, 5, 1), + ), + array(// negative + -2, + array(0, -1, -2), + ), + ); + } + + public function testRandomFunctionWithoutParameter() + { + $max = mt_getrandmax(); + + for ($i = 0; $i < 100; ++$i) { + $val = twig_random(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $this->assertTrue(is_int($val) && $val >= 0 && $val <= $max); + } + } + + public function testRandomFunctionReturnsAsIs() + { + $this->assertSame('', twig_random(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()), '')); + $this->assertSame('', twig_random(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('charset' => null)), '')); + + $instance = new stdClass(); + $this->assertSame($instance, twig_random(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()), $instance)); + } + + /** + * @expectedException Twig_Error_Runtime + */ + public function testRandomFunctionOfEmptyArrayThrowsException() + { + twig_random(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()), array()); + } + + public function testRandomFunctionOnNonUTF8String() + { + if (!function_exists('iconv') && !function_exists('mb_convert_encoding')) { + $this->markTestSkipped('needs iconv or mbstring'); + } + + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig->setCharset('ISO-8859-1'); + + $text = twig_convert_encoding('Äé', 'ISO-8859-1', 'UTF-8'); + for ($i = 0; $i < 30; ++$i) { + $rand = twig_random($twig, $text); + $this->assertTrue(in_array(twig_convert_encoding($rand, 'UTF-8', 'ISO-8859-1'), array('Ä', 'é'), true)); + } + } + + public function testReverseFilterOnNonUTF8String() + { + if (!function_exists('iconv') && !function_exists('mb_convert_encoding')) { + $this->markTestSkipped('needs iconv or mbstring'); + } + + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig->setCharset('ISO-8859-1'); + + $input = twig_convert_encoding('Äé', 'ISO-8859-1', 'UTF-8'); + $output = twig_convert_encoding(twig_reverse_filter($twig, $input), 'UTF-8', 'ISO-8859-1'); + + $this->assertEquals($output, 'éÄ'); + } + + public function testCustomEscaper() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig->getExtension('Twig_Extension_Core')->setEscaper('foo', 'foo_escaper_for_test'); + + $this->assertEquals('fooUTF-8', twig_escape_filter($twig, 'foo', 'foo')); + $this->assertEquals('UTF-8', twig_escape_filter($twig, null, 'foo')); + $this->assertEquals('42UTF-8', twig_escape_filter($twig, 42, 'foo')); + } + + /** + * @expectedException Twig_Error_Runtime + */ + public function testUnknownCustomEscaper() + { + twig_escape_filter(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()), 'foo', 'bar'); + } + + public function testTwigFirst() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $this->assertEquals('a', twig_first($twig, 'abc')); + $this->assertEquals(1, twig_first($twig, array(1, 2, 3))); + $this->assertSame('', twig_first($twig, null)); + $this->assertSame('', twig_first($twig, '')); + } + + public function testTwigLast() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $this->assertEquals('c', twig_last($twig, 'abc')); + $this->assertEquals(3, twig_last($twig, array(1, 2, 3))); + $this->assertSame('', twig_last($twig, null)); + $this->assertSame('', twig_last($twig, '')); + } +} + +function foo_escaper_for_test(Twig_Environment $env, $string, $charset) +{ + return $string.$charset; +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Extension/SandboxTest.php b/src/vendor/twig/twig/test/Twig/Tests/Extension/SandboxTest.php new file mode 100644 index 0000000..0b21582 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Extension/SandboxTest.php @@ -0,0 +1,304 @@ + 'Fabien', + 'obj' => new FooObject(), + 'arr' => array('obj' => new FooObject()), + ); + + self::$templates = array( + '1_basic1' => '{{ obj.foo }}', + '1_basic2' => '{{ name|upper }}', + '1_basic3' => '{% if name %}foo{% endif %}', + '1_basic4' => '{{ obj.bar }}', + '1_basic5' => '{{ obj }}', + '1_basic6' => '{{ arr.obj }}', + '1_basic7' => '{{ cycle(["foo","bar"], 1) }}', + '1_basic8' => '{{ obj.getfoobar }}{{ obj.getFooBar }}', + '1_basic9' => '{{ obj.foobar }}{{ obj.fooBar }}', + '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', + '1_layout' => '{% block content %}{% endblock %}', + '1_child' => "{% extends \"1_layout\" %}\n{% block content %}\n{{ \"a\"|json_encode }}\n{% endblock %}", + '1_include' => '{{ include("1_basic1", sandboxed=true) }}', + ); + } + + /** + * @expectedException Twig_Sandbox_SecurityError + * @expectedExceptionMessage Filter "json_encode" is not allowed in "1_child" at line 3. + */ + public function testSandboxWithInheritance() + { + $twig = $this->getEnvironment(true, array(), self::$templates, array('block')); + $twig->loadTemplate('1_child')->render(array()); + } + + public function testSandboxGloballySet() + { + $twig = $this->getEnvironment(false, array(), self::$templates); + $this->assertEquals('FOO', $twig->loadTemplate('1_basic')->render(self::$params), 'Sandbox does nothing if it is disabled globally'); + } + + public function testSandboxUnallowedMethodAccessor() + { + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic1')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed method is called'); + } catch (Twig_Sandbox_SecurityError $e) { + $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedMethodError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedMethodError'); + $this->assertEquals('FooObject', $e->getClassName(), 'Exception should be raised on the "FooObject" class'); + $this->assertEquals('foo', $e->getMethodName(), 'Exception should be raised on the "foo" method'); + } + } + + public function testSandboxUnallowedFilter() + { + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic2')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed filter is called'); + } catch (Twig_Sandbox_SecurityError $e) { + $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedFilterError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedFilterError'); + $this->assertEquals('upper', $e->getFilterName(), 'Exception should be raised on the "upper" filter'); + } + } + + public function testSandboxUnallowedTag() + { + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic3')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed tag is used in the template'); + } catch (Twig_Sandbox_SecurityError $e) { + $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedTagError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedTagError'); + $this->assertEquals('if', $e->getTagName(), 'Exception should be raised on the "if" tag'); + } + } + + public function testSandboxUnallowedProperty() + { + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic4')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed property is called in the template'); + } catch (Twig_Sandbox_SecurityError $e) { + $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedPropertyError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedPropertyError'); + $this->assertEquals('FooObject', $e->getClassName(), 'Exception should be raised on the "FooObject" class'); + $this->assertEquals('bar', $e->getPropertyName(), 'Exception should be raised on the "bar" property'); + } + } + + public function testSandboxUnallowedToString() + { + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic5')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template'); + } catch (Twig_Sandbox_SecurityError $e) { + $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedMethodError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedMethodError'); + $this->assertEquals('FooObject', $e->getClassName(), 'Exception should be raised on the "FooObject" class'); + $this->assertEquals('__tostring', $e->getMethodName(), 'Exception should be raised on the "__toString" method'); + } + } + + public function testSandboxUnallowedToStringArray() + { + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic6')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template'); + } catch (Twig_Sandbox_SecurityError $e) { + $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedMethodError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedMethodError'); + $this->assertEquals('FooObject', $e->getClassName(), 'Exception should be raised on the "FooObject" class'); + $this->assertEquals('__tostring', $e->getMethodName(), 'Exception should be raised on the "__toString" method'); + } + } + + public function testSandboxUnallowedFunction() + { + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic7')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed function is called in the template'); + } catch (Twig_Sandbox_SecurityError $e) { + $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedFunctionError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedFunctionError'); + $this->assertEquals('cycle', $e->getFunctionName(), 'Exception should be raised on the "cycle" function'); + } + } + + public function testSandboxAllowMethodFoo() + { + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => 'foo')); + FooObject::reset(); + $this->assertEquals('foo', $twig->loadTemplate('1_basic1')->render(self::$params), 'Sandbox allow some methods'); + $this->assertEquals(1, FooObject::$called['foo'], 'Sandbox only calls method once'); + } + + public function testSandboxAllowMethodToString() + { + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => '__toString')); + FooObject::reset(); + $this->assertEquals('foo', $twig->loadTemplate('1_basic5')->render(self::$params), 'Sandbox allow some methods'); + $this->assertEquals(1, FooObject::$called['__toString'], 'Sandbox only calls method once'); + } + + public function testSandboxAllowMethodToStringDisabled() + { + $twig = $this->getEnvironment(false, array(), self::$templates); + FooObject::reset(); + $this->assertEquals('foo', $twig->loadTemplate('1_basic5')->render(self::$params), 'Sandbox allows __toString when sandbox disabled'); + $this->assertEquals(1, FooObject::$called['__toString'], 'Sandbox only calls method once'); + } + + public function testSandboxAllowFilter() + { + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array('upper')); + $this->assertEquals('FABIEN', $twig->loadTemplate('1_basic2')->render(self::$params), 'Sandbox allow some filters'); + } + + public function testSandboxAllowTag() + { + $twig = $this->getEnvironment(true, array(), self::$templates, array('if')); + $this->assertEquals('foo', $twig->loadTemplate('1_basic3')->render(self::$params), 'Sandbox allow some tags'); + } + + public function testSandboxAllowProperty() + { + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array('FooObject' => 'bar')); + $this->assertEquals('bar', $twig->loadTemplate('1_basic4')->render(self::$params), 'Sandbox allow some properties'); + } + + public function testSandboxAllowFunction() + { + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array(), array('cycle')); + $this->assertEquals('bar', $twig->loadTemplate('1_basic7')->render(self::$params), 'Sandbox allow some functions'); + } + + public function testSandboxAllowFunctionsCaseInsensitive() + { + foreach (array('getfoobar', 'getFoobar', 'getFooBar') as $name) { + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => $name)); + FooObject::reset(); + $this->assertEquals('foobarfoobar', $twig->loadTemplate('1_basic8')->render(self::$params), 'Sandbox allow methods in a case-insensitive way'); + $this->assertEquals(2, FooObject::$called['getFooBar'], 'Sandbox only calls method once'); + + $this->assertEquals('foobarfoobar', $twig->loadTemplate('1_basic9')->render(self::$params), 'Sandbox allow methods via shortcut names (ie. without get/set)'); + } + } + + public function testSandboxLocallySetForAnInclude() + { + self::$templates = array( + '2_basic' => '{{ obj.foo }}{% include "2_included" %}{{ obj.foo }}', + '2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', + ); + + $twig = $this->getEnvironment(false, array(), self::$templates); + $this->assertEquals('fooFOOfoo', $twig->loadTemplate('2_basic')->render(self::$params), 'Sandbox does nothing if disabled globally and sandboxed not used for the include'); + + self::$templates = array( + '3_basic' => '{{ obj.foo }}{% sandbox %}{% include "3_included" %}{% endsandbox %}{{ obj.foo }}', + '3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', + ); + + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('3_basic')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception when the included file is sandboxed'); + } catch (Twig_Sandbox_SecurityError $e) { + $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedTagError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedTagError'); + $this->assertEquals('sandbox', $e->getTagName()); + } + } + + public function testMacrosInASandbox() + { + $twig = $this->getEnvironment(true, array('autoescape' => 'html'), array('index' => <<{{ text }}

    {% endmacro %} + +{{- macros.test('username') }} +EOF + ), array('macro', 'import'), array('escape')); + + $this->assertEquals('

    username

    ', $twig->loadTemplate('index')->render(array())); + } + + public function testSandboxDisabledAfterIncludeFunctionError() + { + $twig = $this->getEnvironment(false, array(), self::$templates); + + $e = null; + try { + $twig->loadTemplate('1_include')->render(self::$params); + } catch (Throwable $e) { + } catch (Exception $e) { + } + if ($e === null) { + $this->fail('An exception should be thrown for this test to be valid.'); + } + + $this->assertFalse($twig->getExtension('Twig_Extension_Sandbox')->isSandboxed(), 'Sandboxed include() function call should not leave Sandbox enabled when an error occurs.'); + } + + protected function getEnvironment($sandboxed, $options, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array(), $functions = array()) + { + $loader = new Twig_Loader_Array($templates); + $twig = new Twig_Environment($loader, array_merge(array('debug' => true, 'cache' => false, 'autoescape' => false), $options)); + $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions); + $twig->addExtension(new Twig_Extension_Sandbox($policy, $sandboxed)); + + return $twig; + } +} + +class FooObject +{ + public static $called = array('__toString' => 0, 'foo' => 0, 'getFooBar' => 0); + + public $bar = 'bar'; + + public static function reset() + { + self::$called = array('__toString' => 0, 'foo' => 0, 'getFooBar' => 0); + } + + public function __toString() + { + ++self::$called['__toString']; + + return 'foo'; + } + + public function foo() + { + ++self::$called['foo']; + + return 'foo'; + } + + public function getFooBar() + { + ++self::$called['getFooBar']; + + return 'foobar'; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/FileCachingTest.php b/src/vendor/twig/twig/test/Twig/Tests/FileCachingTest.php new file mode 100644 index 0000000..f7ff53c --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/FileCachingTest.php @@ -0,0 +1,63 @@ +tmpDir = sys_get_temp_dir().'/TwigTests'; + if (!file_exists($this->tmpDir)) { + @mkdir($this->tmpDir, 0777, true); + } + + if (!is_writable($this->tmpDir)) { + $this->markTestSkipped(sprintf('Unable to run the tests as "%s" is not writable.', $this->tmpDir)); + } + + $this->env = new Twig_Environment(new Twig_Loader_Array(array('index' => 'index', 'index2' => 'index2')), array('cache' => $this->tmpDir)); + } + + protected function tearDown() + { + Twig_Tests_FilesystemHelper::removeDir($this->tmpDir); + } + + /** + * @group legacy + */ + public function testWritingCacheFiles() + { + $name = 'index'; + $this->env->loadTemplate($name); + $cacheFileName = $this->env->getCacheFilename($name); + + $this->assertFileExists($cacheFileName, 'Cache file does not exist.'); + } + + /** + * @group legacy + */ + public function testClearingCacheFiles() + { + $name = 'index2'; + $this->env->loadTemplate($name); + $cacheFileName = $this->env->getCacheFilename($name); + + $this->assertFileExists($cacheFileName, 'Cache file does not exist.'); + $this->env->clearCacheFiles(); + $this->assertFileNotExists($cacheFileName, 'Cache file was not cleared.'); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/FileExtensionEscapingStrategyTest.php b/src/vendor/twig/twig/test/Twig/Tests/FileExtensionEscapingStrategyTest.php new file mode 100644 index 0000000..b310a5b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/FileExtensionEscapingStrategyTest.php @@ -0,0 +1,51 @@ +assertSame($strategy, Twig_FileExtensionEscapingStrategy::guess($filename)); + } + + public function getGuessData() + { + return array( + // default + array('html', 'foo.html'), + array('html', 'foo.html.twig'), + array('html', 'foo'), + array('html', 'foo.bar.twig'), + array('html', 'foo.txt/foo'), + array('html', 'foo.txt/foo.js/'), + + // css + array('css', 'foo.css'), + array('css', 'foo.css.twig'), + array('css', 'foo.twig.css'), + array('css', 'foo.js.css'), + array('css', 'foo.js.css.twig'), + + // js + array('js', 'foo.js'), + array('js', 'foo.js.twig'), + array('js', 'foo.txt/foo.js'), + array('js', 'foo.txt.twig/foo.js'), + + // txt + array(false, 'foo.txt'), + array(false, 'foo.txt.twig'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/FilesystemHelper.php b/src/vendor/twig/twig/test/Twig/Tests/FilesystemHelper.php new file mode 100644 index 0000000..0e315e8 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/FilesystemHelper.php @@ -0,0 +1,30 @@ + $fileInfo) { + if ($iterator->isDot()) { + continue; + } + + if ($fileInfo->isDir()) { + rmdir($filename); + } else { + unlink($filename); + } + } + rmdir($dir); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/autoescape/block.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/autoescape/block.test new file mode 100644 index 0000000..1290973 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/autoescape/block.test @@ -0,0 +1,21 @@ +--TEST-- +blocks and autoescape +--TEMPLATE-- +{{ include('unrelated.txt.twig') -}} +{{ include('template.html.twig') -}} +--TEMPLATE(unrelated.txt.twig)-- +{% block content %}{% endblock %} +--TEMPLATE(template.html.twig)-- +{% extends 'parent.html.twig' %} +{% block content %} +{{ br -}} +{% endblock %} +--TEMPLATE(parent.html.twig)-- +{% set _content = block('content')|raw %} +{{ _content|raw }} +--DATA-- +return array('br' => '
    ') +--CONFIG-- +return array('autoescape' => 'name') +--EXPECT-- +<br /> diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/autoescape/name.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/autoescape/name.test new file mode 100644 index 0000000..801c477 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/autoescape/name.test @@ -0,0 +1,18 @@ +--TEST-- +"name" autoescape strategy +--TEMPLATE-- +{{ br -}} +{{ include('index.html.twig') -}} +{{ include('index.txt.twig') -}} +--TEMPLATE(index.html.twig)-- +{{ br -}} +--TEMPLATE(index.txt.twig)-- +{{ br -}} +--DATA-- +return array('br' => '
    ') +--CONFIG-- +return array('autoescape' => 'name') +--EXPECT-- +<br /> +<br /> +
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/errors/base.html b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/errors/base.html new file mode 100644 index 0000000..cb0dbe4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/errors/base.html @@ -0,0 +1 @@ +{% block content %}{% endblock %} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/errors/index.html b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/errors/index.html new file mode 100644 index 0000000..df57c82 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/errors/index.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% block content %} + {{ foo.bar }} +{% endblock %} +{% block foo %} + {{ foo.bar }} +{% endblock %} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/child_contents_outside_blocks.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/child_contents_outside_blocks.test new file mode 100644 index 0000000..a3f0b50 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/child_contents_outside_blocks.test @@ -0,0 +1,15 @@ +--TEST-- +Exception for child templates defining contents outside blocks defined by parent +--TEMPLATE-- +{% extends 'base.twig' %} + +Content outside a block. + +{% block sidebar %} + Content inside a block. +{% endblock %} +--TEMPLATE(base.twig)-- +{% block sidebar %} +{% endblock %} +--EXCEPTION-- +Twig_Error_Syntax: A template that extends another one cannot include contents outside Twig blocks. Did you forget to put the contents inside a {% block %} tag in "index.twig" at line 3? diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_array_with_undefined_variable.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_array_with_undefined_variable.test new file mode 100644 index 0000000..7ff2eed --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_array_with_undefined_variable.test @@ -0,0 +1,18 @@ +--TEST-- +Exception for multiline array with undefined variable +--TEMPLATE-- +{% set foo = { + foo: 'foo', + bar: 'bar', + + + foobar: foobar, + + + + foo2: foo2, +} %} +--DATA-- +return array('foobar' => 'foobar') +--EXCEPTION-- +Twig_Error_Runtime: Variable "foo2" does not exist in "index.twig" at line 11. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_array_with_undefined_variable_again.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_array_with_undefined_variable_again.test new file mode 100644 index 0000000..c425069 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_array_with_undefined_variable_again.test @@ -0,0 +1,18 @@ +--TEST-- +Exception for multiline array with undefined variable +--TEMPLATE-- +{% set foo = { + foo: 'foo', + bar: 'bar', + + + foobar: foobar, + + + + foo2: foo2, +} %} +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Runtime: Variable "foobar" does not exist in "index.twig" at line 7. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_function_with_undefined_variable.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_function_with_undefined_variable.test new file mode 100644 index 0000000..2f94a5e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_function_with_undefined_variable.test @@ -0,0 +1,12 @@ +--TEST-- +Exception for multile function with undefined variable +--TEMPLATE-- +{{ include('foo', + with_context=with_context +) }} +--TEMPLATE(foo)-- +Foo +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Runtime: Variable "with_context" does not exist in "index.twig" at line 3. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_function_with_unknown_argument.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_function_with_unknown_argument.test new file mode 100644 index 0000000..64761fc --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_function_with_unknown_argument.test @@ -0,0 +1,9 @@ +--TEST-- +Exception for multiline function with unknown argument +--TEMPLATE-- +{{ include('foo', + with_context=True, + invalid=False +) }} +--EXCEPTION-- +Twig_Error_Syntax: Unknown argument "invalid" for function "include(template, variables, with_context, ignore_missing, sandboxed)" in "index.twig" at line 4. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_tag_with_undefined_variable.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_tag_with_undefined_variable.test new file mode 100644 index 0000000..7b3b1da --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/multiline_tag_with_undefined_variable.test @@ -0,0 +1,12 @@ +--TEST-- +Exception for multiline tag with undefined variable +--TEMPLATE-- +{% include 'foo' + with vars +%} +--TEMPLATE(foo)-- +Foo +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Runtime: Variable "vars" does not exist in "index.twig" at line 3. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/syntax_error_in_reused_template.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/syntax_error_in_reused_template.test new file mode 100644 index 0000000..9ca418b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/syntax_error_in_reused_template.test @@ -0,0 +1,10 @@ +--TEST-- +Exception for syntax error in reused template +--TEMPLATE-- +{% use 'foo.twig' %} +--TEMPLATE(foo.twig)-- +{% block bar %} + {% do node.data = 5 %} +{% endblock %} +--EXCEPTION-- +Twig_Error_Syntax: Unexpected token "operator" of value "=" ("end of statement block" expected) in "foo.twig" at line 3. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/unclosed_tag.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/unclosed_tag.test new file mode 100644 index 0000000..2c35ad5 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/unclosed_tag.test @@ -0,0 +1,20 @@ +--TEST-- +Exception for an unclosed tag +--TEMPLATE-- +{% block foo %} + {% if foo %} + + + + + {% for i in fo %} + + + + {% endfor %} + + + +{% endblock %} +--EXCEPTION-- +Twig_Error_Syntax: Unexpected "endblock" tag (expecting closing tag for the "if" tag defined near line 4) in "index.twig" at line 16. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_parent.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_parent.test new file mode 100644 index 0000000..c8e7a09 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_parent.test @@ -0,0 +1,8 @@ +--TEST-- +Exception for an undefined parent +--TEMPLATE-- +{% extends 'foo.html' %} + +{% set foo = "foo" %} +--EXCEPTION-- +Twig_Error_Loader: Template "foo.html" is not defined in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_template_in_child_template.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_template_in_child_template.test new file mode 100644 index 0000000..1992510 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_template_in_child_template.test @@ -0,0 +1,15 @@ +--TEST-- +Exception for an undefined template in a child template +--TEMPLATE-- +{% extends 'base.twig' %} + +{% block sidebar %} + {{ include('include.twig') }} +{% endblock %} +--TEMPLATE(base.twig)-- +{% block sidebar %} +{% endblock %} +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Loader: Template "include.twig" is not defined in "index.twig" at line 5. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_trait.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_trait.test new file mode 100644 index 0000000..6679fbe --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_trait.test @@ -0,0 +1,9 @@ +--TEST-- +Exception for an undefined trait +--TEMPLATE-- +{% use 'foo' with foobar as bar %} +--TEMPLATE(foo)-- +{% block bar %} +{% endblock %} +--EXCEPTION-- +Twig_Error_Runtime: Block "foobar" is not defined in trait "foo" in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/_self.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/_self.test new file mode 100644 index 0000000..32fed8f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/_self.test @@ -0,0 +1,8 @@ +--TEST-- +_self returns the template name +--TEMPLATE-- +{{ _self }} +--DATA-- +return array() +--EXPECT-- +index.twig diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/array.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/array.test new file mode 100644 index 0000000..c69b119 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/array.test @@ -0,0 +1,61 @@ +--TEST-- +Twig supports array notation +--TEMPLATE-- +{# empty array #} +{{ []|join(',') }} + +{{ [1, 2]|join(',') }} +{{ ['foo', "bar"]|join(',') }} +{{ {0: 1, 'foo': 'bar'}|join(',') }} +{{ {0: 1, 'foo': 'bar'}|keys|join(',') }} + +{{ {0: 1, foo: 'bar'}|join(',') }} +{{ {0: 1, foo: 'bar'}|keys|join(',') }} + +{# nested arrays #} +{% set a = [1, 2, [1, 2], {'foo': {'foo': 'bar'}}] %} +{{ a[2]|join(',') }} +{{ a[3]["foo"]|join(',') }} + +{# works even if [] is used inside the array #} +{{ [foo[bar]]|join(',') }} + +{# elements can be any expression #} +{{ ['foo'|upper, bar|upper, bar == foo]|join(',') }} + +{# arrays can have a trailing , like in PHP #} +{{ + [ + 1, + 2, + ]|join(',') +}} + +{# keys can be any expression #} +{% set a = 1 %} +{% set b = "foo" %} +{% set ary = { (a): 'a', (b): 'b', 'c': 'c', (a ~ b): 'd' } %} +{{ ary|keys|join(',') }} +{{ ary|join(',') }} +--DATA-- +return array('bar' => 'bar', 'foo' => array('bar' => 'bar')) +--EXPECT-- +1,2 +foo,bar +1,bar +0,foo + +1,bar +0,foo + +1,2 +bar + +bar + +FOO,BAR, + +1,2 + +1,foo,c,1foo +a,b,c,d diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/array_call.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/array_call.test new file mode 100644 index 0000000..f3df328 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/array_call.test @@ -0,0 +1,14 @@ +--TEST-- +Twig supports method calls +--TEMPLATE-- +{{ items.foo }} +{{ items['foo'] }} +{{ items[foo] }} +{{ items[items[foo]] }} +--DATA-- +return array('foo' => 'bar', 'items' => array('foo' => 'bar', 'bar' => 'foo')) +--EXPECT-- +bar +bar +foo +bar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/binary.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/binary.test new file mode 100644 index 0000000..f5e6845 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/binary.test @@ -0,0 +1,46 @@ +--TEST-- +Twig supports binary operations (+, -, *, /, ~, %, and, or) +--TEMPLATE-- +{{ 1 + 1 }} +{{ 2 - 1 }} +{{ 2 * 2 }} +{{ 2 / 2 }} +{{ 3 % 2 }} +{{ 1 and 1 }} +{{ 1 and 0 }} +{{ 0 and 1 }} +{{ 0 and 0 }} +{{ 1 or 1 }} +{{ 1 or 0 }} +{{ 0 or 1 }} +{{ 0 or 0 }} +{{ 0 or 1 and 0 }} +{{ 1 or 0 and 1 }} +{{ "foo" ~ "bar" }} +{{ foo ~ "bar" }} +{{ "foo" ~ bar }} +{{ foo ~ bar }} +{{ 20 // 7 }} +--DATA-- +return array('foo' => 'bar', 'bar' => 'foo') +--EXPECT-- +2 +1 +4 +1 +1 +1 + + + +1 +1 +1 + + +1 +foobar +barbar +foofoo +barfoo +2 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/bitwise.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/bitwise.test new file mode 100644 index 0000000..74fe6ca --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/bitwise.test @@ -0,0 +1,14 @@ +--TEST-- +Twig supports bitwise operations +--TEMPLATE-- +{{ 1 b-and 5 }} +{{ 1 b-or 5 }} +{{ 1 b-xor 5 }} +{{ (1 and 0 b-or 0) is same as(1 and (0 b-or 0)) ? 'ok' : 'ko' }} +--DATA-- +return array() +--EXPECT-- +1 +5 +4 +ok diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/comparison.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/comparison.test new file mode 100644 index 0000000..726b850 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/comparison.test @@ -0,0 +1,14 @@ +--TEST-- +Twig supports comparison operators (==, !=, <, >, >=, <=) +--TEMPLATE-- +{{ 1 > 2 }}/{{ 1 > 1 }}/{{ 1 >= 2 }}/{{ 1 >= 1 }} +{{ 1 < 2 }}/{{ 1 < 1 }}/{{ 1 <= 2 }}/{{ 1 <= 1 }} +{{ 1 == 1 }}/{{ 1 == 2 }} +{{ 1 != 1 }}/{{ 1 != 2 }} +--DATA-- +return array() +--EXPECT-- +///1 +1//1/1 +1/ +/1 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/divisibleby.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/divisibleby.test new file mode 100644 index 0000000..238dd27 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/divisibleby.test @@ -0,0 +1,17 @@ +--TEST-- +Twig supports the "divisible by" operator +--TEMPLATE-- +{{ 8 is divisible by(2) ? 'OK' }} +{{ 8 is not divisible by(3) ? 'OK' }} +{{ 8 is divisible by (2) ? 'OK' }} +{{ 8 is not + divisible + by + (3) ? 'OK' }} +--DATA-- +return array() +--EXPECT-- +OK +OK +OK +OK diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/dotdot.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/dotdot.test new file mode 100644 index 0000000..9cd0676 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/dotdot.test @@ -0,0 +1,20 @@ +--TEST-- +Twig supports the .. operator +--TEMPLATE-- +{% for i in 0..10 %}{{ i }} {% endfor %} + +{% for letter in 'a'..'z' %}{{ letter }} {% endfor %} + +{% for letter in 'a'|upper..'z'|upper %}{{ letter }} {% endfor %} + +{% for i in foo[0]..foo[1] %}{{ i }} {% endfor %} + +{% for i in 0 + 1 .. 10 - 1 %}{{ i }} {% endfor %} +--DATA-- +return array('foo' => array(1, 10)) +--EXPECT-- +0 1 2 3 4 5 6 7 8 9 10 +a b c d e f g h i j k l m n o p q r s t u v w x y z +A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +1 2 3 4 5 6 7 8 9 10 +1 2 3 4 5 6 7 8 9 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ends_with.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ends_with.test new file mode 100644 index 0000000..9ad5e5e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ends_with.test @@ -0,0 +1,26 @@ +--TEST-- +Twig supports the "ends with" operator +--TEMPLATE-- +{{ 'foo' ends with 'o' ? 'OK' : 'KO' }} +{{ not ('foo' ends with 'f') ? 'OK' : 'KO' }} +{{ not ('foo' ends with 'foowaytoolong') ? 'OK' : 'KO' }} +{{ 'foo' ends with '' ? 'OK' : 'KO' }} +{{ '1' ends with true ? 'OK' : 'KO' }} +{{ 1 ends with true ? 'OK' : 'KO' }} +{{ 0 ends with false ? 'OK' : 'KO' }} +{{ '' ends with false ? 'OK' : 'KO' }} +{{ false ends with false ? 'OK' : 'KO' }} +{{ false ends with '' ? 'OK' : 'KO' }} +--DATA-- +return array() +--EXPECT-- +OK +OK +OK +OK +KO +KO +KO +KO +KO +KO diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/grouping.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/grouping.test new file mode 100644 index 0000000..79f8e0b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/grouping.test @@ -0,0 +1,8 @@ +--TEST-- +Twig supports grouping of expressions +--TEMPLATE-- +{{ (2 + 2) / 2 }} +--DATA-- +return array() +--EXPECT-- +2 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/literals.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/literals.test new file mode 100644 index 0000000..7ae3bae --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/literals.test @@ -0,0 +1,22 @@ +--TEST-- +Twig supports literals +--TEMPLATE-- +1 {{ true }} +2 {{ TRUE }} +3 {{ false }} +4 {{ FALSE }} +5 {{ none }} +6 {{ NONE }} +7 {{ null }} +8 {{ NULL }} +--DATA-- +return array() +--EXPECT-- +1 1 +2 1 +3 +4 +5 +6 +7 +8 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/magic_call.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/magic_call.test new file mode 100644 index 0000000..1a27a2d --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/magic_call.test @@ -0,0 +1,27 @@ +--TEST-- +Twig supports __call() for attributes +--TEMPLATE-- +{{ foo.foo }} +{{ foo.bar }} +--DATA-- +class TestClassForMagicCallAttributes +{ + public function getBar() + { + return 'bar_from_getbar'; + } + + public function __call($method, $arguments) + { + if ('foo' === $method) { + return 'foo_from_call'; + } + + return false; + } +} + +return array('foo' => new TestClassForMagicCallAttributes()) +--EXPECT-- +foo_from_call +bar_from_getbar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/matches.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/matches.test new file mode 100644 index 0000000..b6c7716 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/matches.test @@ -0,0 +1,12 @@ +--TEST-- +Twig supports the "matches" operator +--TEMPLATE-- +{{ 'foo' matches '/o/' ? 'OK' : 'KO' }} +{{ 'foo' matches '/^fo/' ? 'OK' : 'KO' }} +{{ 'foo' matches '/O/i' ? 'OK' : 'KO' }} +--DATA-- +return array() +--EXPECT-- +OK +OK +OK diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/method_call.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/method_call.test new file mode 100644 index 0000000..5f801e6 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/method_call.test @@ -0,0 +1,28 @@ +--TEST-- +Twig supports method calls +--TEMPLATE-- +{{ items.foo.foo }} +{{ items.foo.getFoo() }} +{{ items.foo.bar }} +{{ items.foo['bar'] }} +{{ items.foo.bar('a', 43) }} +{{ items.foo.bar(foo) }} +{{ items.foo.self.foo() }} +{{ items.foo.is }} +{{ items.foo.in }} +{{ items.foo.not }} +--DATA-- +return array('foo' => 'bar', 'items' => array('foo' => new TwigTestFoo(), 'bar' => 'foo')) +--CONFIG-- +return array('strict_variables' => false) +--EXPECT-- +foo +foo +bar + +bar_a-43 +bar_bar +foo +is +in +not diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/negative_numbers.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/negative_numbers.test new file mode 100644 index 0000000..1853b1b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/negative_numbers.test @@ -0,0 +1,18 @@ +--TEST-- +Twig manages negative numbers correctly +--TEMPLATE-- +{{ -1 }} +{{ - 1 }} +{{ 5 - 1 }} +{{ 5-1 }} +{{ 5 + -1 }} +{{ 5 + - 1 }} +--DATA-- +return array() +--EXPECT-- +-1 +-1 +4 +4 +4 +4 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/operators_as_variables.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/operators_as_variables.test new file mode 100644 index 0000000..fe29d08 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/operators_as_variables.test @@ -0,0 +1,16 @@ +--TEST-- +Twig allows to use named operators as variable names +--TEMPLATE-- +{% for match in matches %} + {{- match }} +{% endfor %} +{{ in }} +{{ is }} +--DATA-- +return array('matches' => array(1, 2, 3), 'in' => 'in', 'is' => 'is') +--EXPECT-- +1 +2 +3 +in +is diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/postfix.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/postfix.test new file mode 100644 index 0000000..542c350 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/postfix.test @@ -0,0 +1,22 @@ +--TEST-- +Twig parses postfix expressions +--TEMPLATE-- +{% import _self as macros %} + +{% macro foo() %}foo{% endmacro %} + +{{ 'a' }} +{{ 'a'|upper }} +{{ ('a')|upper }} +{{ -1|upper }} +{{ macros.foo() }} +{{ (macros).foo() }} +--DATA-- +return array(); +--EXPECT-- +a +A +A +-1 +foo +foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/power.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/power.test new file mode 100644 index 0000000..eacc98f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/power.test @@ -0,0 +1,20 @@ +--TEST-- +Twig parses power expressions +--TEMPLATE-- +{{ 2**3 }} +{{ (-2)**3 }} +{{ (-2)**(-3) }} +{{ a ** a }} +{{ a ** b }} +{{ b ** a }} +{{ b ** b }} +--DATA-- +return array('a' => 4, 'b' => -2); +--EXPECT-- +8 +-8 +-0.125 +256 +0.0625 +16 +0.25 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/sameas.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/sameas.test new file mode 100644 index 0000000..601201d --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/sameas.test @@ -0,0 +1,21 @@ +--TEST-- +Twig supports the "same as" operator +--TEMPLATE-- +{{ 1 is same as(1) ? 'OK' }} +{{ 1 is not same as(true) ? 'OK' }} +{{ 1 is same as(1) ? 'OK' }} +{{ 1 is not same as(true) ? 'OK' }} +{{ 1 is same as (1) ? 'OK' }} +{{ 1 is not + same + as + (true) ? 'OK' }} +--DATA-- +return array() +--EXPECT-- +OK +OK +OK +OK +OK +OK diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/starts_with.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/starts_with.test new file mode 100644 index 0000000..75d331e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/starts_with.test @@ -0,0 +1,27 @@ +--TEST-- +Twig supports the "starts with" operator +--TEMPLATE-- +{{ 'foo' starts with 'f' ? 'OK' : 'KO' }} +{{ not ('foo' starts with 'oo') ? 'OK' : 'KO' }} +{{ not ('foo' starts with 'foowaytoolong') ? 'OK' : 'KO' }} +{{ 'foo' starts with 'f' ? 'OK' : 'KO' }} +{{ 'foo' starts +with 'f' ? 'OK' : 'KO' }} +{{ 'foo' starts with '' ? 'OK' : 'KO' }} +{{ '1' starts with true ? 'OK' : 'KO' }} +{{ '' starts with false ? 'OK' : 'KO' }} +{{ 'a' starts with false ? 'OK' : 'KO' }} +{{ false starts with '' ? 'OK' : 'KO' }} +--DATA-- +return array() +--EXPECT-- +OK +OK +OK +OK +OK +OK +KO +KO +KO +KO diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/strings.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/strings.test new file mode 100644 index 0000000..a911661 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/strings.test @@ -0,0 +1,10 @@ +--TEST-- +Twig supports string interpolation +--TEMPLATE-- +{{ "foo #{"foo #{bar} baz"} baz" }} +{{ "foo #{bar}#{bar} baz" }} +--DATA-- +return array('bar' => 'BAR'); +--EXPECT-- +foo foo BAR baz baz +foo BARBAR baz diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ternary_operator.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ternary_operator.test new file mode 100644 index 0000000..0e6fa96 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ternary_operator.test @@ -0,0 +1,18 @@ +--TEST-- +Twig supports the ternary operator +--TEMPLATE-- +{{ 1 ? 'YES' : 'NO' }} +{{ 0 ? 'YES' : 'NO' }} +{{ 0 ? 'YES' : (1 ? 'YES1' : 'NO1') }} +{{ 0 ? 'YES' : (0 ? 'YES1' : 'NO1') }} +{{ 1 == 1 ? 'foo
    ':'' }} +{{ foo ~ (bar ? ('-' ~ bar) : '') }} +--DATA-- +return array('foo' => 'foo', 'bar' => 'bar') +--EXPECT-- +YES +NO +YES1 +NO1 +foo
    +foo-bar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ternary_operator_noelse.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ternary_operator_noelse.test new file mode 100644 index 0000000..fdc660f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ternary_operator_noelse.test @@ -0,0 +1,10 @@ +--TEST-- +Twig supports the ternary operator +--TEMPLATE-- +{{ 1 ? 'YES' }} +{{ 0 ? 'YES' }} +--DATA-- +return array() +--EXPECT-- +YES + diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ternary_operator_nothen.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ternary_operator_nothen.test new file mode 100644 index 0000000..9057e83 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/ternary_operator_nothen.test @@ -0,0 +1,10 @@ +--TEST-- +Twig supports the ternary operator +--TEMPLATE-- +{{ 'YES' ?: 'NO' }} +{{ 0 ?: 'NO' }} +--DATA-- +return array() +--EXPECT-- +YES +NO diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/two_word_operators_as_variables.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/two_word_operators_as_variables.test new file mode 100644 index 0000000..0eaabb4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/two_word_operators_as_variables.test @@ -0,0 +1,8 @@ +--TEST-- +Twig does not allow to use two-word named operators as variable names +--TEMPLATE-- +{{ starts with }} +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Syntax: Unexpected token "operator" of value "starts with" in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/unary.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/unary.test new file mode 100644 index 0000000..b79219a --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/unary.test @@ -0,0 +1,12 @@ +--TEST-- +Twig supports unary operators (not, -, +) +--TEMPLATE-- +{{ not 1 }}/{{ not 0 }} +{{ +1 + 1 }}/{{ -1 - 1 }} +{{ not (false or true) }} +--DATA-- +return array() +--EXPECT-- +/1 +2/-2 + diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/unary_macro_arguments.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/unary_macro_arguments.test new file mode 100644 index 0000000..ad84a9c --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/unary_macro_arguments.test @@ -0,0 +1,22 @@ +--TEST-- +Twig manages negative numbers as default parameters +--TEMPLATE-- +{% import _self as macros %} +{{ macros.negative_number1() }} +{{ macros.negative_number2() }} +{{ macros.negative_number3() }} +{{ macros.positive_number1() }} +{{ macros.positive_number2() }} +{% macro negative_number1(nb=-1) %}{{ nb }}{% endmacro %} +{% macro negative_number2(nb = --1) %}{{ nb }}{% endmacro %} +{% macro negative_number3(nb = - 1) %}{{ nb }}{% endmacro %} +{% macro positive_number1(nb = +1) %}{{ nb }}{% endmacro %} +{% macro positive_number2(nb = ++1) %}{{ nb }}{% endmacro %} +--DATA-- +return array() +--EXPECT-- +-1 +1 +-1 +1 +1 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/unary_precedence.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/unary_precedence.test new file mode 100644 index 0000000..cc6eef8 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/unary_precedence.test @@ -0,0 +1,14 @@ +--TEST-- +Twig unary operators precedence +--TEMPLATE-- +{{ -1 - 1 }} +{{ -1 - -1 }} +{{ -1 * -1 }} +{{ 4 / -1 * 5 }} +--DATA-- +return array() +--EXPECT-- +-2 +0 +1 +-20 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/abs.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/abs.test new file mode 100644 index 0000000..27e93fd --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/abs.test @@ -0,0 +1,30 @@ +--TEST-- +"abs" filter +--TEMPLATE-- +{{ (-5.5)|abs }} +{{ (-5)|abs }} +{{ (-0)|abs }} +{{ 0|abs }} +{{ 5|abs }} +{{ 5.5|abs }} +{{ number1|abs }} +{{ number2|abs }} +{{ number3|abs }} +{{ number4|abs }} +{{ number5|abs }} +{{ number6|abs }} +--DATA-- +return array('number1' => -5.5, 'number2' => -5, 'number3' => -0, 'number4' => 0, 'number5' => 5, 'number6' => 5.5) +--EXPECT-- +5.5 +5 +0 +0 +5 +5.5 +5.5 +5 +0 +0 +5 +5.5 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch.test new file mode 100644 index 0000000..cb6de7f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch.test @@ -0,0 +1,31 @@ +--TEST-- +"batch" filter +--TEMPLATE-- +{% for row in items|batch(3) %} +
    + {% for column in row %} +
    {{ column }}
    + {% endfor %} +
    +{% endfor %} +--DATA-- +return array('items' => array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j')) +--EXPECT-- +
    +
    a
    +
    b
    +
    c
    +
    +
    +
    d
    +
    e
    +
    f
    +
    +
    +
    g
    +
    h
    +
    i
    +
    +
    +
    j
    +
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_float.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_float.test new file mode 100644 index 0000000..e2ec4be --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_float.test @@ -0,0 +1,29 @@ +--TEST-- +"batch" filter +--TEMPLATE-- +{% for row in items|batch(3.1) %} +
    + {% for column in row %} +
    {{ column }}
    + {% endfor %} +
    +{% endfor %} +--DATA-- +return array('items' => array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j')) +--EXPECT-- +
    +
    a
    +
    b
    +
    c
    +
    d
    +
    +
    +
    e
    +
    f
    +
    g
    +
    h
    +
    +
    +
    i
    +
    j
    +
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_empty_fill.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_empty_fill.test new file mode 100644 index 0000000..af996f2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_empty_fill.test @@ -0,0 +1,37 @@ +--TEST-- +"batch" filter +--TEMPLATE-- + +{% for row in items|batch(3, '') %} + + {% for column in row %} + + {% endfor %} + +{% endfor %} +
    {{ column }}
    +--DATA-- +return array('items' => array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j')) +--EXPECT-- + + + + + + + + + + + + + + + + + + + + + +
    abc
    def
    ghi
    j
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_exact_elements.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_exact_elements.test new file mode 100644 index 0000000..72483f4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_exact_elements.test @@ -0,0 +1,33 @@ +--TEST-- +"batch" filter +--TEMPLATE-- +{% for row in items|batch(3, 'fill') %} +
    + {% for column in row %} +
    {{ column }}
    + {% endfor %} +
    +{% endfor %} +--DATA-- +return array('items' => array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l')) +--EXPECT-- +
    +
    a
    +
    b
    +
    c
    +
    +
    +
    d
    +
    e
    +
    f
    +
    +
    +
    g
    +
    h
    +
    i
    +
    +
    +
    j
    +
    k
    +
    l
    +
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_fill.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_fill.test new file mode 100644 index 0000000..746295f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_fill.test @@ -0,0 +1,37 @@ +--TEST-- +"batch" filter +--TEMPLATE-- + +{% for row in items|batch(3, 'fill') %} + + {% for column in row %} + + {% endfor %} + +{% endfor %} +
    {{ column }}
    +--DATA-- +return array('items' => array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j')) +--EXPECT-- + + + + + + + + + + + + + + + + + + + + + +
    abc
    def
    ghi
    jfillfill
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_keys.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_keys.test new file mode 100644 index 0000000..6015380 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_keys.test @@ -0,0 +1,10 @@ +--TEST-- +"batch" filter preserves array keys +--TEMPLATE-- +{{ {'foo': 'bar', 'key': 'value'}|batch(4)|first|keys|join(',') }} +{{ {'foo': 'bar', 'key': 'value'}|batch(4, 'fill')|first|keys|join(',') }} +--DATA-- +return array() +--EXPECT-- +foo,key +foo,key,0,1 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_zero_elements.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_zero_elements.test new file mode 100644 index 0000000..b9c058d --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_zero_elements.test @@ -0,0 +1,10 @@ +--TEST-- +"batch" filter with zero elements +--TEMPLATE-- +{{ []|batch(3)|length }} +{{ []|batch(3, 'fill')|length }} +--DATA-- +return array() +--EXPECT-- +0 +0 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/convert_encoding.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/convert_encoding.test new file mode 100644 index 0000000..380b04b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/convert_encoding.test @@ -0,0 +1,10 @@ +--TEST-- +"convert_encoding" filter +--CONDITION-- +function_exists('iconv') || function_exists('mb_convert_encoding') +--TEMPLATE-- +{{ "愛していますか?"|convert_encoding('ISO-2022-JP', 'UTF-8')|convert_encoding('UTF-8', 'ISO-2022-JP') }} +--DATA-- +return array() +--EXPECT-- +愛していますか? diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date.test new file mode 100644 index 0000000..d17e5e2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date.test @@ -0,0 +1,90 @@ +--TEST-- +"date" filter +--TEMPLATE-- +{{ date1|date }} +{{ date1|date('d/m/Y') }} +{{ date1|date('d/m/Y H:i:s', 'Asia/Hong_Kong') }} +{{ date1|date('d/m/Y H:i:s P', 'Asia/Hong_Kong') }} +{{ date1|date('d/m/Y H:i:s P', 'America/Chicago') }} +{{ date1|date('e') }} +{{ date1|date('d/m/Y H:i:s') }} + +{{ date2|date }} +{{ date2|date('d/m/Y') }} +{{ date2|date('d/m/Y H:i:s', 'Asia/Hong_Kong') }} +{{ date2|date('d/m/Y H:i:s', timezone1) }} +{{ date2|date('d/m/Y H:i:s') }} + +{{ date3|date }} +{{ date3|date('d/m/Y') }} + +{{ date4|date }} +{{ date4|date('d/m/Y') }} + +{{ date5|date }} +{{ date5|date('d/m/Y') }} + +{{ date6|date('d/m/Y H:i:s P', 'Europe/Paris') }} +{{ date6|date('d/m/Y H:i:s P', 'Asia/Hong_Kong') }} +{{ date6|date('d/m/Y H:i:s P', false) }} +{{ date6|date('e', 'Europe/Paris') }} +{{ date6|date('e', false) }} + +{{ date7|date }} +{{ date7|date(timezone='Europe/Paris') }} +{{ date7|date(timezone='Asia/Hong_Kong') }} +{{ date7|date(timezone=false) }} +{{ date7|date(timezone='Indian/Mauritius') }} + +{{ '2010-01-28 15:00:00'|date(timezone="Europe/Paris") }} +{{ '2010-01-28 15:00:00'|date(timezone="Asia/Hong_Kong") }} +--DATA-- +date_default_timezone_set('Europe/Paris'); +return array( + 'date1' => mktime(13, 45, 0, 10, 4, 2010), + 'date2' => new DateTime('2010-10-04 13:45'), + 'date3' => '2010-10-04 13:45', + 'date4' => 1286199900, // DateTime::createFromFormat('Y-m-d H:i', '2010-10-04 13:45', new DateTimeZone('UTC'))->getTimestamp() -- A unixtimestamp is always GMT + 'date5' => -189291360, // DateTime::createFromFormat('Y-m-d H:i', '1964-01-02 03:04', new DateTimeZone('UTC'))->getTimestamp(), + 'date6' => new DateTime('2010-10-04 13:45', new DateTimeZone('America/New_York')), + 'date7' => '2010-01-28T15:00:00+04:00', + 'timezone1' => new DateTimeZone('America/New_York'), +) +--EXPECT-- +October 4, 2010 13:45 +04/10/2010 +04/10/2010 19:45:00 +04/10/2010 19:45:00 +08:00 +04/10/2010 06:45:00 -05:00 +Europe/Paris +04/10/2010 13:45:00 + +October 4, 2010 13:45 +04/10/2010 +04/10/2010 19:45:00 +04/10/2010 07:45:00 +04/10/2010 13:45:00 + +October 4, 2010 13:45 +04/10/2010 + +October 4, 2010 15:45 +04/10/2010 + +January 2, 1964 04:04 +02/01/1964 + +04/10/2010 19:45:00 +02:00 +05/10/2010 01:45:00 +08:00 +04/10/2010 13:45:00 -04:00 +Europe/Paris +America/New_York + +January 28, 2010 12:00 +January 28, 2010 12:00 +January 28, 2010 19:00 +January 28, 2010 15:00 +January 28, 2010 15:00 + +January 28, 2010 15:00 +January 28, 2010 22:00 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_default_format.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_default_format.test new file mode 100644 index 0000000..6ad504c --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_default_format.test @@ -0,0 +1,14 @@ +--TEST-- +"date" filter +--TEMPLATE-- +{{ date1|date }} +{{ date1|date('d/m/Y') }} +--DATA-- +date_default_timezone_set('UTC'); +$twig->getExtension('Twig_Extension_Core')->setDateFormat('Y-m-d', '%d days %h hours'); +return array( + 'date1' => mktime(13, 45, 0, 10, 4, 2010), +) +--EXPECT-- +2010-10-04 +04/10/2010 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_default_format_interval.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_default_format_interval.test new file mode 100644 index 0000000..be4a642 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_default_format_interval.test @@ -0,0 +1,16 @@ +--TEST-- +"date" filter (interval support as of PHP 5.3) +--CONDITION-- +version_compare(phpversion(), '5.3.0', '>=') +--TEMPLATE-- +{{ date2|date }} +{{ date2|date('%d days') }} +--DATA-- +date_default_timezone_set('UTC'); +$twig->getExtension('Twig_Extension_Core')->setDateFormat('Y-m-d', '%d days %h hours'); +return array( + 'date2' => new DateInterval('P2D'), +) +--EXPECT-- +2 days 0 hours +2 days diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_immutable.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_immutable.test new file mode 100644 index 0000000..4e18325 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_immutable.test @@ -0,0 +1,37 @@ +--TEST-- +"date" filter +--CONDITION-- +version_compare(phpversion(), '5.5.0', '>=') +--TEMPLATE-- +{{ date1|date }} +{{ date1|date('d/m/Y') }} +{{ date1|date('d/m/Y H:i:s', 'Asia/Hong_Kong') }} +{{ date1|date('d/m/Y H:i:s', timezone1) }} +{{ date1|date('d/m/Y H:i:s') }} +{{ date1|date_modify('+1 hour')|date('d/m/Y H:i:s') }} + +{{ date2|date('d/m/Y H:i:s P', 'Europe/Paris') }} +{{ date2|date('d/m/Y H:i:s P', 'Asia/Hong_Kong') }} +{{ date2|date('d/m/Y H:i:s P', false) }} +{{ date2|date('e', 'Europe/Paris') }} +{{ date2|date('e', false) }} +--DATA-- +date_default_timezone_set('Europe/Paris'); +return array( + 'date1' => new DateTimeImmutable('2010-10-04 13:45'), + 'date2' => new DateTimeImmutable('2010-10-04 13:45', new DateTimeZone('America/New_York')), + 'timezone1' => new DateTimeZone('America/New_York'), +) +--EXPECT-- +October 4, 2010 13:45 +04/10/2010 +04/10/2010 19:45:00 +04/10/2010 07:45:00 +04/10/2010 13:45:00 +04/10/2010 14:45:00 + +04/10/2010 19:45:00 +02:00 +05/10/2010 01:45:00 +08:00 +04/10/2010 13:45:00 -04:00 +Europe/Paris +America/New_York diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_interval.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_interval.test new file mode 100644 index 0000000..0c8c6f1 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_interval.test @@ -0,0 +1,19 @@ +--TEST-- +"date" filter (interval support as of PHP 5.3) +--CONDITION-- +version_compare(phpversion(), '5.3.0', '>=') +--TEMPLATE-- +{{ date1|date }} +{{ date1|date('%d days %h hours') }} +{{ date1|date('%d days %h hours', timezone1) }} +--DATA-- +date_default_timezone_set('UTC'); +return array( + 'date1' => new DateInterval('P2D'), + // This should have no effect on DateInterval formatting + 'timezone1' => new DateTimeZone('America/New_York'), +) +--EXPECT-- +2 days +2 days 0 hours +2 days 0 hours diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_modify.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_modify.test new file mode 100644 index 0000000..53d3a69 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_modify.test @@ -0,0 +1,14 @@ +--TEST-- +"date_modify" filter +--TEMPLATE-- +{{ date1|date_modify('-1day')|date('Y-m-d H:i:s') }} +{{ date2|date_modify('-1day')|date('Y-m-d H:i:s') }} +--DATA-- +date_default_timezone_set('UTC'); +return array( + 'date1' => '2010-10-04 13:45', + 'date2' => new DateTime('2010-10-04 13:45'), +) +--EXPECT-- +2010-10-03 13:45:00 +2010-10-03 13:45:00 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_namedargs.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_namedargs.test new file mode 100644 index 0000000..4ecde8a --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/date_namedargs.test @@ -0,0 +1,13 @@ +--TEST-- +"date" filter +--TEMPLATE-- +{{ date|date(format='d/m/Y H:i:s P', timezone='America/Chicago') }} +{{ date|date(timezone='America/Chicago', format='d/m/Y H:i:s P') }} +{{ date|date('d/m/Y H:i:s P', timezone='America/Chicago') }} +--DATA-- +date_default_timezone_set('UTC'); +return array('date' => mktime(13, 45, 0, 10, 4, 2010)) +--EXPECT-- +04/10/2010 08:45:00 -05:00 +04/10/2010 08:45:00 -05:00 +04/10/2010 08:45:00 -05:00 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/default.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/default.test new file mode 100644 index 0000000..b8d1d66 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/default.test @@ -0,0 +1,150 @@ +--TEST-- +"default" filter +--TEMPLATE-- +Variable: +{{ definedVar |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ zeroVar |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ emptyVar |default('default') is same as('default') ? 'ok' : 'ko' }} +{{ nullVar |default('default') is same as('default') ? 'ok' : 'ko' }} +{{ undefinedVar |default('default') is same as('default') ? 'ok' : 'ko' }} +Array access: +{{ nested.definedVar |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ nested['definedVar'] |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ nested.zeroVar |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ nested.emptyVar |default('default') is same as('default') ? 'ok' : 'ko' }} +{{ nested.nullVar |default('default') is same as('default') ? 'ok' : 'ko' }} +{{ nested.undefinedVar |default('default') is same as('default') ? 'ok' : 'ko' }} +{{ nested['undefinedVar'] |default('default') is same as('default') ? 'ok' : 'ko' }} +{{ undefinedVar.foo |default('default') is same as('default') ? 'ok' : 'ko' }} +Plain values: +{{ 'defined' |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ 0 |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ '' |default('default') is same as('default') ? 'ok' : 'ko' }} +{{ null |default('default') is same as('default') ? 'ok' : 'ko' }} +Precedence: +{{ 'o' ~ nullVar |default('k') }} +{{ 'o' ~ nested.nullVar |default('k') }} +Object methods: +{{ object.foo |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ object.undefinedMethod |default('default') is same as('default') ? 'ok' : 'ko' }} +{{ object.getFoo() |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ object.getFoo('a') |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ object.undefinedMethod() |default('default') is same as('default') ? 'ok' : 'ko' }} +{{ object.undefinedMethod('a') |default('default') is same as('default') ? 'ok' : 'ko' }} +Deep nested: +{{ nested.undefinedVar.foo.bar |default('default') is same as('default') ? 'ok' : 'ko' }} +{{ nested.definedArray.0 |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ nested['definedArray'][0] |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ object.self.foo |default('default') is same as('default') ? 'ko' : 'ok' }} +{{ object.self.undefinedMethod |default('default') is same as('default') ? 'ok' : 'ko' }} +{{ object.undefinedMethod.self |default('default') is same as('default') ? 'ok' : 'ko' }} +--DATA-- +return array( + 'definedVar' => 'defined', + 'zeroVar' => 0, + 'emptyVar' => '', + 'nullVar' => null, + 'nested' => array( + 'definedVar' => 'defined', + 'zeroVar' => 0, + 'emptyVar' => '', + 'nullVar' => null, + 'definedArray' => array(0), + ), + 'object' => new TwigTestFoo(), +) +--CONFIG-- +return array('strict_variables' => false) +--EXPECT-- +Variable: +ok +ok +ok +ok +ok +Array access: +ok +ok +ok +ok +ok +ok +ok +ok +Plain values: +ok +ok +ok +ok +Precedence: +ok +ok +Object methods: +ok +ok +ok +ok +ok +ok +Deep nested: +ok +ok +ok +ok +ok +ok +--DATA-- +return array( + 'definedVar' => 'defined', + 'zeroVar' => 0, + 'emptyVar' => '', + 'nullVar' => null, + 'nested' => array( + 'definedVar' => 'defined', + 'zeroVar' => 0, + 'emptyVar' => '', + 'nullVar' => null, + 'definedArray' => array(0), + ), + 'object' => new TwigTestFoo(), +) +--CONFIG-- +return array('strict_variables' => true) +--EXPECT-- +Variable: +ok +ok +ok +ok +ok +Array access: +ok +ok +ok +ok +ok +ok +ok +ok +Plain values: +ok +ok +ok +ok +Precedence: +ok +ok +Object methods: +ok +ok +ok +ok +ok +ok +Deep nested: +ok +ok +ok +ok +ok +ok diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/dynamic_filter.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/dynamic_filter.test new file mode 100644 index 0000000..93c5913 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/dynamic_filter.test @@ -0,0 +1,10 @@ +--TEST-- +dynamic filter +--TEMPLATE-- +{{ 'bar'|foo_path }} +{{ 'bar'|a_foo_b_bar }} +--DATA-- +return array() +--EXPECT-- +foo/bar +a/b/bar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape.test new file mode 100644 index 0000000..a606c10 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape.test @@ -0,0 +1,8 @@ +--TEST-- +"escape" filter +--TEMPLATE-- +{{ "foo
    "|e }} +--DATA-- +return array() +--EXPECT-- +foo <br /> diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape_html_attr.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape_html_attr.test new file mode 100644 index 0000000..009a245 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape_html_attr.test @@ -0,0 +1,8 @@ +--TEST-- +"escape" filter does not escape with the html strategy when using the html_attr strategy +--TEMPLATE-- +{{ '
    '|escape('html_attr') }} +--DATA-- +return array() +--EXPECT-- +<br /> diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape_non_supported_charset.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape_non_supported_charset.test new file mode 100644 index 0000000..bba26a0 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape_non_supported_charset.test @@ -0,0 +1,8 @@ +--TEST-- +"escape" filter +--TEMPLATE-- +{{ "愛していますか?
    "|e }} +--DATA-- +return array() +--EXPECT-- +愛していますか? <br /> diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/first.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/first.test new file mode 100644 index 0000000..aa54645 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/first.test @@ -0,0 +1,17 @@ +--TEST-- +"first" filter +--TEMPLATE-- +{{ [1, 2, 3, 4]|first }} +{{ {a: 1, b: 2, c: 3, d: 4}|first }} +{{ '1234'|first }} +{{ arr|first }} +{{ 'Ä€é'|first }} +{{ ''|first }} +--DATA-- +return array('arr' => new ArrayObject(array(1, 2, 3, 4))) +--EXPECT-- +1 +1 +1 +1 +Ä diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/force_escape.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/force_escape.test new file mode 100644 index 0000000..85a9b71 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/force_escape.test @@ -0,0 +1,18 @@ +--TEST-- +"escape" filter +--TEMPLATE-- +{% set foo %} + foo
    +{% endset %} + +{{ foo|e('html') -}} +{{ foo|e('js') }} +{% autoescape true %} + {{ foo }} +{% endautoescape %} +--DATA-- +return array() +--EXPECT-- + foo<br /> +\x20\x20\x20\x20foo\x3Cbr\x20\x2F\x3E\x0A + foo
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/format.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/format.test new file mode 100644 index 0000000..97221ff --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/format.test @@ -0,0 +1,8 @@ +--TEST-- +"format" filter +--TEMPLATE-- +{{ string|format(foo, 3) }} +--DATA-- +return array('string' => '%s/%d', 'foo' => 'bar') +--EXPECT-- +bar/3 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/join.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/join.test new file mode 100644 index 0000000..b342c17 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/join.test @@ -0,0 +1,12 @@ +--TEST-- +"join" filter +--TEMPLATE-- +{{ ["foo", "bar"]|join(', ') }} +{{ foo|join(', ') }} +{{ bar|join(', ') }} +--DATA-- +return array('foo' => new TwigTestFoo(), 'bar' => new ArrayObject(array(3, 4))) +--EXPECT-- +foo, bar +1, 2 +3, 4 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/json_encode.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/json_encode.test new file mode 100644 index 0000000..1738d40 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/json_encode.test @@ -0,0 +1,12 @@ +--TEST-- +"json_encode" filter +--TEMPLATE-- +{{ "foo"|json_encode|raw }} +{{ foo|json_encode|raw }} +{{ [foo, "foo"]|json_encode|raw }} +--DATA-- +return array('foo' => new Twig_Markup('foo', 'UTF-8')) +--EXPECT-- +"foo" +"foo" +["foo","foo"] diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/last.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/last.test new file mode 100644 index 0000000..1b8031e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/last.test @@ -0,0 +1,17 @@ +--TEST-- +"last" filter +--TEMPLATE-- +{{ [1, 2, 3, 4]|last }} +{{ {a: 1, b: 2, c: 3, d: 4}|last }} +{{ '1234'|last }} +{{ arr|last }} +{{ 'Ä€é'|last }} +{{ ''|last }} +--DATA-- +return array('arr' => new ArrayObject(array(1, 2, 3, 4))) +--EXPECT-- +4 +4 +4 +4 +é diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/length.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/length.test new file mode 100644 index 0000000..3347474 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/length.test @@ -0,0 +1,14 @@ +--TEST-- +"length" filter +--TEMPLATE-- +{{ array|length }} +{{ string|length }} +{{ number|length }} +{{ markup|length }} +--DATA-- +return array('array' => array(1, 4), 'string' => 'foo', 'number' => 1000, 'markup' => new Twig_Markup('foo', 'UTF-8')) +--EXPECT-- +2 +3 +4 +3 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/length_utf8.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/length_utf8.test new file mode 100644 index 0000000..5d5e243 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/length_utf8.test @@ -0,0 +1,12 @@ +--TEST-- +"length" filter +--CONDITION-- +function_exists('mb_get_info') +--TEMPLATE-- +{{ string|length }} +{{ markup|length }} +--DATA-- +return array('string' => 'été', 'markup' => new Twig_Markup('foo', 'UTF-8')) +--EXPECT-- +3 +3 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/merge.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/merge.test new file mode 100644 index 0000000..81371a4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/merge.test @@ -0,0 +1,18 @@ +--TEST-- +"merge" filter +--TEMPLATE-- +{{ items|merge({'bar': 'foo'})|join }} +{{ items|merge({'bar': 'foo'})|keys|join }} +{{ {'bar': 'foo'}|merge(items)|join }} +{{ {'bar': 'foo'}|merge(items)|keys|join }} +{{ numerics|merge([4, 5, 6])|join }} +{{ traversable.a|merge(traversable.b)|join }} +--DATA-- +return array('items' => array('foo' => 'bar'), 'numerics' => array(1, 2, 3), 'traversable' => array('a' => new ArrayObject(array(0 => 1, 1 => 2, 2 => 3)), 'b' => new ArrayObject(array('a' => 'b')))) +--EXPECT-- +barfoo +foobar +foobar +barfoo +123456 +123b diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/nl2br.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/nl2br.test new file mode 100644 index 0000000..6545a9b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/nl2br.test @@ -0,0 +1,14 @@ +--TEST-- +"nl2br" filter +--TEMPLATE-- +{{ "I like Twig.\nYou will like it too.\n\nEverybody like it!"|nl2br }} +{{ text|nl2br }} +--DATA-- +return array('text' => "If you have some HTML\nit will be escaped.") +--EXPECT-- +I like Twig.
    +You will like it too.
    +
    +Everybody like it! +If you have some <strong>HTML</strong>
    +it will be escaped. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/number_format.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/number_format.test new file mode 100644 index 0000000..639a865 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/number_format.test @@ -0,0 +1,18 @@ +--TEST-- +"number_format" filter +--TEMPLATE-- +{{ 20|number_format }} +{{ 20.25|number_format }} +{{ 20.25|number_format(2) }} +{{ 20.25|number_format(2, ',') }} +{{ 1020.25|number_format(2, ',') }} +{{ 1020.25|number_format(2, ',', '.') }} +--DATA-- +return array(); +--EXPECT-- +20 +20 +20.25 +20,25 +1,020,25 +1.020,25 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/number_format_default.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/number_format_default.test new file mode 100644 index 0000000..65c1cdb --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/number_format_default.test @@ -0,0 +1,21 @@ +--TEST-- +"number_format" filter with defaults. +--TEMPLATE-- +{{ 20|number_format }} +{{ 20.25|number_format }} +{{ 20.25|number_format(1) }} +{{ 20.25|number_format(2, ',') }} +{{ 1020.25|number_format }} +{{ 1020.25|number_format(2, ',') }} +{{ 1020.25|number_format(2, ',', '.') }} +--DATA-- +$twig->getExtension('Twig_Extension_Core')->setNumberFormat(2, '!', '='); +return array(); +--EXPECT-- +20!00 +20!25 +20!3 +20,25 +1=020!25 +1=020,25 +1.020,25 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/replace.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/replace.test new file mode 100644 index 0000000..06be7e2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/replace.test @@ -0,0 +1,12 @@ +--TEST-- +"replace" filter +--TEMPLATE-- +{{ "I liké %this% and %that%."|replace({'%this%': "foo", '%that%': "bar"}) }} +{{ 'I like single replace operation only %that%'|replace({'%that%' : '%that%1'}) }} +{{ 'I like %this% and %that%.'|replace(traversable) }} +--DATA-- +return array('traversable' => new ArrayObject(array('%this%' => 'foo', '%that%' => 'bar'))) +--EXPECT-- +I liké foo and bar. +I like single replace operation only %that%1 +I like foo and bar. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/replace_invalid_arg.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/replace_invalid_arg.test new file mode 100644 index 0000000..2143a86 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/replace_invalid_arg.test @@ -0,0 +1,8 @@ +--TEST-- +Exception for invalid argument type in replace call +--TEMPLATE-- +{{ 'test %foo%'|replace(stdClass) }} +--DATA-- +return array('stdClass' => new stdClass()) +--EXCEPTION-- +Twig_Error_Runtime: The "replace" filter expects an array or "Traversable" as replace values, got "stdClass" in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/reverse.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/reverse.test new file mode 100644 index 0000000..7948ac4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/reverse.test @@ -0,0 +1,18 @@ +--TEST-- +"reverse" filter +--TEMPLATE-- +{{ [1, 2, 3, 4]|reverse|join('') }} +{{ '1234évènement'|reverse }} +{{ arr|reverse|join('') }} +{{ {'a': 'c', 'b': 'a'}|reverse()|join(',') }} +{{ {'a': 'c', 'b': 'a'}|reverse(preserveKeys=true)|join(glue=',') }} +{{ {'a': 'c', 'b': 'a'}|reverse(preserve_keys=true)|join(glue=',') }} +--DATA-- +return array('arr' => new ArrayObject(array(1, 2, 3, 4))) +--EXPECT-- +4321 +tnemenèvé4321 +4321 +a,c +a,c +a,c diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/round.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/round.test new file mode 100644 index 0000000..57806b6 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/round.test @@ -0,0 +1,22 @@ +--TEST-- +"round" filter +--TEMPLATE-- +{{ 2.7|round }} +{{ 2.1|round }} +{{ 2.1234|round(3, 'floor') }} +{{ 2.1|round(0, 'ceil') }} + +{{ 21.3|round(-1)}} +{{ 21.3|round(-1, 'ceil')}} +{{ 21.3|round(-1, 'floor')}} +--DATA-- +return array() +--EXPECT-- +3 +2 +2.123 +3 + +20 +30 +20 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/slice.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/slice.test new file mode 100644 index 0000000..b49b89f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/slice.test @@ -0,0 +1,54 @@ +--TEST-- +"slice" filter +--TEMPLATE-- +{{ [1, 2, 3, 4][1:2]|join('') }} +{{ {a: 1, b: 2, c: 3, d: 4}[1:2]|join('') }} +{{ [1, 2, 3, 4][start:length]|join('') }} +{{ [1, 2, 3, 4]|slice(1, 2)|join('') }} +{{ [1, 2, 3, 4]|slice(1, 2)|keys|join('') }} +{{ [1, 2, 3, 4]|slice(1, 2, true)|keys|join('') }} +{{ {a: 1, b: 2, c: 3, d: 4}|slice(1, 2)|join('') }} +{{ {a: 1, b: 2, c: 3, d: 4}|slice(1, 2)|keys|join('') }} +{{ '1234'|slice(1, 2) }} +{{ '1234'[1:2] }} +{{ arr|slice(1, 2)|join('') }} +{{ arr[1:2]|join('') }} +{{ arr[4:1]|join('') }} +{{ arr[3:2]|join('') }} + +{{ [1, 2, 3, 4]|slice(1)|join('') }} +{{ [1, 2, 3, 4][1:]|join('') }} +{{ '1234'|slice(1) }} +{{ '1234'[1:] }} +{{ '1234'[:1] }} + +{{ arr|slice(3)|join('') }} +{{ arr[2:]|join('') }} +{{ xml|slice(1)|join('')}} +--DATA-- +return array('start' => 1, 'length' => 2, 'arr' => new ArrayObject(array(1, 2, 3, 4)), 'xml' => new SimpleXMLElement('12')) +--EXPECT-- +23 +23 +23 +23 +01 +12 +23 +bc +23 +23 +23 +23 + +4 + +234 +234 +234 +234 +1 + +4 +34 +2 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/sort.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/sort.test new file mode 100644 index 0000000..c67c18e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/sort.test @@ -0,0 +1,12 @@ +--TEST-- +"sort" filter +--TEMPLATE-- +{{ array1|sort|join }} +{{ array2|sort|join }} +{{ traversable|sort|join }} +--DATA-- +return array('array1' => array(4, 1), 'array2' => array('foo', 'bar'), 'traversable' => new ArrayObject(array(0 => 3, 1 => 2, 2 => 1))) +--EXPECT-- +14 +barfoo +123 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/special_chars.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/special_chars.test new file mode 100644 index 0000000..dbaf7dc --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/special_chars.test @@ -0,0 +1,8 @@ +--TEST-- +"§" custom filter +--TEMPLATE-- +{{ 'foo'|§ }} +--DATA-- +return array() +--EXPECT-- +§foo§ diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/split.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/split.test new file mode 100644 index 0000000..a093ed7 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/split.test @@ -0,0 +1,20 @@ +--TEST-- +"split" filter +--TEMPLATE-- +{{ "one,two,three,four,five"|split(',')|join('-') }} +{{ foo|split(',')|join('-') }} +{{ foo|split(',', 3)|join('-') }} +{{ baz|split('')|join('-') }} +{{ baz|split('', 1)|join('-') }} +{{ baz|split('', 2)|join('-') }} +{{ foo|split(',', -2)|join('-') }} +--DATA-- +return array('foo' => "one,two,three,four,five", 'baz' => '12345',) +--EXPECT-- +one-two-three-four-five +one-two-three-four-five +one-two-three,four,five +1-2-3-4-5 +1-2-3-4-5 +12-34-5 +one-two-three \ No newline at end of file diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/split_utf8.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/split_utf8.test new file mode 100644 index 0000000..305e162 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/split_utf8.test @@ -0,0 +1,24 @@ +--TEST-- +"split" filter +--CONDITION-- +function_exists('mb_get_info') +--TEMPLATE-- +{{ "é"|split('', 10)|join('-') }} +{{ foo|split(',')|join('-') }} +{{ foo|split(',', 1)|join('-') }} +{{ foo|split(',', 2)|join('-') }} +{{ foo|split(',', 3)|join('-') }} +{{ baz|split('')|join('-') }} +{{ baz|split('', 1)|join('-') }} +{{ baz|split('', 2)|join('-') }} +--DATA-- +return array('foo' => 'Ä,é,Äほ', 'baz' => 'éÄßごa',) +--EXPECT-- +é +Ä-é-Äほ +Ä,é,Äほ +Ä-é,Äほ +Ä-é-Äほ +é-Ä-ß-ご-a +é-Ä-ß-ご-a +éÄ-ßご-a \ No newline at end of file diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/static_calls.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/static_calls.test new file mode 100644 index 0000000..4e17b77 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/static_calls.test @@ -0,0 +1,10 @@ +--TEST-- +Filters as static method calls +--TEMPLATE-- +{{ 'foo'|static_call_string }} +{{ 'foo'|static_call_array }} +--DATA-- +return array('foo' => 'foo') +--EXPECT-- +*foo* +*foo* diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/trim.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/trim.test new file mode 100644 index 0000000..3192062 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/trim.test @@ -0,0 +1,12 @@ +--TEST-- +"trim" filter +--TEMPLATE-- +{{ " I like Twig. "|trim }} +{{ text|trim }} +{{ " foo/"|trim("/") }} +--DATA-- +return array('text' => " If you have some HTML it will be escaped. ") +--EXPECT-- +I like Twig. +If you have some <strong>HTML</strong> it will be escaped. + foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/urlencode.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/urlencode.test new file mode 100644 index 0000000..8726159 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/urlencode.test @@ -0,0 +1,16 @@ +--TEST-- +"url_encode" filter +--CONDITION-- +defined('PHP_QUERY_RFC3986') +--TEMPLATE-- +{{ {foo: "bar", number: 3, "spéßi%l": "e%c0d@d", "spa ce": ""}|url_encode }} +{{ {foo: "bar", number: 3, "spéßi%l": "e%c0d@d", "spa ce": ""}|url_encode|raw }} +{{ {}|url_encode|default("default") }} +{{ 'spéßi%le%c0d@dspa ce'|url_encode }} +--DATA-- +return array() +--EXPECT-- +foo=bar&number=3&sp%C3%A9%C3%9Fi%25l=e%25c0d%40d&spa%20ce= +foo=bar&number=3&sp%C3%A9%C3%9Fi%25l=e%25c0d%40d&spa%20ce= +default +sp%C3%A9%C3%9Fi%25le%25c0d%40dspa%20ce diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/urlencode_deprecated.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/urlencode_deprecated.test new file mode 100644 index 0000000..11800e9 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/urlencode_deprecated.test @@ -0,0 +1,16 @@ +--TEST-- +"url_encode" filter for PHP < 5.4 and HHVM +--CONDITION-- +defined('PHP_QUERY_RFC3986') +--TEMPLATE-- +{{ {foo: "bar", number: 3, "spéßi%l": "e%c0d@d", "spa ce": ""}|url_encode }} +{{ {foo: "bar", number: 3, "spéßi%l": "e%c0d@d", "spa ce": ""}|url_encode|raw }} +{{ {}|url_encode|default("default") }} +{{ 'spéßi%le%c0d@dspa ce'|url_encode }} +--DATA-- +return array() +--EXPECT-- +foo=bar&number=3&sp%C3%A9%C3%9Fi%25l=e%25c0d%40d&spa%20ce= +foo=bar&number=3&sp%C3%A9%C3%9Fi%25l=e%25c0d%40d&spa%20ce= +default +sp%C3%A9%C3%9Fi%25le%25c0d%40dspa%20ce diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/attribute.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/attribute.test new file mode 100644 index 0000000..71b2038 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/attribute.test @@ -0,0 +1,18 @@ +--TEST-- +"attribute" function +--TEMPLATE-- +{{ attribute(obj, method) }} +{{ attribute(array, item) }} +{{ attribute(obj, "bar", ["a", "b"]) }} +{{ attribute(obj, "bar", arguments) }} +{{ attribute(obj, method) is defined ? 'ok' : 'ko' }} +{{ attribute(obj, nonmethod) is defined ? 'ok' : 'ko' }} +--DATA-- +return array('obj' => new TwigTestFoo(), 'method' => 'foo', 'array' => array('foo' => 'bar'), 'item' => 'foo', 'nonmethod' => 'xxx', 'arguments' => array('a', 'b')) +--EXPECT-- +foo +bar +bar_a-b +bar_a-b +ok +ko diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/block.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/block.test new file mode 100644 index 0000000..8e54059 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/block.test @@ -0,0 +1,12 @@ +--TEST-- +"block" function +--TEMPLATE-- +{% extends 'base.twig' %} +{% block bar %}BAR{% endblock %} +--TEMPLATE(base.twig)-- +{% block foo %}{{ block('bar') }}{% endblock %} +{% block bar %}BAR_BASE{% endblock %} +--DATA-- +return array() +--EXPECT-- +BARBAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/block_with_template.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/block_with_template.test new file mode 100644 index 0000000..8305eb6 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/block_with_template.test @@ -0,0 +1,22 @@ +--TEST-- +"block" function with a template argument +--TEMPLATE-- +{{ block('foo', 'included.twig') }} +{{ block('foo', included_loaded) }} +{{ block('foo', included_loaded_internal) }} +{% set output = block('foo', 'included.twig') %} +{{ output }} +{% block foo %}NOT FOO{% endblock %} +--TEMPLATE(included.twig)-- +{% block foo %}FOO{% endblock %} +--DATA-- +return array( + 'included_loaded' => $twig->load('included.twig'), + 'included_loaded_internal' => $twig->loadTemplate('included.twig'), +) +--EXPECT-- +FOO +FOO +FOO +FOO +NOT FOO diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/block_without_name.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/block_without_name.test new file mode 100644 index 0000000..665cc87 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/block_without_name.test @@ -0,0 +1,12 @@ +--TEST-- +"block" function without arguments +--TEMPLATE-- +{% extends 'base.twig' %} +{% block bar %}BAR{% endblock %} +--TEMPLATE(base.twig)-- +{% block foo %}{{ block() }}{% endblock %} +{% block bar %}BAR_BASE{% endblock %} +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Syntax: The "block" function takes one argument (the block name) in "base.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/constant.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/constant.test new file mode 100644 index 0000000..6312879 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/constant.test @@ -0,0 +1,10 @@ +--TEST-- +"constant" function +--TEMPLATE-- +{{ constant('DATE_W3C') == expect ? 'true' : 'false' }} +{{ constant('ARRAY_AS_PROPS', object) }} +--DATA-- +return array('expect' => DATE_W3C, 'object' => new ArrayObject(array('hi'))); +--EXPECT-- +true +2 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/cycle.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/cycle.test new file mode 100644 index 0000000..522a63b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/cycle.test @@ -0,0 +1,16 @@ +--TEST-- +"cycle" function +--TEMPLATE-- +{% for i in 0..6 %} +{{ cycle(array1, i) }}-{{ cycle(array2, i) }} +{% endfor %} +--DATA-- +return array('array1' => array('odd', 'even'), 'array2' => array('apple', 'orange', 'citrus')) +--EXPECT-- +odd-apple +even-orange +odd-citrus +even-apple +odd-orange +even-citrus +odd-apple diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/date.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/date.test new file mode 100644 index 0000000..a9ffe40 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/date.test @@ -0,0 +1,25 @@ +--TEST-- +"date" function +--TEMPLATE-- +{{ date().format('r') == date('now').format('r') ? 'OK' : 'KO' }} +{{ date(date1) == date('2010-10-04 13:45') ? 'OK' : 'KO' }} +{{ date(date2) == date('2010-10-04 13:45') ? 'OK' : 'KO' }} +{{ date(date3) == date('2010-10-04 13:45') ? 'OK' : 'KO' }} +{{ date(date4) == date('2010-10-04 13:45') ? 'OK' : 'KO' }} +{{ date(date5) == date('1964-01-02 03:04') ? 'OK' : 'KO' }} +--DATA-- +date_default_timezone_set('UTC'); +return array( + 'date1' => mktime(13, 45, 0, 10, 4, 2010), + 'date2' => new DateTime('2010-10-04 13:45'), + 'date3' => '2010-10-04 13:45', + 'date4' => 1286199900, // DateTime::createFromFormat('Y-m-d H:i', '2010-10-04 13:45', new DateTimeZone('UTC'))->getTimestamp() -- A unixtimestamp is always GMT + 'date5' => -189291360, // DateTime::createFromFormat('Y-m-d H:i', '1964-01-02 03:04', new DateTimeZone('UTC'))->getTimestamp(), +) +--EXPECT-- +OK +OK +OK +OK +OK +OK diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/date_namedargs.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/date_namedargs.test new file mode 100644 index 0000000..b9dd9e3 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/date_namedargs.test @@ -0,0 +1,11 @@ +--TEST-- +"date" function +--TEMPLATE-- +{{ date(date, "America/New_York")|date('d/m/Y H:i:s P', false) }} +{{ date(timezone="America/New_York", date=date)|date('d/m/Y H:i:s P', false) }} +--DATA-- +date_default_timezone_set('UTC'); +return array('date' => mktime(13, 45, 0, 10, 4, 2010)) +--EXPECT-- +04/10/2010 09:45:00 -04:00 +04/10/2010 09:45:00 -04:00 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/dump.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/dump.test new file mode 100644 index 0000000..f407237 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/dump.test @@ -0,0 +1,16 @@ +--TEST-- +"dump" function +--CONDITION-- +!extension_loaded('xdebug') +--TEMPLATE-- +{{ dump('foo') }} +{{ dump('foo', 'bar') }} +--DATA-- +return array('foo' => 'foo', 'bar' => 'bar') +--CONFIG-- +return array('debug' => true, 'autoescape' => false); +--EXPECT-- +string(3) "foo" + +string(3) "foo" +string(3) "bar" diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/dump_array.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/dump_array.test new file mode 100644 index 0000000..889b7a9 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/dump_array.test @@ -0,0 +1,19 @@ +--TEST-- +"dump" function, xdebug is not loaded or xdebug <2.2-dev is loaded +--CONDITION-- +!extension_loaded('xdebug') || (($r = new ReflectionExtension('xdebug')) && version_compare($r->getVersion(), '2.2-dev', '<')) +--TEMPLATE-- +{{ dump() }} +--DATA-- +return array('foo' => 'foo', 'bar' => 'bar') +--CONFIG-- +return array('debug' => true, 'autoescape' => false); +--EXPECT-- +array(3) { + ["foo"]=> + string(3) "foo" + ["bar"]=> + string(3) "bar" + ["global"]=> + string(6) "global" +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/dynamic_function.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/dynamic_function.test new file mode 100644 index 0000000..913fbc9 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/dynamic_function.test @@ -0,0 +1,10 @@ +--TEST-- +dynamic function +--TEMPLATE-- +{{ foo_path('bar') }} +{{ a_foo_b_bar('bar') }} +--DATA-- +return array() +--EXPECT-- +foo/bar +a/b/bar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/assignment.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/assignment.test new file mode 100644 index 0000000..b7653b4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/assignment.test @@ -0,0 +1,13 @@ +--TEST-- +"include" function +--TEMPLATE-- +{% set tmp = include("foo.twig") %} + +FOO{{ tmp }}BAR +--TEMPLATE(foo.twig)-- +FOOBAR +--DATA-- +return array() +--EXPECT-- +FOO +FOOBARBAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/autoescaping.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/autoescaping.test new file mode 100644 index 0000000..56f8f3b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/autoescaping.test @@ -0,0 +1,10 @@ +--TEST-- +"include" function is safe for auto-escaping +--TEMPLATE-- +{{ include("foo.twig") }} +--TEMPLATE(foo.twig)-- +

    Test

    +--DATA-- +return array() +--EXPECT-- +

    Test

    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/basic.test new file mode 100644 index 0000000..a434182 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/basic.test @@ -0,0 +1,17 @@ +--TEST-- +"include" function +--TEMPLATE-- +FOO +{{ include("foo.twig") }} + +BAR +--TEMPLATE(foo.twig)-- +FOOBAR +--DATA-- +return array() +--EXPECT-- +FOO + +FOOBAR + +BAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/expression.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/expression.test new file mode 100644 index 0000000..aba30ce --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/expression.test @@ -0,0 +1,17 @@ +--TEST-- +"include" function allows expressions for the template to include +--TEMPLATE-- +FOO +{{ include(foo) }} + +BAR +--TEMPLATE(foo.twig)-- +FOOBAR +--DATA-- +return array('foo' => 'foo.twig') +--EXPECT-- +FOO + +FOOBAR + +BAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/ignore_missing.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/ignore_missing.test new file mode 100644 index 0000000..43a2ccc --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/ignore_missing.test @@ -0,0 +1,10 @@ +--TEST-- +"include" function +--TEMPLATE-- +{{ include(["foo.twig", "bar.twig"], ignore_missing = true) }} +{{ include("foo.twig", ignore_missing = true) }} +{{ include("foo.twig", ignore_missing = true, variables = {}) }} +{{ include("foo.twig", ignore_missing = true, variables = {}, with_context = true) }} +--DATA-- +return array() +--EXPECT-- diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/missing.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/missing.test new file mode 100644 index 0000000..4d2f6cf --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/missing.test @@ -0,0 +1,8 @@ +--TEST-- +"include" function +--TEMPLATE-- +{{ include("foo.twig") }} +--DATA-- +return array(); +--EXCEPTION-- +Twig_Error_Loader: Template "foo.twig" is not defined in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/missing_nested.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/missing_nested.test new file mode 100644 index 0000000..78fddc7 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/missing_nested.test @@ -0,0 +1,16 @@ +--TEST-- +"include" function +--TEMPLATE-- +{% extends "base.twig" %} + +{% block content %} + {{ parent() }} +{% endblock %} +--TEMPLATE(base.twig)-- +{% block content %} + {{ include("foo.twig") }} +{% endblock %} +--DATA-- +return array(); +--EXCEPTION-- +Twig_Error_Loader: Template "foo.twig" is not defined in "base.twig" at line 3. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox.test new file mode 100644 index 0000000..7b9ccac --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox.test @@ -0,0 +1,13 @@ +--TEST-- +"include" tag sandboxed +--TEMPLATE-- +{{ include("foo.twig", sandboxed = true) }} +--TEMPLATE(foo.twig)-- + + +{{ foo|e }} +{{ foo|e }} +--DATA-- +return array() +--EXCEPTION-- +Twig_Sandbox_SecurityNotAllowedFilterError: Filter "e" is not allowed in "foo.twig" at line 4. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox_disabling.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox_disabling.test new file mode 100644 index 0000000..8ffc492 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox_disabling.test @@ -0,0 +1,16 @@ +--TEST-- +"include" tag sandboxed +--TEMPLATE-- +{{ include("foo.twig", sandboxed = true) }} +{{ include("bar.twig") }} +--TEMPLATE(foo.twig)-- +foo +--TEMPLATE(bar.twig)-- +{{ foo|e }} +--DATA-- +return array('foo' => 'bar
    ') +--EXPECT-- +foo + + +bar<br /> diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.test new file mode 100644 index 0000000..8bf6e10 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.test @@ -0,0 +1,13 @@ +--TEST-- +"include" tag sandboxed +--TEMPLATE-- +{{ include("unknown.twig", sandboxed = true, ignore_missing = true) }} +{{ include("bar.twig") }} +--TEMPLATE(bar.twig)-- +{{ foo|e }} +--DATA-- +return array('foo' => 'bar
    ') +--EXPECT-- + + +bar<br /> diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/template_instance.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/template_instance.test new file mode 100644 index 0000000..18d405a --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/template_instance.test @@ -0,0 +1,10 @@ +--TEST-- +"include" function accepts Twig_Template instance +--TEMPLATE-- +{{ include(foo) }} FOO +--TEMPLATE(foo.twig)-- +BAR +--DATA-- +return array('foo' => $twig->loadTemplate('foo.twig')) +--EXPECT-- +BAR FOO diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/templates_as_array.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/templates_as_array.test new file mode 100644 index 0000000..1a81006 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/templates_as_array.test @@ -0,0 +1,12 @@ +--TEST-- +"include" function +--TEMPLATE-- +{{ include(["foo.twig", "bar.twig"]) }} +{{- include(["bar.twig", "foo.twig"]) }} +--TEMPLATE(foo.twig)-- +foo +--DATA-- +return array() +--EXPECT-- +foo +foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/with_context.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/with_context.test new file mode 100644 index 0000000..35611fb --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/with_context.test @@ -0,0 +1,16 @@ +--TEST-- +"include" function accept variables and with_context +--TEMPLATE-- +{{ include("foo.twig") }} +{{- include("foo.twig", with_context = false) }} +{{- include("foo.twig", {'foo1': 'bar'}) }} +{{- include("foo.twig", {'foo1': 'bar'}, with_context = false) }} +--TEMPLATE(foo.twig)-- +{% for k, v in _context %}{{ k }},{% endfor %} +--DATA-- +return array('foo' => 'bar') +--EXPECT-- +foo,global,_parent, +global,_parent, +foo,global,foo1,_parent, +foo1,global,_parent, diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/with_variables.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/with_variables.test new file mode 100644 index 0000000..b2ace94 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/with_variables.test @@ -0,0 +1,12 @@ +--TEST-- +"include" function accept variables +--TEMPLATE-- +{{ include("foo.twig", {'foo': 'bar'}) }} +{{- include("foo.twig", vars) }} +--TEMPLATE(foo.twig)-- +{{ foo }} +--DATA-- +return array('vars' => array('foo' => 'bar')) +--EXPECT-- +bar +bar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/magic_call.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/magic_call.test new file mode 100644 index 0000000..9335443 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/magic_call.test @@ -0,0 +1,8 @@ +--TEST-- +__call calls +--TEMPLATE-- +{{ 'foo'|magic_call }} +--DATA-- +return array() +--EXPECT-- +magic_foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/magic_call53.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/magic_call53.test new file mode 100644 index 0000000..a0f55e1 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/magic_call53.test @@ -0,0 +1,12 @@ +--TEST-- +__staticCall calls +--CONDITION-- +version_compare(phpversion(), '5.3.0', '>=') +--TEMPLATE-- +{{ 'foo'|magic_call_string }} +{{ 'foo'|magic_call_array }} +--DATA-- +return array() +--EXPECT-- +static_magic_foo +static_magic_foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/max.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/max.test new file mode 100644 index 0000000..e6c94af --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/max.test @@ -0,0 +1,12 @@ +--TEST-- +"max" function +--TEMPLATE-- +{{ max([2, 1, 3, 5, 4]) }} +{{ max(2, 1, 3, 5, 4) }} +{{ max({2:"two", 1:"one", 3:"three", 5:"five", 4:"for"}) }} +--DATA-- +return array() +--EXPECT-- +5 +5 +two diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/min.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/min.test new file mode 100644 index 0000000..660471c --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/min.test @@ -0,0 +1,12 @@ +--TEST-- +"min" function +--TEMPLATE-- +{{ min(2, 1, 3, 5, 4) }} +{{ min([2, 1, 3, 5, 4]) }} +{{ min({2:"two", 1:"one", 3:"three", 5:"five", 4:"for"}) }} +--DATA-- +return array() +--EXPECT-- +1 +1 +five diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/range.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/range.test new file mode 100644 index 0000000..e0377c8 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/range.test @@ -0,0 +1,8 @@ +--TEST-- +"range" function +--TEMPLATE-- +{{ range(low=0+1, high=10+0, step=2)|join(',') }} +--DATA-- +return array() +--EXPECT-- +1,3,5,7,9 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/recursive_block_with_inheritance.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/recursive_block_with_inheritance.test new file mode 100644 index 0000000..bf0556d --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/recursive_block_with_inheritance.test @@ -0,0 +1,21 @@ +--TEST-- +"block" function recursively called in a parent template +--TEMPLATE-- +{% extends "ordered_menu.twig" %} +{% block label %}"{{ parent() }}"{% endblock %} +{% block list %}{% set class = 'b' %}{{ parent() }}{% endblock %} +--TEMPLATE(ordered_menu.twig)-- +{% extends "menu.twig" %} +{% block list %}{% set class = class|default('a') %}
      {{ block('children') }}
    {% endblock %} +--TEMPLATE(menu.twig)-- +{% extends "base.twig" %} +{% block list %}
      {{ block('children') }}
    {% endblock %} +{% block children %}{% set currentItem = item %}{% for item in currentItem %}{{ block('item') }}{% endfor %}{% set item = currentItem %}{% endblock %} +{% block item %}
  • {% if item is not iterable %}{{ block('label') }}{% else %}{{ block('list') }}{% endif %}
  • {% endblock %} +{% block label %}{{ item }}{% endblock %} +--TEMPLATE(base.twig)-- +{{ block('list') }} +--DATA-- +return array('item' => array('1', '2', array('3.1', array('3.2.1', '3.2.2'), '3.4'))) +--EXPECT-- +
    1. "1"
    2. "2"
      1. "3.1"
        1. "3.2.1"
        2. "3.2.2"
      2. "3.4"
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/source.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/source.test new file mode 100644 index 0000000..0e094c3 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/source.test @@ -0,0 +1,17 @@ +--TEST-- +"source" function +--TEMPLATE-- +FOO +{{ source("foo.twig") }} + +BAR +--TEMPLATE(foo.twig)-- +{{ foo }}
    +--DATA-- +return array() +--EXPECT-- +FOO + +{{ foo }}
    + +BAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/special_chars.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/special_chars.test new file mode 100644 index 0000000..30c3df5 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/special_chars.test @@ -0,0 +1,8 @@ +--TEST-- +"§" custom function +--TEMPLATE-- +{{ §('foo') }} +--DATA-- +return array() +--EXPECT-- +§foo§ diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/static_calls.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/static_calls.test new file mode 100644 index 0000000..57e5be3 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/static_calls.test @@ -0,0 +1,10 @@ +--TEST-- +Functions as static method calls +--TEMPLATE-- +{{ static_call_string('foo') }} +{{ static_call_array('foo') }} +--DATA-- +return array('foo' => 'foo') +--EXPECT-- +*foo* +*foo* diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/template_from_string.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/template_from_string.test new file mode 100644 index 0000000..3d3b958 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/functions/template_from_string.test @@ -0,0 +1,15 @@ +--TEST-- +"template_from_string" function +--TEMPLATE-- +{% include template_from_string(template) %} + +{% include template_from_string("Hello {{ name }}") %} +{% include template_from_string('{% extends "parent.twig" %}{% block content %}Hello {{ name }}{% endblock %}') %} +--TEMPLATE(parent.twig)-- +{% block content %}{% endblock %} +--DATA-- +return array('name' => 'Fabien', 'template' => "Hello {{ name }}") +--EXPECT-- +Hello Fabien +Hello Fabien +Hello Fabien diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/default_values.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/default_values.test new file mode 100644 index 0000000..4ccff7b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/default_values.test @@ -0,0 +1,16 @@ +--TEST-- +macro +--TEMPLATE-- +{% from _self import test %} + +{% macro test(a, b = 'bar') -%} +{{ a }}{{ b }} +{%- endmacro %} + +{{ test('foo') }} +{{ test('bar', 'foo') }} +--DATA-- +return array(); +--EXPECT-- +foobar +barfoo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/nested_calls.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/nested_calls.test new file mode 100644 index 0000000..cd25428 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/nested_calls.test @@ -0,0 +1,18 @@ +--TEST-- +macro +--TEMPLATE-- +{% import _self as macros %} + +{% macro foo(data) %} + {{ data }} +{% endmacro %} + +{% macro bar() %} +
    +{% endmacro %} + +{{ macros.foo(macros.bar()) }} +--DATA-- +return array(); +--EXPECT-- +
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/reserved_variables.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/reserved_variables.test new file mode 100644 index 0000000..cbfb921 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/reserved_variables.test @@ -0,0 +1,14 @@ +--TEST-- +macro +--TEMPLATE-- +{% from _self import test %} + +{% macro test(this) -%} + {{ this }} +{%- endmacro %} + +{{ test(this) }} +--DATA-- +return array('this' => 'foo'); +--EXPECT-- +foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/simple.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/simple.test new file mode 100644 index 0000000..6a366cd --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/simple.test @@ -0,0 +1,22 @@ +--TEST-- +macro +--TEMPLATE-- +{% import _self as test %} +{% from _self import test %} + +{% macro test(a, b) -%} + {{ a|default('a') }}
    + {{- b|default('b') }}
    +{%- endmacro %} + +{{ test.test() }} +{{ test() }} +{{ test.test(1, "c") }} +{{ test(1, "c") }} +--DATA-- +return array(); +--EXPECT-- +a
    b
    +a
    b
    +1
    c
    +1
    c
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/varargs.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/varargs.test new file mode 100644 index 0000000..412c90f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/varargs.test @@ -0,0 +1,21 @@ +--TEST-- +macro with arbitrary arguments +--TEMPLATE-- +{% from _self import test1, test2 %} + +{% macro test1(var) %} + {{- var }}: {{ varargs|join(", ") }} +{% endmacro %} + +{% macro test2() %} + {{- varargs|join(", ") }} +{% endmacro %} + +{{ test1("foo", "bar", "foobar") }} +{{ test2("foo", "bar", "foobar") }} +--DATA-- +return array(); +--EXPECT-- +foo: bar, foobar + +foo, bar, foobar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/varargs_argument.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/varargs_argument.test new file mode 100644 index 0000000..800c262 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/varargs_argument.test @@ -0,0 +1,7 @@ +--TEST-- +macro with varargs argument +--TEMPLATE-- +{% macro test(varargs) %} +{% endmacro %} +--EXCEPTION-- +Twig_Error_Syntax: The argument "varargs" in macro "test" cannot be defined because the variable "varargs" is reserved for arbitrary arguments in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/with_filters.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/with_filters.test new file mode 100644 index 0000000..685626f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/with_filters.test @@ -0,0 +1,14 @@ +--TEST-- +macro with a filter +--TEMPLATE-- +{% import _self as test %} + +{% macro test() %} + {% filter escape %}foo
    {% endfilter %} +{% endmacro %} + +{{ test.test() }} +--DATA-- +return array(); +--EXPECT-- +foo<br /> diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/combined_debug_info.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/combined_debug_info.test new file mode 100644 index 0000000..ff977ad --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/combined_debug_info.test @@ -0,0 +1,15 @@ +--TEST-- +Exception with bad line number +--TEMPLATE-- +{% block content %} + {{ foo }} + {{ include("foo") }} +{% endblock %} +index +--TEMPLATE(foo)-- +foo +{{ foo.bar }} +--DATA-- +return array('foo' => 'foo'); +--EXCEPTION-- +Twig_Error_Runtime: Impossible to access an attribute ("bar") on a string variable ("foo") in "foo" at line 3. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/empty_token.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/empty_token.test new file mode 100644 index 0000000..65f6cd2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/empty_token.test @@ -0,0 +1,8 @@ +--TEST-- +Twig outputs 0 nodes correctly +--TEMPLATE-- +{{ foo }}0{{ foo }} +--DATA-- +return array('foo' => 'foo') +--EXPECT-- +foo0foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/issue_1143.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/issue_1143.test new file mode 100644 index 0000000..ff7c8bb --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/issue_1143.test @@ -0,0 +1,23 @@ +--TEST-- +error in twig extension +--TEMPLATE-- +{{ object.region is not null ? object.regionChoices[object.region] }} +--DATA-- +class House +{ + const REGION_S = 1; + const REGION_P = 2; + + public static $regionChoices = array(self::REGION_S => 'house.region.s', self::REGION_P => 'house.region.p'); + + public function getRegionChoices() + { + return self::$regionChoices; + } +} + +$object = new House(); +$object->region = 1; +return array('object' => $object) +--EXPECT-- +house.region.s diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/multi_word_tests.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/multi_word_tests.test new file mode 100644 index 0000000..269a305 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/multi_word_tests.test @@ -0,0 +1,10 @@ +--TEST-- +Twig allows multi-word tests without a custom node class +--TEMPLATE-- +{{ 'foo' is multi word ? 'yes' : 'no' }} +{{ 'foo bar' is multi word ? 'yes' : 'no' }} +--DATA-- +return array() +--EXPECT-- +no +yes diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/simple_xml_element.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/simple_xml_element.test new file mode 100644 index 0000000..60c3c51 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/simple_xml_element.test @@ -0,0 +1,19 @@ +--TEST-- +Twig is able to deal with SimpleXMLElement instances as variables +--CONDITION-- +version_compare(phpversion(), '5.3.0', '>=') +--TEMPLATE-- +Hello '{{ images.image.0.group }}'! +{{ images.image.0.group.attributes.myattr }} +{{ images.children().image.count() }} +{% for image in images %} + - {{ image.group }} +{% endfor %} +--DATA-- +return array('images' => new SimpleXMLElement('foobar')) +--EXPECT-- +Hello 'foo'! +example +2 + - foo + - bar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/strings_like_numbers.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/strings_like_numbers.test new file mode 100644 index 0000000..e18e110 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/regression/strings_like_numbers.test @@ -0,0 +1,8 @@ +--TEST-- +Twig does not confuse strings with integers in getAttribute() +--TEMPLATE-- +{{ hash['2e2'] }} +--DATA-- +return array('hash' => array('2e2' => 'works')) +--EXPECT-- +works diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/basic.test new file mode 100644 index 0000000..2f6a3e1 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/basic.test @@ -0,0 +1,26 @@ +--TEST-- +"autoescape" tag applies escaping on its children +--TEMPLATE-- +{% autoescape %} +{{ var }}
    +{% endautoescape %} +{% autoescape 'html' %} +{{ var }}
    +{% endautoescape %} +{% autoescape false %} +{{ var }}
    +{% endautoescape %} +{% autoescape true %} +{{ var }}
    +{% endautoescape %} +{% autoescape false %} +{{ var }}
    +{% endautoescape %} +--DATA-- +return array('var' => '
    ') +--EXPECT-- +<br />
    +<br />
    +

    +<br />
    +

    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/blocks.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/blocks.test new file mode 100644 index 0000000..05ab83c --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/blocks.test @@ -0,0 +1,12 @@ +--TEST-- +"autoescape" tag applies escaping on embedded blocks +--TEMPLATE-- +{% autoescape 'html' %} + {% block foo %} + {{ var }} + {% endblock %} +{% endautoescape %} +--DATA-- +return array('var' => '
    ') +--EXPECT-- +<br /> diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/double_escaping.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/double_escaping.test new file mode 100644 index 0000000..9c09724 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/double_escaping.test @@ -0,0 +1,10 @@ +--TEST-- +"autoescape" tag does not double-escape +--TEMPLATE-- +{% autoescape 'html' %} +{{ var|escape }} +{% endautoescape %} +--DATA-- +return array('var' => '
    ') +--EXPECT-- +<br /> diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/functions.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/functions.test new file mode 100644 index 0000000..ce7ea78 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/functions.test @@ -0,0 +1,83 @@ +--TEST-- +"autoescape" tag applies escaping after calling functions +--TEMPLATE-- + +autoescape false +{% autoescape false %} + +safe_br +{{ safe_br() }} + +unsafe_br +{{ unsafe_br() }} + +{% endautoescape %} + +autoescape 'html' +{% autoescape 'html' %} + +safe_br +{{ safe_br() }} + +unsafe_br +{{ unsafe_br() }} + +unsafe_br()|raw +{{ (unsafe_br())|raw }} + +safe_br()|escape +{{ (safe_br())|escape }} + +safe_br()|raw +{{ (safe_br())|raw }} + +unsafe_br()|escape +{{ (unsafe_br())|escape }} + +{% endautoescape %} + +autoescape js +{% autoescape 'js' %} + +safe_br +{{ safe_br() }} + +{% endautoescape %} +--DATA-- +return array() +--EXPECT-- + +autoescape false + +safe_br +
    + +unsafe_br +
    + + +autoescape 'html' + +safe_br +
    + +unsafe_br +<br /> + +unsafe_br()|raw +
    + +safe_br()|escape +<br /> + +safe_br()|raw +
    + +unsafe_br()|escape +<br /> + + +autoescape js + +safe_br +\x3Cbr\x20\x2F\x3E diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/literal.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/literal.test new file mode 100644 index 0000000..e389d4d --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/literal.test @@ -0,0 +1,45 @@ +--TEST-- +"autoescape" tag does not apply escaping on literals +--TEMPLATE-- +{% autoescape 'html' %} + +1. Simple literal +{{ "
    " }} + +2. Conditional expression with only literals +{{ true ? "
    " : "
    " }} + +3. Conditional expression with a variable +{{ true ? "
    " : someVar }} + +4. Nested conditionals with only literals +{{ true ? (true ? "
    " : "
    ") : "\n" }} + +5. Nested conditionals with a variable +{{ true ? (true ? "
    " : someVar) : "\n" }} + +6. Nested conditionals with a variable marked safe +{{ true ? (true ? "
    " : someVar|raw) : "\n" }} + +{% endautoescape %} +--DATA-- +return array() +--EXPECT-- + +1. Simple literal +
    + +2. Conditional expression with only literals +
    + +3. Conditional expression with a variable +<br /> + +4. Nested conditionals with only literals +
    + +5. Nested conditionals with a variable +<br /> + +6. Nested conditionals with a variable marked safe +
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/nested.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/nested.test new file mode 100644 index 0000000..798e6fe --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/nested.test @@ -0,0 +1,26 @@ +--TEST-- +"autoescape" tags can be nested at will +--TEMPLATE-- +{{ var }} +{% autoescape 'html' %} + {{ var }} + {% autoescape false %} + {{ var }} + {% autoescape 'html' %} + {{ var }} + {% endautoescape %} + {{ var }} + {% endautoescape %} + {{ var }} +{% endautoescape %} +{{ var }} +--DATA-- +return array('var' => '
    ') +--EXPECT-- +<br /> + <br /> +
    + <br /> +
    + <br /> +<br /> diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/objects.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/objects.test new file mode 100644 index 0000000..e896aa4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/objects.test @@ -0,0 +1,26 @@ +--TEST-- +"autoescape" tag applies escaping to object method calls +--TEMPLATE-- +{% autoescape 'html' %} +{{ user.name }} +{{ user.name|lower }} +{{ user }} +{% endautoescape %} +--DATA-- +class UserForAutoEscapeTest +{ + public function getName() + { + return 'Fabien
    '; + } + + public function __toString() + { + return 'Fabien
    '; + } +} +return array('user' => new UserForAutoEscapeTest()) +--EXPECT-- +Fabien<br /> +fabien<br /> +Fabien<br /> diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/raw.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/raw.test new file mode 100644 index 0000000..9f1cedd --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/raw.test @@ -0,0 +1,10 @@ +--TEST-- +"autoescape" tag does not escape when raw is used as a filter +--TEMPLATE-- +{% autoescape 'html' %} +{{ var|raw }} +{% endautoescape %} +--DATA-- +return array('var' => '
    ') +--EXPECT-- +
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/strategy.legacy.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/strategy.legacy.test new file mode 100644 index 0000000..bbf1356 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/strategy.legacy.test @@ -0,0 +1,11 @@ +--TEST-- +"autoescape" tag accepts an escaping strategy +--TEMPLATE-- +{% autoescape true js %}{{ var }}{% endautoescape %} + +{% autoescape true html %}{{ var }}{% endautoescape %} +--DATA-- +return array('var' => '
    "') +--EXPECT-- +\x3Cbr\x20\x2F\x3E\x22 +<br />" diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test new file mode 100644 index 0000000..e496f60 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test @@ -0,0 +1,11 @@ +--TEST-- +"autoescape" tag accepts an escaping strategy +--TEMPLATE-- +{% autoescape 'js' %}{{ var }}{% endautoescape %} + +{% autoescape 'html' %}{{ var }}{% endautoescape %} +--DATA-- +return array('var' => '
    "') +--EXPECT-- +\x3Cbr\x20\x2F\x3E\x22 +<br />" diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/type.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/type.test new file mode 100644 index 0000000..4f41520 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/type.test @@ -0,0 +1,69 @@ +--TEST-- +escape types +--TEMPLATE-- + +1. autoescape 'html' |escape('js') + +{% autoescape 'html' %} + +{% endautoescape %} + +2. autoescape 'html' |escape('js') + +{% autoescape 'html' %} + +{% endautoescape %} + +3. autoescape 'js' |escape('js') + +{% autoescape 'js' %} + +{% endautoescape %} + +4. no escape + +{% autoescape false %} + +{% endautoescape %} + +5. |escape('js')|escape('html') + +{% autoescape false %} + +{% endautoescape %} + +6. autoescape 'html' |escape('js')|escape('html') + +{% autoescape 'html' %} + +{% endautoescape %} + +--DATA-- +return array('msg' => "<>\n'\"") +--EXPECT-- + +1. autoescape 'html' |escape('js') + + + +2. autoescape 'html' |escape('js') + + + +3. autoescape 'js' |escape('js') + + + +4. no escape + + + +5. |escape('js')|escape('html') + + + +6. autoescape 'html' |escape('js')|escape('html') + + + diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_filters.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_filters.test new file mode 100644 index 0000000..7821a9a --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_filters.test @@ -0,0 +1,131 @@ +--TEST-- +"autoescape" tag applies escaping after calling filters +--TEMPLATE-- +{% autoescape 'html' %} + +(escape_and_nl2br is an escaper filter) + +1. Don't escape escaper filter output +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is not escaped ) +{{ var|escape_and_nl2br }} + +2. Don't escape escaper filter output +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is not escaped, |raw is redundant ) +{{ var|escape_and_nl2br|raw }} + +3. Explicit escape +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is explicitly escaped by |escape ) +{{ var|escape_and_nl2br|escape }} + +4. Escape non-escaper filter output +( var is upper-cased by |upper, + the output is auto-escaped ) +{{ var|upper }} + +5. Escape if last filter is not an escaper +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is upper-cased by |upper, + the output is auto-escaped as |upper is not an escaper ) +{{ var|escape_and_nl2br|upper }} + +6. Don't escape escaper filter output +( var is upper cased by upper, + the output is escaped by |escape_and_nl2br, line-breaks are added, + the output is not escaped as |escape_and_nl2br is an escaper ) +{{ var|upper|escape_and_nl2br }} + +7. Escape if last filter is not an escaper +( the output of |format is "" ~ var ~ "", + the output is auto-escaped ) +{{ "%s"|format(var) }} + +8. Escape if last filter is not an escaper +( the output of |format is "" ~ var ~ "", + |raw is redundant, + the output is auto-escaped ) +{{ "%s"|raw|format(var) }} + +9. Don't escape escaper filter output +( the output of |format is "" ~ var ~ "", + the output is not escaped due to |raw filter at the end ) +{{ "%s"|format(var)|raw }} + +10. Don't escape escaper filter output +( the output of |format is "" ~ var ~ "", + the output is not escaped due to |raw filter at the end, + the |raw filter on var is redundant ) +{{ "%s"|format(var|raw)|raw }} + +{% endautoescape %} +--DATA-- +return array('var' => "\nTwig") +--EXPECT-- + +(escape_and_nl2br is an escaper filter) + +1. Don't escape escaper filter output +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is not escaped ) +<Fabien>
    +Twig + +2. Don't escape escaper filter output +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is not escaped, |raw is redundant ) +<Fabien>
    +Twig + +3. Explicit escape +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is explicitly escaped by |escape ) +&lt;Fabien&gt;<br /> +Twig + +4. Escape non-escaper filter output +( var is upper-cased by |upper, + the output is auto-escaped ) +<FABIEN> +TWIG + +5. Escape if last filter is not an escaper +( var is escaped by |escape_and_nl2br, line-breaks are added, + the output is upper-cased by |upper, + the output is auto-escaped as |upper is not an escaper ) +&LT;FABIEN&GT;<BR /> +TWIG + +6. Don't escape escaper filter output +( var is upper cased by upper, + the output is escaped by |escape_and_nl2br, line-breaks are added, + the output is not escaped as |escape_and_nl2br is an escaper ) +<FABIEN>
    +TWIG + +7. Escape if last filter is not an escaper +( the output of |format is "" ~ var ~ "", + the output is auto-escaped ) +<b><Fabien> +Twig</b> + +8. Escape if last filter is not an escaper +( the output of |format is "" ~ var ~ "", + |raw is redundant, + the output is auto-escaped ) +<b><Fabien> +Twig</b> + +9. Don't escape escaper filter output +( the output of |format is "" ~ var ~ "", + the output is not escaped due to |raw filter at the end ) + +Twig + +10. Don't escape escaper filter output +( the output of |format is "" ~ var ~ "", + the output is not escaped due to |raw filter at the end, + the |raw filter on var is redundant ) + +Twig diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_filters_arguments.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_filters_arguments.test new file mode 100644 index 0000000..f58a1e0 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_filters_arguments.test @@ -0,0 +1,23 @@ +--TEST-- +"autoescape" tag do not applies escaping on filter arguments +--TEMPLATE-- +{% autoescape 'html' %} +{{ var|nl2br("
    ") }} +{{ var|nl2br("
    "|escape) }} +{{ var|nl2br(sep) }} +{{ var|nl2br(sep|raw) }} +{{ var|nl2br(sep|escape) }} +{% endautoescape %} +--DATA-- +return array('var' => "\nTwig", 'sep' => '
    ') +--EXPECT-- +<Fabien>
    +Twig +<Fabien><br /> +Twig +<Fabien>
    +Twig +<Fabien>
    +Twig +<Fabien><br /> +Twig diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_pre_escape_filters.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_pre_escape_filters.test new file mode 100644 index 0000000..134c77e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_pre_escape_filters.test @@ -0,0 +1,68 @@ +--TEST-- +"autoescape" tag applies escaping after calling filters, and before calling pre_escape filters +--TEMPLATE-- +{% autoescape 'html' %} + +(nl2br is pre_escaped for "html" and declared safe for "html") + +1. Pre-escape and don't post-escape +( var|escape|nl2br ) +{{ var|nl2br }} + +2. Don't double-pre-escape +( var|escape|nl2br ) +{{ var|escape|nl2br }} + +3. Don't escape safe values +( var|raw|nl2br ) +{{ var|raw|nl2br }} + +4. Don't escape safe values +( var|escape|nl2br|nl2br ) +{{ var|nl2br|nl2br }} + +5. Re-escape values that are escaped for an other contexts +( var|escape_something|escape|nl2br ) +{{ var|escape_something|nl2br }} + +6. Still escape when using filters not declared safe +( var|escape|nl2br|upper|escape ) +{{ var|nl2br|upper }} + +{% endautoescape %} +--DATA-- +return array('var' => "\nTwig") +--EXPECT-- + +(nl2br is pre_escaped for "html" and declared safe for "html") + +1. Pre-escape and don't post-escape +( var|escape|nl2br ) +<Fabien>
    +Twig + +2. Don't double-pre-escape +( var|escape|nl2br ) +<Fabien>
    +Twig + +3. Don't escape safe values +( var|raw|nl2br ) +
    +Twig + +4. Don't escape safe values +( var|escape|nl2br|nl2br ) +<Fabien>

    +Twig + +5. Re-escape values that are escaped for an other contexts +( var|escape_something|escape|nl2br ) +<FABIEN>
    +TWIG + +6. Still escape when using filters not declared safe +( var|escape|nl2br|upper|escape ) +&LT;FABIEN&GT;<BR /> +TWIG + diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_preserves_safety_filters.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_preserves_safety_filters.test new file mode 100644 index 0000000..32d3943 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/autoescape/with_preserves_safety_filters.test @@ -0,0 +1,50 @@ +--TEST-- +"autoescape" tag handles filters preserving the safety +--TEMPLATE-- +{% autoescape 'html' %} + +(preserves_safety is preserving safety for "html") + +1. Unsafe values are still unsafe +( var|preserves_safety|escape ) +{{ var|preserves_safety }} + +2. Safe values are still safe +( var|escape|preserves_safety ) +{{ var|escape|preserves_safety }} + +3. Re-escape values that are escaped for an other contexts +( var|escape_something|preserves_safety|escape ) +{{ var|escape_something|preserves_safety }} + +4. Still escape when using filters not declared safe +( var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'})|escape ) +{{ var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'}) }} + +{% endautoescape %} +--DATA-- +return array('var' => "\nTwig") +--EXPECT-- + +(preserves_safety is preserving safety for "html") + +1. Unsafe values are still unsafe +( var|preserves_safety|escape ) +<FABIEN> +TWIG + +2. Safe values are still safe +( var|escape|preserves_safety ) +<FABIEN> +TWIG + +3. Re-escape values that are escaped for an other contexts +( var|escape_something|preserves_safety|escape ) +<FABIEN> +TWIG + +4. Still escape when using filters not declared safe +( var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'})|escape ) +&LT;FABPOT&GT; +TWIG + diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/basic.test new file mode 100644 index 0000000..360dcf0 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/basic.test @@ -0,0 +1,11 @@ +--TEST-- +"block" tag +--TEMPLATE-- +{% block title1 %}FOO{% endblock %} +{% block title2 foo|lower %} +--TEMPLATE(foo.twig)-- +{% block content %}{% endblock %} +--DATA-- +return array('foo' => 'bar') +--EXPECT-- +FOObar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/block_unique_name.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/block_unique_name.test new file mode 100644 index 0000000..bc89ec8 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/block_unique_name.test @@ -0,0 +1,11 @@ +--TEST-- +"block" tag +--TEMPLATE-- +{% block content %} + {% block content %} + {% endblock %} +{% endblock %} +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Syntax: The block 'content' has already been defined line 2 in "index.twig" at line 3. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/special_chars.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/special_chars.test new file mode 100644 index 0000000..be17fed --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/special_chars.test @@ -0,0 +1,10 @@ +--TEST-- +"§" special chars in a block name +--TEMPLATE-- +{% block § %} +§ +{% endblock § %} +--DATA-- +return array() +--EXPECT-- +§ diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/basic.test new file mode 100644 index 0000000..f44296e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/basic.test @@ -0,0 +1,35 @@ +--TEST-- +"embed" tag +--TEMPLATE-- +FOO +{% embed "foo.twig" %} + {% block c1 %} + {{ parent() }} + block1extended + {% endblock %} +{% endembed %} + +BAR +--TEMPLATE(foo.twig)-- +A +{% block c1 %} + block1 +{% endblock %} +B +{% block c2 %} + block2 +{% endblock %} +C +--DATA-- +return array() +--EXPECT-- +FOO + +A + block1 + + block1extended + B + block2 +C +BAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/complex_dynamic_parent.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/complex_dynamic_parent.test new file mode 100644 index 0000000..de5ea7e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/complex_dynamic_parent.test @@ -0,0 +1,35 @@ +--TEST-- +"embed" tag +--TEMPLATE-- +FOO +{% embed foo ~ ".twig" %} + {% block c1 %} + {{ parent() }} + block1extended + {% endblock %} +{% endembed %} + +BAR +--TEMPLATE(foo.twig)-- +A +{% block c1 %} + block1 +{% endblock %} +B +{% block c2 %} + block2 +{% endblock %} +C +--DATA-- +return array('foo' => 'foo') +--EXPECT-- +FOO + +A + block1 + + block1extended + B + block2 +C +BAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/dynamic_parent.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/dynamic_parent.test new file mode 100644 index 0000000..2a125e6 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/dynamic_parent.test @@ -0,0 +1,35 @@ +--TEST-- +"embed" tag +--TEMPLATE-- +FOO +{% embed foo %} + {% block c1 %} + {{ parent() }} + block1extended + {% endblock %} +{% endembed %} + +BAR +--TEMPLATE(foo.twig)-- +A +{% block c1 %} + block1 +{% endblock %} +B +{% block c2 %} + block2 +{% endblock %} +C +--DATA-- +return array('foo' => 'foo.twig') +--EXPECT-- +FOO + +A + block1 + + block1extended + B + block2 +C +BAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/error_line.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/error_line.test new file mode 100644 index 0000000..4314737 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/error_line.test @@ -0,0 +1,16 @@ +--TEST-- +"embed" tag +--TEMPLATE(index.twig)-- +FOO +{% embed "foo.twig" %} + {% block c1 %} + {{ nothing }} + {% endblock %} +{% endembed %} +BAR +--TEMPLATE(foo.twig)-- +{% block c1 %}{% endblock %} +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Runtime: Variable "nothing" does not exist in "index.twig" at line 5. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/multiple.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/multiple.test new file mode 100644 index 0000000..da161e6 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/multiple.test @@ -0,0 +1,50 @@ +--TEST-- +"embed" tag +--TEMPLATE-- +FOO +{% embed "foo.twig" %} + {% block c1 %} + {{ parent() }} + block1extended + {% endblock %} +{% endembed %} + +{% embed "foo.twig" %} + {% block c1 %} + {{ parent() }} + block1extended + {% endblock %} +{% endembed %} + +BAR +--TEMPLATE(foo.twig)-- +A +{% block c1 %} + block1 +{% endblock %} +B +{% block c2 %} + block2 +{% endblock %} +C +--DATA-- +return array() +--EXPECT-- +FOO + +A + block1 + + block1extended + B + block2 +C + +A + block1 + + block1extended + B + block2 +C +BAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/nested.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/nested.test new file mode 100644 index 0000000..81563dc --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/nested.test @@ -0,0 +1,42 @@ +--TEST-- +"embed" tag +--TEMPLATE-- +{% embed "foo.twig" %} + {% block c1 %} + {{ parent() }} + {% embed "foo.twig" %} + {% block c1 %} + {{ parent() }} + block1extended + {% endblock %} + {% endembed %} + + {% endblock %} +{% endembed %} +--TEMPLATE(foo.twig)-- +A +{% block c1 %} + block1 +{% endblock %} +B +{% block c2 %} + block2 +{% endblock %} +C +--DATA-- +return array() +--EXPECT-- +A + block1 + + +A + block1 + + block1extended + B + block2 +C + B + block2 +C diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/with_extends.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/with_extends.test new file mode 100644 index 0000000..2c1dd58 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/embed/with_extends.test @@ -0,0 +1,60 @@ +--TEST-- +"embed" tag +--TEMPLATE-- +{% extends "base.twig" %} + +{% block c1 %} + {{ parent() }} + blockc1baseextended +{% endblock %} + +{% block c2 %} + {{ parent() }} + + {% embed "foo.twig" %} + {% block c1 %} + {{ parent() }} + block1extended + {% endblock %} + {% endembed %} + {{ parent() }} +{% endblock %} +--TEMPLATE(base.twig)-- +A +{% block c1 %} + blockc1base +{% endblock %} +{% block c2 %} + blockc2base +{% endblock %} +B +--TEMPLATE(foo.twig)-- +A +{% block c1 %} + block1 +{% endblock %} +B +{% block c2 %} + block2 +{% endblock %} +C +--DATA-- +return array() +--EXPECT-- +A + blockc1base + + blockc1baseextended + blockc2base + + + +A + block1 + + block1extended + B + block2 +C blockc2base + +B \ No newline at end of file diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/basic.test new file mode 100644 index 0000000..82094f2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/basic.test @@ -0,0 +1,10 @@ +--TEST-- +"filter" tag applies a filter on its children +--TEMPLATE-- +{% filter upper %} +Some text with a {{ var }} +{% endfilter %} +--DATA-- +return array('var' => 'var') +--EXPECT-- +SOME TEXT WITH A VAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/json_encode.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/json_encode.test new file mode 100644 index 0000000..3e7148b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/json_encode.test @@ -0,0 +1,8 @@ +--TEST-- +"filter" tag applies a filter on its children +--TEMPLATE-- +{% filter json_encode|raw %}test{% endfilter %} +--DATA-- +return array() +--EXPECT-- +"test" diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/multiple.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/multiple.test new file mode 100644 index 0000000..75512ef --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/multiple.test @@ -0,0 +1,10 @@ +--TEST-- +"filter" tags accept multiple chained filters +--TEMPLATE-- +{% filter lower|title %} + {{ var }} +{% endfilter %} +--DATA-- +return array('var' => 'VAR') +--EXPECT-- + Var diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/nested.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/nested.test new file mode 100644 index 0000000..7e4e4eb --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/nested.test @@ -0,0 +1,16 @@ +--TEST-- +"filter" tags can be nested at will +--TEMPLATE-- +{% filter lower|title %} + {{ var }} + {% filter upper %} + {{ var }} + {% endfilter %} + {{ var }} +{% endfilter %} +--DATA-- +return array('var' => 'var') +--EXPECT-- + Var + Var + Var diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.test new file mode 100644 index 0000000..22745ea --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.test @@ -0,0 +1,13 @@ +--TEST-- +"filter" tag applies the filter on "for" tags +--TEMPLATE-- +{% filter upper %} +{% for item in items %} +{{ item }} +{% endfor %} +{% endfilter %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- +A +B diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.test new file mode 100644 index 0000000..afd95b2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.test @@ -0,0 +1,29 @@ +--TEST-- +"filter" tag applies the filter on "if" tags +--TEMPLATE-- +{% filter upper %} +{% if items %} +{{ items|join(', ') }} +{% endif %} + +{% if items.3 is defined %} +FOO +{% else %} +{{ items.1 }} +{% endif %} + +{% if items.3 is defined %} +FOO +{% elseif items.1 %} +{{ items.0 }} +{% endif %} + +{% endfilter %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- +A, B + +B + +A diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/condition.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/condition.test new file mode 100644 index 0000000..380531f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/condition.test @@ -0,0 +1,14 @@ +--TEST-- +"for" tag takes a condition +--TEMPLATE-- +{% for i in 1..5 if i is odd -%} + {{ loop.index }}.{{ i }}{{ foo.bar }} +{% endfor %} +--DATA-- +return array('foo' => array('bar' => 'X')) +--CONFIG-- +return array('strict_variables' => false) +--EXPECT-- +1.1X +2.3X +3.5X diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/context.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/context.test new file mode 100644 index 0000000..ddc6930 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/context.test @@ -0,0 +1,18 @@ +--TEST-- +"for" tag keeps the context safe +--TEMPLATE-- +{% for item in items %} + {% for item in items %} + * {{ item }} + {% endfor %} + * {{ item }} +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- + * a + * b + * a + * a + * b + * b diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/else.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/else.test new file mode 100644 index 0000000..20ccc88 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/else.test @@ -0,0 +1,23 @@ +--TEST-- +"for" tag can use an "else" clause +--TEMPLATE-- +{% for item in items %} + * {{ item }} +{% else %} + no item +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- + * a + * b +--DATA-- +return array('items' => array()) +--EXPECT-- + no item +--DATA-- +return array() +--CONFIG-- +return array('strict_variables' => false) +--EXPECT-- + no item diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/inner_variables.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/inner_variables.test new file mode 100644 index 0000000..49fb9ca --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/inner_variables.test @@ -0,0 +1,17 @@ +--TEST-- +"for" tag does not reset inner variables +--TEMPLATE-- +{% for i in 1..2 %} + {% for j in 0..2 %} + {{k}}{% set k = k+1 %} {{ loop.parent.loop.index }} + {% endfor %} +{% endfor %} +--DATA-- +return array('k' => 0) +--EXPECT-- + 0 1 + 1 1 + 2 1 + 3 2 + 4 2 + 5 2 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/keys.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/keys.test new file mode 100644 index 0000000..4e22cb4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/keys.test @@ -0,0 +1,11 @@ +--TEST-- +"for" tag can iterate over keys +--TEMPLATE-- +{% for key in items|keys %} + * {{ key }} +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- + * 0 + * 1 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/keys_and_values.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/keys_and_values.test new file mode 100644 index 0000000..4c21168 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/keys_and_values.test @@ -0,0 +1,11 @@ +--TEST-- +"for" tag can iterate over keys and values +--TEMPLATE-- +{% for key, item in items %} + * {{ key }}/{{ item }} +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- + * 0/a + * 1/b diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_context.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_context.test new file mode 100644 index 0000000..93bc76a --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_context.test @@ -0,0 +1,19 @@ +--TEST-- +"for" tag adds a loop variable to the context +--TEMPLATE-- +{% for item in items %} + * {{ loop.index }}/{{ loop.index0 }} + * {{ loop.revindex }}/{{ loop.revindex0 }} + * {{ loop.first }}/{{ loop.last }}/{{ loop.length }} + +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- + * 1/0 + * 2/1 + * 1//2 + + * 2/1 + * 1/0 + * /1/2 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_context_local.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_context_local.test new file mode 100644 index 0000000..58af2c3 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_context_local.test @@ -0,0 +1,10 @@ +--TEST-- +"for" tag adds a loop variable to the context locally +--TEMPLATE-- +{% for item in items %} +{% endfor %} +{% if loop is not defined %}WORKS{% endif %} +--DATA-- +return array('items' => array()) +--EXPECT-- +WORKS diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_not_defined.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_not_defined.test new file mode 100644 index 0000000..6a2af63 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_not_defined.test @@ -0,0 +1,10 @@ +--TEST-- +"for" tag +--TEMPLATE-- +{% for i, item in items if i > 0 %} + {{ loop.last }} +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXCEPTION-- +Twig_Error_Syntax: The "loop.last" variable is not defined when looping with a condition in "index.twig" at line 3. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_not_defined_cond.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_not_defined_cond.test new file mode 100644 index 0000000..1e819ca --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/loop_not_defined_cond.test @@ -0,0 +1,9 @@ +--TEST-- +"for" tag +--TEMPLATE-- +{% for i, item in items if loop.last > 0 %} +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXCEPTION-- +Twig_Error_Syntax: The "loop" variable cannot be used in a looping condition in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/nested_else.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/nested_else.test new file mode 100644 index 0000000..f8b9f6b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/nested_else.test @@ -0,0 +1,17 @@ +--TEST-- +"for" tag can use an "else" clause +--TEMPLATE-- +{% for item in items %} + {% for item in items1 %} + * {{ item }} + {% else %} + no {{ item }} + {% endfor %} +{% else %} + no item1 +{% endfor %} +--DATA-- +return array('items' => array('a', 'b'), 'items1' => array()) +--EXPECT-- +no a + no b diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/objects.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/objects.test new file mode 100644 index 0000000..5034437 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/objects.test @@ -0,0 +1,43 @@ +--TEST-- +"for" tag iterates over iterable objects +--TEMPLATE-- +{% for item in items %} + * {{ item }} + * {{ loop.index }}/{{ loop.index0 }} + * {{ loop.first }} + +{% endfor %} + +{% for key, value in items %} + * {{ key }}/{{ value }} +{% endfor %} + +{% for key in items|keys %} + * {{ key }} +{% endfor %} +--DATA-- +class ItemsIterator implements Iterator +{ + protected $values = array('foo' => 'bar', 'bar' => 'foo'); + public function current() { return current($this->values); } + public function key() { return key($this->values); } + public function next() { return next($this->values); } + public function rewind() { return reset($this->values); } + public function valid() { return false !== current($this->values); } +} +return array('items' => new ItemsIterator()) +--EXPECT-- + * bar + * 1/0 + * 1 + + * foo + * 2/1 + * + + + * foo/bar + * bar/foo + + * foo + * bar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/objects_countable.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/objects_countable.test new file mode 100644 index 0000000..4a1ff61 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/objects_countable.test @@ -0,0 +1,47 @@ +--TEST-- +"for" tag iterates over iterable and countable objects +--TEMPLATE-- +{% for item in items %} + * {{ item }} + * {{ loop.index }}/{{ loop.index0 }} + * {{ loop.revindex }}/{{ loop.revindex0 }} + * {{ loop.first }}/{{ loop.last }}/{{ loop.length }} + +{% endfor %} + +{% for key, value in items %} + * {{ key }}/{{ value }} +{% endfor %} + +{% for key in items|keys %} + * {{ key }} +{% endfor %} +--DATA-- +class ItemsIteratorCountable implements Iterator, Countable +{ + protected $values = array('foo' => 'bar', 'bar' => 'foo'); + public function current() { return current($this->values); } + public function key() { return key($this->values); } + public function next() { return next($this->values); } + public function rewind() { return reset($this->values); } + public function valid() { return false !== current($this->values); } + public function count() { return count($this->values); } +} +return array('items' => new ItemsIteratorCountable()) +--EXPECT-- + * bar + * 1/0 + * 2/1 + * 1//2 + + * foo + * 2/1 + * 1/0 + * /1/2 + + + * foo/bar + * bar/foo + + * foo + * bar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/recursive.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/recursive.test new file mode 100644 index 0000000..17b2e22 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/recursive.test @@ -0,0 +1,18 @@ +--TEST-- +"for" tags can be nested +--TEMPLATE-- +{% for key, item in items %} +* {{ key }} ({{ loop.length }}): +{% for value in item %} + * {{ value }} ({{ loop.length }}) +{% endfor %} +{% endfor %} +--DATA-- +return array('items' => array('a' => array('a1', 'a2', 'a3'), 'b' => array('b1'))) +--EXPECT-- +* a (2): + * a1 (3) + * a2 (3) + * a3 (3) +* b (2): + * b1 (1) diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/values.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/values.test new file mode 100644 index 0000000..82f2ae8 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/values.test @@ -0,0 +1,11 @@ +--TEST-- +"for" tag iterates over item values +--TEMPLATE-- +{% for item in items %} + * {{ item }} +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXPECT-- + * a + * b diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/from.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/from.test new file mode 100644 index 0000000..5f5da0e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/from.test @@ -0,0 +1,14 @@ +--TEST-- +global variables +--TEMPLATE-- +{% include "included.twig" %} +{% from "included.twig" import foobar %} +{{ foobar() }} +--TEMPLATE(included.twig)-- +{% macro foobar() %} +called foobar +{% endmacro %} +--DATA-- +return array(); +--EXPECT-- +called foobar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/if/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/if/basic.test new file mode 100644 index 0000000..c1c3d27 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/if/basic.test @@ -0,0 +1,22 @@ +--TEST-- +"if" creates a condition +--TEMPLATE-- +{% if a is defined %} + {{ a }} +{% elseif b is defined %} + {{ b }} +{% else %} + NOTHING +{% endif %} +--DATA-- +return array('a' => 'a') +--EXPECT-- + a +--DATA-- +return array('b' => 'b') +--EXPECT-- + b +--DATA-- +return array() +--EXPECT-- + NOTHING diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/if/expression.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/if/expression.test new file mode 100644 index 0000000..edfb73d --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/if/expression.test @@ -0,0 +1,22 @@ +--TEST-- +"if" takes an expression as a test +--TEMPLATE-- +{% if a < 2 %} + A1 +{% elseif a > 10 %} + A2 +{% else %} + A3 +{% endif %} +--DATA-- +return array('a' => 1) +--EXPECT-- + A1 +--DATA-- +return array('a' => 12) +--EXPECT-- + A2 +--DATA-- +return array('a' => 7) +--EXPECT-- + A3 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/basic.test new file mode 100644 index 0000000..8fe1a6c --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/basic.test @@ -0,0 +1,16 @@ +--TEST-- +"include" tag +--TEMPLATE-- +FOO +{% include "foo.twig" %} + +BAR +--TEMPLATE(foo.twig)-- +FOOBAR +--DATA-- +return array() +--EXPECT-- +FOO + +FOOBAR +BAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/expression.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/expression.test new file mode 100644 index 0000000..eaeeb11 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/expression.test @@ -0,0 +1,16 @@ +--TEST-- +"include" tag allows expressions for the template to include +--TEMPLATE-- +FOO +{% include foo %} + +BAR +--TEMPLATE(foo.twig)-- +FOOBAR +--DATA-- +return array('foo' => 'foo.twig') +--EXPECT-- +FOO + +FOOBAR +BAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/ignore_missing.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/ignore_missing.test new file mode 100644 index 0000000..24aed06 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/ignore_missing.test @@ -0,0 +1,10 @@ +--TEST-- +"include" tag +--TEMPLATE-- +{% include ["foo.twig", "bar.twig"] ignore missing %} +{% include "foo.twig" ignore missing %} +{% include "foo.twig" ignore missing with {} %} +{% include "foo.twig" ignore missing with {} only %} +--DATA-- +return array() +--EXPECT-- diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/missing.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/missing.test new file mode 100644 index 0000000..f25e871 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/missing.test @@ -0,0 +1,8 @@ +--TEST-- +"include" tag +--TEMPLATE-- +{% include "foo.twig" %} +--DATA-- +return array(); +--EXCEPTION-- +Twig_Error_Loader: Template "foo.twig" is not defined in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/missing_nested.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/missing_nested.test new file mode 100644 index 0000000..86c1864 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/missing_nested.test @@ -0,0 +1,16 @@ +--TEST-- +"include" tag +--TEMPLATE-- +{% extends "base.twig" %} + +{% block content %} + {{ parent() }} +{% endblock %} +--TEMPLATE(base.twig)-- +{% block content %} + {% include "foo.twig" %} +{% endblock %} +--DATA-- +return array(); +--EXCEPTION-- +Twig_Error_Loader: Template "foo.twig" is not defined in "base.twig" at line 3. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/only.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/only.test new file mode 100644 index 0000000..77760a0 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/only.test @@ -0,0 +1,16 @@ +--TEST-- +"include" tag accept variables and only +--TEMPLATE-- +{% include "foo.twig" %} +{% include "foo.twig" only %} +{% include "foo.twig" with {'foo1': 'bar'} %} +{% include "foo.twig" with {'foo1': 'bar'} only %} +--TEMPLATE(foo.twig)-- +{% for k, v in _context %}{{ k }},{% endfor %} +--DATA-- +return array('foo' => 'bar') +--EXPECT-- +foo,global,_parent, +global,_parent, +foo,global,foo1,_parent, +foo1,global,_parent, diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/template_instance.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/template_instance.test new file mode 100644 index 0000000..6ba064a --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/template_instance.test @@ -0,0 +1,10 @@ +--TEST-- +"include" tag accepts Twig_Template instance +--TEMPLATE-- +{% include foo %} FOO +--TEMPLATE(foo.twig)-- +BAR +--DATA-- +return array('foo' => $twig->loadTemplate('foo.twig')) +--EXPECT-- +BAR FOO diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/templates_as_array.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/templates_as_array.test new file mode 100644 index 0000000..ab670ee --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/templates_as_array.test @@ -0,0 +1,12 @@ +--TEST-- +"include" tag +--TEMPLATE-- +{% include ["foo.twig", "bar.twig"] %} +{% include ["bar.twig", "foo.twig"] %} +--TEMPLATE(foo.twig)-- +foo +--DATA-- +return array() +--EXPECT-- +foo +foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/with_variables.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/with_variables.test new file mode 100644 index 0000000..41384ac --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/include/with_variables.test @@ -0,0 +1,12 @@ +--TEST-- +"include" tag accept variables +--TEMPLATE-- +{% include "foo.twig" with {'foo': 'bar'} %} +{% include "foo.twig" with vars %} +--TEMPLATE(foo.twig)-- +{{ foo }} +--DATA-- +return array('vars' => array('foo' => 'bar')) +--EXPECT-- +bar +bar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/basic.test new file mode 100644 index 0000000..0778a4b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/basic.test @@ -0,0 +1,14 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "foo.twig" %} + +{% block content %} +FOO +{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %}{% endblock %} +--DATA-- +return array() +--EXPECT-- +FOO diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/block_expr.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/block_expr.test new file mode 100644 index 0000000..9a81499 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/block_expr.test @@ -0,0 +1,32 @@ +--TEST-- +block_expr +--TEMPLATE-- +{% extends "base.twig" %} + +{% block element -%} + Element: + {{- parent() -}} +{% endblock %} +--TEMPLATE(base.twig)-- +{% spaceless %} +{% block element -%} +
    + {%- if item.children is defined %} + {%- for item in item.children %} + {{- block('element') -}} + {% endfor %} + {%- endif -%} +
    +{%- endblock %} +{% endspaceless %} +--DATA-- +return array( + 'item' => array( + 'children' => array( + null, + null, + ) + ) +) +--EXPECT-- +Element:
    Element:
    Element:
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/block_expr2.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/block_expr2.test new file mode 100644 index 0000000..3e868c0 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/block_expr2.test @@ -0,0 +1,34 @@ +--TEST-- +block_expr2 +--TEMPLATE-- +{% extends "base2.twig" %} + +{% block element -%} + Element: + {{- parent() -}} +{% endblock %} +--TEMPLATE(base2.twig)-- +{% extends "base.twig" %} +--TEMPLATE(base.twig)-- +{% spaceless %} +{% block element -%} +
    + {%- if item.children is defined %} + {%- for item in item.children %} + {{- block('element') -}} + {% endfor %} + {%- endif -%} +
    +{%- endblock %} +{% endspaceless %} +--DATA-- +return array( + 'item' => array( + 'children' => array( + null, + null, + ) + ) +) +--EXPECT-- +Element:
    Element:
    Element:
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/conditional.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/conditional.test new file mode 100644 index 0000000..8576e77 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/conditional.test @@ -0,0 +1,14 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends standalone ? foo : 'bar.twig' %} + +{% block content %}{{ parent() }}FOO{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %}FOO{% endblock %} +--TEMPLATE(bar.twig)-- +{% block content %}BAR{% endblock %} +--DATA-- +return array('foo' => 'foo.twig', 'standalone' => true) +--EXPECT-- +FOOFOO diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/dynamic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/dynamic.test new file mode 100644 index 0000000..ee06ddc --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/dynamic.test @@ -0,0 +1,14 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends foo %} + +{% block content %} +FOO +{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %}{% endblock %} +--DATA-- +return array('foo' => 'foo.twig') +--EXPECT-- +FOO diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/empty.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/empty.test new file mode 100644 index 0000000..784f357 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/empty.test @@ -0,0 +1,10 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "foo.twig" %} +--TEMPLATE(foo.twig)-- +{% block content %}FOO{% endblock %} +--DATA-- +return array() +--EXPECT-- +FOO diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array.test new file mode 100644 index 0000000..a1cb1ce --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array.test @@ -0,0 +1,12 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends ["foo.twig", "bar.twig"] %} +--TEMPLATE(bar.twig)-- +{% block content %} +foo +{% endblock %} +--DATA-- +return array() +--EXPECT-- +foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array_with_empty_name.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array_with_empty_name.test new file mode 100644 index 0000000..acc74f6 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array_with_empty_name.test @@ -0,0 +1,12 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends ["", "bar.twig"] %} +--TEMPLATE(bar.twig)-- +{% block content %} +foo +{% endblock %} +--DATA-- +return array() +--EXPECT-- +foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array_with_null_name.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array_with_null_name.test new file mode 100644 index 0000000..cfa648d --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array_with_null_name.test @@ -0,0 +1,12 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends [null, "bar.twig"] %} +--TEMPLATE(bar.twig)-- +{% block content %} +foo +{% endblock %} +--DATA-- +return array() +--EXPECT-- +foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/multiple.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/multiple.test new file mode 100644 index 0000000..dfc2b6c --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/multiple.test @@ -0,0 +1,12 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "layout.twig" %}{% block content %}{{ parent() }}index {% endblock %} +--TEMPLATE(layout.twig)-- +{% extends "base.twig" %}{% block content %}{{ parent() }}layout {% endblock %} +--TEMPLATE(base.twig)-- +{% block content %}base {% endblock %} +--DATA-- +return array() +--EXPECT-- +base layout index diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/multiple_dynamic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/multiple_dynamic.test new file mode 100644 index 0000000..1d3e639 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/multiple_dynamic.test @@ -0,0 +1,22 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% set foo = 1 %} +{{ include('parent.twig') }} +{{ include('parent.twig') }} +{% set foo = 2 %} +{{ include('parent.twig') }} +--TEMPLATE(parent.twig)-- +{% extends foo~'_parent.twig' %}{% block content %}{{ parent() }} parent{% endblock %} +--TEMPLATE(1_parent.twig)-- +{% block content %}1{% endblock %} +--TEMPLATE(2_parent.twig)-- +{% block content %}2{% endblock %} +--DATA-- +return array() +--EXPECT-- +1 parent + +1 parent + +2 parent diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_blocks.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_blocks.test new file mode 100644 index 0000000..faca925 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_blocks.test @@ -0,0 +1,22 @@ +--TEST-- +"block" tag +--TEMPLATE-- +{% extends "foo.twig" %} + +{% block content %} + {% block subcontent %} + {% block subsubcontent %} + SUBSUBCONTENT + {% endblock %} + {% endblock %} +{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %} + {% block subcontent %} + SUBCONTENT + {% endblock %} +{% endblock %} +--DATA-- +return array() +--EXPECT-- +SUBSUBCONTENT diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_blocks_parent_only.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_blocks_parent_only.test new file mode 100644 index 0000000..0ad11d0 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_blocks_parent_only.test @@ -0,0 +1,15 @@ +--TEST-- +"block" tag +--TEMPLATE-- +{% block content %} + CONTENT + {%- block subcontent -%} + SUBCONTENT + {%- endblock -%} + ENDCONTENT +{% endblock %} +--TEMPLATE(foo.twig)-- +--DATA-- +return array() +--EXPECT-- +CONTENTSUBCONTENTENDCONTENT diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_inheritance.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_inheritance.test new file mode 100644 index 0000000..71e3cdf --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_inheritance.test @@ -0,0 +1,16 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "layout.twig" %} +{% block inside %}INSIDE{% endblock inside %} +--TEMPLATE(layout.twig)-- +{% extends "base.twig" %} +{% block body %} + {% block inside '' %} +{% endblock body %} +--TEMPLATE(base.twig)-- +{% block body '' %} +--DATA-- +return array() +--EXPECT-- +INSIDE diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent.test new file mode 100644 index 0000000..4f975db --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent.test @@ -0,0 +1,12 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "foo.twig" %} + +{% block content %}{{ parent() }}FOO{{ parent() }}{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %}BAR{% endblock %} +--DATA-- +return array() +--EXPECT-- +BARFOOBAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_change.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_change.test new file mode 100644 index 0000000..a8bc90c --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_change.test @@ -0,0 +1,16 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends foo ? 'foo.twig' : 'bar.twig' %} +--TEMPLATE(foo.twig)-- +FOO +--TEMPLATE(bar.twig)-- +BAR +--DATA-- +return array('foo' => true) +--EXPECT-- +FOO +--DATA-- +return array('foo' => false) +--EXPECT-- +BAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_in_a_block.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_in_a_block.test new file mode 100644 index 0000000..cca6dbc --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_in_a_block.test @@ -0,0 +1,8 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% block content %} + {% extends "foo.twig" %} +{% endblock %} +--EXCEPTION-- +Twig_Error_Syntax: Cannot extend from a block in "index.twig" at line 3. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_isolation.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_isolation.test new file mode 100644 index 0000000..6281671 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_isolation.test @@ -0,0 +1,20 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "base.twig" %} +{% block content %}{% include "included.twig" %}{% endblock %} + +{% block footer %}Footer{% endblock %} +--TEMPLATE(included.twig)-- +{% extends "base.twig" %} +{% block content %}Included Content{% endblock %} +--TEMPLATE(base.twig)-- +{% block content %}Default Content{% endblock %} + +{% block footer %}Default Footer{% endblock %} +--DATA-- +return array() +--EXPECT-- +Included Content +Default Footer +Footer diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_nested.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_nested.test new file mode 100644 index 0000000..71e7c20 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_nested.test @@ -0,0 +1,28 @@ +--TEST-- +"extends" tag +--TEMPLATE-- +{% extends "foo.twig" %} + +{% block content %} + {% block inside %} + INSIDE OVERRIDDEN + {% endblock %} + + BEFORE + {{ parent() }} + AFTER +{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %} + BAR +{% endblock %} +--DATA-- +return array() +--EXPECT-- + +INSIDE OVERRIDDEN + + BEFORE + BAR + + AFTER diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_without_extends.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_without_extends.test new file mode 100644 index 0000000..e29b1ac --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_without_extends.test @@ -0,0 +1,8 @@ +--TEST-- +"parent" tag +--TEMPLATE-- +{% block content %} + {{ parent() }} +{% endblock %} +--EXCEPTION-- +Twig_Error_Syntax: Calling "parent" on a template that does not extend nor "use" another template is forbidden in "index.twig" at line 3. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_without_extends_but_traits.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_without_extends_but_traits.test new file mode 100644 index 0000000..63c7305 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_without_extends_but_traits.test @@ -0,0 +1,14 @@ +--TEST-- +"parent" tag +--TEMPLATE-- +{% use 'foo.twig' %} + +{% block content %} + {{ parent() }} +{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %}BAR{% endblock %} +--DATA-- +return array() +--EXPECT-- +BAR diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/template_instance.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/template_instance.test new file mode 100644 index 0000000..d1876a5 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/template_instance.test @@ -0,0 +1,14 @@ +--TEST-- +"extends" tag accepts Twig_Template instance +--TEMPLATE-- +{% extends foo %} + +{% block content %} +{{ parent() }}FOO +{% endblock %} +--TEMPLATE(foo.twig)-- +{% block content %}BAR{% endblock %} +--DATA-- +return array('foo' => $twig->loadTemplate('foo.twig')) +--EXPECT-- +BARFOO diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/use.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/use.test new file mode 100644 index 0000000..8f9ece7 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/use.test @@ -0,0 +1,44 @@ +--TEST-- +"parent" function +--TEMPLATE-- +{% extends "parent.twig" %} + +{% use "use1.twig" %} +{% use "use2.twig" %} + +{% block content_parent %} + {{ parent() }} +{% endblock %} + +{% block content_use1 %} + {{ parent() }} +{% endblock %} + +{% block content_use2 %} + {{ parent() }} +{% endblock %} + +{% block content %} + {{ block('content_use1_only') }} + {{ block('content_use2_only') }} +{% endblock %} +--TEMPLATE(parent.twig)-- +{% block content_parent 'content_parent' %} +{% block content_use1 'content_parent' %} +{% block content_use2 'content_parent' %} +{% block content '' %} +--TEMPLATE(use1.twig)-- +{% block content_use1 'content_use1' %} +{% block content_use2 'content_use1' %} +{% block content_use1_only 'content_use1_only' %} +--TEMPLATE(use2.twig)-- +{% block content_use2 'content_use2' %} +{% block content_use2_only 'content_use2_only' %} +--DATA-- +return array() +--EXPECT-- + content_parent + content_use1 + content_use2 + content_use1_only + content_use2_only diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/basic.test new file mode 100644 index 0000000..eef0c10 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/basic.test @@ -0,0 +1,17 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% import _self as macros %} + +{{ macros.input('username') }} +{{ macros.input('password', null, 'password', 1) }} + +{% macro input(name, value, type, size) %} + +{% endmacro %} +--DATA-- +return array() +--EXPECT-- + + + diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/endmacro_name.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/endmacro_name.test new file mode 100644 index 0000000..ae6203b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/endmacro_name.test @@ -0,0 +1,16 @@ +--TEST-- +"macro" tag supports name for endmacro +--TEMPLATE-- +{% import _self as macros %} + +{{ macros.foo() }} +{{ macros.bar() }} + +{% macro foo() %}foo{% endmacro %} +{% macro bar() %}bar{% endmacro bar %} +--DATA-- +return array() +--EXPECT-- +foo +bar + diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/external.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/external.test new file mode 100644 index 0000000..5cd3dae --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/external.test @@ -0,0 +1,17 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% import 'forms.twig' as forms %} + +{{ forms.input('username') }} +{{ forms.input('password', null, 'password', 1) }} +--TEMPLATE(forms.twig)-- +{% macro input(name, value, type, size) %} + +{% endmacro %} +--DATA-- +return array() +--EXPECT-- + + + diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from.test new file mode 100644 index 0000000..205f591 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from.test @@ -0,0 +1,18 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% from 'forms.twig' import foo %} +{% from 'forms.twig' import foo as foobar, bar %} + +{{ foo('foo') }} +{{ foobar('foo') }} +{{ bar('foo') }} +--TEMPLATE(forms.twig)-- +{% macro foo(name) %}foo{{ name }}{% endmacro %} +{% macro bar(name) %}bar{{ name }}{% endmacro %} +--DATA-- +return array() +--EXPECT-- +foofoo +foofoo +barfoo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_with_reserved_name.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_with_reserved_name.test new file mode 100644 index 0000000..2de9765 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_with_reserved_name.test @@ -0,0 +1,9 @@ +--TEST-- +"from" tag with reserved name +--TEMPLATE-- +{% from 'forms.twig' import templateName %} +--TEMPLATE(forms.twig)-- +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Syntax: "templateName" cannot be an imported macro as it is a reserved keyword in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/global.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/global.test new file mode 100644 index 0000000..6b37176 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/global.test @@ -0,0 +1,14 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% from 'forms.twig' import foo %} + +{{ foo('foo') }} +{{ foo() }} +--TEMPLATE(forms.twig)-- +{% macro foo(name) %}{{ name|default('foo') }}{{ global }}{% endmacro %} +--DATA-- +return array() +--EXPECT-- +fooglobal +fooglobal diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_with_reserved_nam.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_with_reserved_nam.test new file mode 100644 index 0000000..7bd93c6 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_with_reserved_nam.test @@ -0,0 +1,11 @@ +--TEST-- +"from" tag with reserved name +--TEMPLATE-- +{% import 'forms.twig' as macros %} + +{{ macros.parent() }} +--TEMPLATE(forms.twig)-- +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Syntax: "parent" cannot be called as macro as it is a reserved keyword in "index.twig" at line 4. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/reserved_name.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/reserved_name.test new file mode 100644 index 0000000..f7c102f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/reserved_name.test @@ -0,0 +1,10 @@ +--TEST-- +"macro" tag with reserved name +--TEMPLATE-- +{% macro parent(arg1, arg2) %} + parent +{% endmacro %} +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Syntax: "parent" cannot be used as a macro name as it is a reserved keyword in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/self_import.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/self_import.test new file mode 100644 index 0000000..17756cb --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/self_import.test @@ -0,0 +1,17 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% import _self as forms %} + +{{ forms.input('username') }} +{{ forms.input('password', null, 'password', 1) }} + +{% macro input(name, value, type, size) %} + +{% endmacro %} +--DATA-- +return array() +--EXPECT-- + + + diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/special_chars.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/special_chars.test new file mode 100644 index 0000000..3721770 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/special_chars.test @@ -0,0 +1,14 @@ +--TEST-- +"§" as a macro name +--TEMPLATE-- +{% import _self as macros %} + +{{ macros.§('foo') }} + +{% macro §(foo) %} + §{{ foo }}§ +{% endmacro %} +--DATA-- +return array() +--EXPECT-- +§foo§ diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/super_globals.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/super_globals.test new file mode 100644 index 0000000..5679462 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/super_globals.test @@ -0,0 +1,14 @@ +--TEST-- +Super globals as macro arguments +--TEMPLATE-- +{% import _self as macros %} + +{{ macros.foo('foo') }} + +{% macro foo(GET) %} + {{ GET }} +{% endmacro %} +--DATA-- +return array() +--EXPECT-- +foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/raw/basic.legacy.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/raw/basic.legacy.test new file mode 100644 index 0000000..0445e85 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/raw/basic.legacy.test @@ -0,0 +1,10 @@ +--TEST-- +"raw" tag +--TEMPLATE-- +{% raw %} +{{ foo }} +{% endraw %} +--DATA-- +return array() +--EXPECT-- +{{ foo }} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/raw/mixed_usage_with_raw.legacy.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/raw/mixed_usage_with_raw.legacy.test new file mode 100644 index 0000000..99deefc --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/raw/mixed_usage_with_raw.legacy.test @@ -0,0 +1,10 @@ +--TEST-- +"raw" tag +--TEMPLATE-- +{% raw %} +{{ foo }} +{% endverbatim %} +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Syntax: Unexpected end of file: Unclosed "raw" block in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/raw/whitespace_control.legacy.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/raw/whitespace_control.legacy.test new file mode 100644 index 0000000..352bb18 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/raw/whitespace_control.legacy.test @@ -0,0 +1,56 @@ +--TEST-- +"raw" tag +--TEMPLATE-- +1*** + +{%- raw %} + {{ 'bla' }} +{% endraw %} + +1*** +2*** + +{%- raw -%} + {{ 'bla' }} +{% endraw %} + +2*** +3*** + +{%- raw -%} + {{ 'bla' }} +{% endraw -%} + +3*** +4*** + +{%- raw -%} + {{ 'bla' }} +{%- endraw %} + +4*** +5*** + +{%- raw -%} + {{ 'bla' }} +{%- endraw -%} + +5*** +--DATA-- +return array() +--EXPECT-- +1*** + {{ 'bla' }} + + +1*** +2***{{ 'bla' }} + + +2*** +3***{{ 'bla' }} +3*** +4***{{ 'bla' }} + +4*** +5***{{ 'bla' }}5*** diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/sandbox/not_valid1.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/sandbox/not_valid1.test new file mode 100644 index 0000000..dfddc15 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/sandbox/not_valid1.test @@ -0,0 +1,11 @@ +--TEST-- +sandbox tag +--TEMPLATE-- +{%- sandbox %} + {%- include "foo.twig" %} + a +{%- endsandbox %} +--TEMPLATE(foo.twig)-- +foo +--EXCEPTION-- +Twig_Error_Syntax: Only "include" tags are allowed within a "sandbox" section in "index.twig" at line 4. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/sandbox/not_valid2.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/sandbox/not_valid2.test new file mode 100644 index 0000000..a33a13e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/sandbox/not_valid2.test @@ -0,0 +1,14 @@ +--TEST-- +sandbox tag +--TEMPLATE-- +{%- sandbox %} + {%- include "foo.twig" %} + + {% if 1 %} + {%- include "foo.twig" %} + {% endif %} +{%- endsandbox %} +--TEMPLATE(foo.twig)-- +foo +--EXCEPTION-- +Twig_Error_Syntax: Only "include" tags are allowed within a "sandbox" section in "index.twig" at line 5. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/sandbox/simple.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/sandbox/simple.test new file mode 100644 index 0000000..de20f3d --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/sandbox/simple.test @@ -0,0 +1,22 @@ +--TEST-- +sandbox tag +--TEMPLATE-- +{%- sandbox %} + {%- include "foo.twig" %} +{%- endsandbox %} + +{%- sandbox %} + {%- include "foo.twig" %} + {%- include "foo.twig" %} +{%- endsandbox %} + +{%- sandbox %}{% include "foo.twig" %}{% endsandbox %} +--TEMPLATE(foo.twig)-- +foo +--DATA-- +return array() +--EXPECT-- +foo +foo +foo +foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/basic.test new file mode 100644 index 0000000..a5a9f83 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/basic.test @@ -0,0 +1,20 @@ +--TEST-- +"set" tag +--TEMPLATE-- +{% set foo = 'foo' %} +{% set bar = 'foo
    ' %} + +{{ foo }} +{{ bar }} + +{% set foo, bar = 'foo', 'bar' %} + +{{ foo }}{{ bar }} +--DATA-- +return array() +--EXPECT-- +foo +foo<br /> + + +foobar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/capture-empty.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/capture-empty.test new file mode 100644 index 0000000..ec657f0 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/capture-empty.test @@ -0,0 +1,9 @@ +--TEST-- +"set" tag block empty capture +--TEMPLATE-- +{% set foo %}{% endset %} + +{% if foo %}FAIL{% endif %} +--DATA-- +return array() +--EXPECT-- diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/capture.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/capture.test new file mode 100644 index 0000000..f156a1a --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/capture.test @@ -0,0 +1,10 @@ +--TEST-- +"set" tag block capture +--TEMPLATE-- +{% set foo %}f
    o
    o{% endset %} + +{{ foo }} +--DATA-- +return array() +--EXPECT-- +f
    o
    o diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/expression.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/expression.test new file mode 100644 index 0000000..8ff434a --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/set/expression.test @@ -0,0 +1,12 @@ +--TEST-- +"set" tag +--TEMPLATE-- +{% set foo, bar = 'foo' ~ 'bar', 'bar' ~ 'foo' %} + +{{ foo }} +{{ bar }} +--DATA-- +return array() +--EXPECT-- +foobar +barfoo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/spaceless/simple.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/spaceless/simple.test new file mode 100644 index 0000000..dd06dec --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/spaceless/simple.test @@ -0,0 +1,12 @@ +--TEST-- +"spaceless" tag removes whites between HTML tags +--TEMPLATE-- +{% spaceless %} + +
    foo
    + +{% endspaceless %} +--DATA-- +return array() +--EXPECT-- +
    foo
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/special_chars.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/special_chars.test new file mode 100644 index 0000000..789b4ba --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/special_chars.test @@ -0,0 +1,8 @@ +--TEST-- +"§" custom tag +--TEMPLATE-- +{% § %} +--DATA-- +return array() +--EXPECT-- +§ diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/trim_block.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/trim_block.test new file mode 100644 index 0000000..1d2273f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/trim_block.test @@ -0,0 +1,74 @@ +--TEST-- +Whitespace trimming on tags. +--TEMPLATE-- +{{ 5 * '{#-'|length }} +{{ '{{-'|length * 5 + '{%-'|length }} + +Trim on control tag: +{% for i in range(1, 9) -%} + {{ i }} +{%- endfor %} + + +Trim on output tag: +{% for i in range(1, 9) %} + {{- i -}} +{% endfor %} + + +Trim comments: + +{#- Invisible -#} + +After the comment. + +Trim leading space: +{% if leading %} + + {{- leading }} +{% endif %} + +{%- if leading %} + {{- leading }} + +{%- endif %} + + +Trim trailing space: +{% if trailing -%} + {{ trailing -}} + +{% endif -%} + +Combined: + +{%- if both -%} +
      +
    • {{- both -}}
    • +
    + +{%- endif -%} + +end +--DATA-- +return array('leading' => 'leading space', 'trailing' => 'trailing space', 'both' => 'both') +--EXPECT-- +15 +18 + +Trim on control tag: +123456789 + +Trim on output tag: +123456789 + +Trim comments:After the comment. + +Trim leading space: +leading space +leading space + +Trim trailing space: +trailing spaceCombined:
      +
    • both
    • +
    end diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/aliases.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/aliases.test new file mode 100644 index 0000000..f887006 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/aliases.test @@ -0,0 +1,12 @@ +--TEST-- +"use" tag +--TEMPLATE-- +{% use "blocks.twig" with content as foo %} + +{{ block('foo') }} +--TEMPLATE(blocks.twig)-- +{% block content 'foo' %} +--DATA-- +return array() +--EXPECT-- +foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/basic.test new file mode 100644 index 0000000..7364d76 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/basic.test @@ -0,0 +1,12 @@ +--TEST-- +"use" tag +--TEMPLATE-- +{% use "blocks.twig" %} + +{{ block('content') }} +--TEMPLATE(blocks.twig)-- +{% block content 'foo' %} +--DATA-- +return array() +--EXPECT-- +foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/deep.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/deep.test new file mode 100644 index 0000000..b551a1e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/deep.test @@ -0,0 +1,22 @@ +--TEST-- +"use" tag +--TEMPLATE-- +{% use "foo.twig" %} + +{{ block('content') }} +{{ block('foo') }} +{{ block('bar') }} +--TEMPLATE(foo.twig)-- +{% use "bar.twig" %} + +{% block content 'foo' %} +{% block foo 'foo' %} +--TEMPLATE(bar.twig)-- +{% block content 'bar' %} +{% block bar 'bar' %} +--DATA-- +return array() +--EXPECT-- +foo +foo +bar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/deep_empty.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/deep_empty.test new file mode 100644 index 0000000..05cca68 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/deep_empty.test @@ -0,0 +1,10 @@ +--TEST-- +"use" tag +--TEMPLATE-- +{% use "foo.twig" %} +--TEMPLATE(foo.twig)-- +{% use "bar.twig" %} +--TEMPLATE(bar.twig)-- +--DATA-- +return array() +--EXPECT-- diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/inheritance.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/inheritance.test new file mode 100644 index 0000000..0d0d470 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/inheritance.test @@ -0,0 +1,25 @@ +--TEST-- +"use" tag +--TEMPLATE-- +{% use "parent.twig" %} + +{{ block('container') }} +--TEMPLATE(parent.twig)-- +{% use "ancestor.twig" %} + +{% block sub_container %} +
    overridden sub_container
    +{% endblock %} +--TEMPLATE(ancestor.twig)-- +{% block container %} +
    {{ block('sub_container') }}
    +{% endblock %} + +{% block sub_container %} +
    sub_container
    +{% endblock %} +--DATA-- +return array() +--EXPECT-- +
    overridden sub_container
    +
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/inheritance2.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/inheritance2.test new file mode 100644 index 0000000..df95599 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/inheritance2.test @@ -0,0 +1,24 @@ +--TEST-- +"use" tag +--TEMPLATE-- +{% use "ancestor.twig" %} +{% use "parent.twig" %} + +{{ block('container') }} +--TEMPLATE(parent.twig)-- +{% block sub_container %} +
    overridden sub_container
    +{% endblock %} +--TEMPLATE(ancestor.twig)-- +{% block container %} +
    {{ block('sub_container') }}
    +{% endblock %} + +{% block sub_container %} +
    sub_container
    +{% endblock %} +--DATA-- +return array() +--EXPECT-- +
    overridden sub_container
    +
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/multiple.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/multiple.test new file mode 100644 index 0000000..198be0c --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/multiple.test @@ -0,0 +1,21 @@ +--TEST-- +"use" tag +--TEMPLATE-- +{% use "foo.twig" %} +{% use "bar.twig" %} + +{{ block('content') }} +{{ block('foo') }} +{{ block('bar') }} +--TEMPLATE(foo.twig)-- +{% block content 'foo' %} +{% block foo 'foo' %} +--TEMPLATE(bar.twig)-- +{% block content 'bar' %} +{% block bar 'bar' %} +--DATA-- +return array() +--EXPECT-- +bar +foo +bar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/multiple_aliases.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/multiple_aliases.test new file mode 100644 index 0000000..8de871a --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/multiple_aliases.test @@ -0,0 +1,23 @@ +--TEST-- +"use" tag +--TEMPLATE-- +{% use "foo.twig" with content as foo_content %} +{% use "bar.twig" %} + +{{ block('content') }} +{{ block('foo') }} +{{ block('bar') }} +{{ block('foo_content') }} +--TEMPLATE(foo.twig)-- +{% block content 'foo' %} +{% block foo 'foo' %} +--TEMPLATE(bar.twig)-- +{% block content 'bar' %} +{% block bar 'bar' %} +--DATA-- +return array() +--EXPECT-- +bar +foo +bar +foo diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block.test new file mode 100644 index 0000000..59db23d --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block.test @@ -0,0 +1,24 @@ +--TEST-- +"use" tag +--TEMPLATE-- +{% use 'file2.html.twig' with foobar as base_base_foobar %} +{% block foobar %} + {{- block('base_base_foobar') -}} + Content of block (second override) +{% endblock foobar %} +--TEMPLATE(file2.html.twig)-- +{% use 'file1.html.twig' with foobar as base_foobar %} +{% block foobar %} + {{- block('base_foobar') -}} + Content of block (first override) +{% endblock foobar %} +--TEMPLATE(file1.html.twig)-- +{% block foobar -%} + Content of block +{% endblock foobar %} +--DATA-- +return array() +--EXPECT-- +Content of block +Content of block (first override) +Content of block (second override) diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block2.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block2.test new file mode 100644 index 0000000..d3f302d --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block2.test @@ -0,0 +1,24 @@ +--TEST-- +"use" tag +--TEMPLATE-- +{% use 'file2.html.twig'%} +{% block foobar %} + {{- parent() -}} + Content of block (second override) +{% endblock foobar %} +--TEMPLATE(file2.html.twig)-- +{% use 'file1.html.twig' %} +{% block foobar %} + {{- parent() -}} + Content of block (first override) +{% endblock foobar %} +--TEMPLATE(file1.html.twig)-- +{% block foobar -%} + Content of block +{% endblock foobar %} +--DATA-- +return array() +--EXPECT-- +Content of block +Content of block (first override) +Content of block (second override) diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block3.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block3.test new file mode 100644 index 0000000..95b55a4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block3.test @@ -0,0 +1,38 @@ +--TEST-- +"use" tag +--TEMPLATE-- +{% use 'file2.html.twig' %} +{% use 'file1.html.twig' with foo %} +{% block foo %} + {{- parent() -}} + Content of foo (second override) +{% endblock foo %} +{% block bar %} + {{- parent() -}} + Content of bar (second override) +{% endblock bar %} +--TEMPLATE(file2.html.twig)-- +{% use 'file1.html.twig' %} +{% block foo %} + {{- parent() -}} + Content of foo (first override) +{% endblock foo %} +{% block bar %} + {{- parent() -}} + Content of bar (first override) +{% endblock bar %} +--TEMPLATE(file1.html.twig)-- +{% block foo -%} + Content of foo +{% endblock foo %} +{% block bar -%} + Content of bar +{% endblock bar %} +--DATA-- +return array() +--EXPECT-- +Content of foo +Content of foo (first override) +Content of foo (second override) +Content of bar +Content of bar (second override) diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/basic.test new file mode 100644 index 0000000..a95be55 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/basic.test @@ -0,0 +1,10 @@ +--TEST-- +"verbatim" tag +--TEMPLATE-- +{% verbatim %} +{{ foo }} +{% endverbatim %} +--DATA-- +return array() +--EXPECT-- +{{ foo }} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/mixed_usage_with_raw.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/mixed_usage_with_raw.test new file mode 100644 index 0000000..28626a8 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/mixed_usage_with_raw.test @@ -0,0 +1,10 @@ +--TEST-- +"verbatim" tag +--TEMPLATE-- +{% verbatim %} +{{ foo }} +{% endraw %} +--DATA-- +return array() +--EXCEPTION-- +Twig_Error_Syntax: Unexpected end of file: Unclosed "verbatim" block in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/whitespace_control.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/whitespace_control.test new file mode 100644 index 0000000..eb61044 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/whitespace_control.test @@ -0,0 +1,56 @@ +--TEST-- +"verbatim" tag +--TEMPLATE-- +1*** + +{%- verbatim %} + {{ 'bla' }} +{% endverbatim %} + +1*** +2*** + +{%- verbatim -%} + {{ 'bla' }} +{% endverbatim %} + +2*** +3*** + +{%- verbatim -%} + {{ 'bla' }} +{% endverbatim -%} + +3*** +4*** + +{%- verbatim -%} + {{ 'bla' }} +{%- endverbatim %} + +4*** +5*** + +{%- verbatim -%} + {{ 'bla' }} +{%- endverbatim -%} + +5*** +--DATA-- +return array() +--EXPECT-- +1*** + {{ 'bla' }} + + +1*** +2***{{ 'bla' }} + + +2*** +3***{{ 'bla' }} +3*** +4***{{ 'bla' }} + +4*** +5***{{ 'bla' }}5*** diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/basic.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/basic.test new file mode 100644 index 0000000..264ca5e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/basic.test @@ -0,0 +1,13 @@ +--TEST-- +"with" tag +--TEMPLATE-- +{% with %} + {% set bar = 'BAZ' %} + {{ foo }}{{ bar }} +{% endwith %} +{{ foo }}{{ bar }} +--DATA-- +return array('foo' => 'foo', 'bar' => 'bar') +--EXPECT-- +fooBAZ +foobar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/expression.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/expression.test new file mode 100644 index 0000000..32ed091 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/expression.test @@ -0,0 +1,10 @@ +--TEST-- +"with" tag with expression +--TEMPLATE-- +{% with {foo: 'foo', bar: 'BAZ'} %} + {{ foo }}{{ bar }} +{% endwith %} +--DATA-- +return array('foo' => 'baz') +--EXPECT-- +fooBAZ diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/nested.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/nested.test new file mode 100644 index 0000000..98e3aef --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/nested.test @@ -0,0 +1,15 @@ +--TEST-- +nested "with" tags +--TEMPLATE-- +{% set foo, bar = 'foo', 'bar' %} +{% with {bar: 'BAZ'} %} + {% with {foo: 'FOO'} %} + {{ foo }}{{ bar }} + {% endwith %} +{% endwith %} +{{ foo }}{{ bar }} +--DATA-- +return array() +--EXPECT-- +FOOBAZ + foobar diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/with_no_hash.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/with_no_hash.test new file mode 100644 index 0000000..93689f4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/with_no_hash.test @@ -0,0 +1,10 @@ +--TEST-- +"with" tag with an expression that is not a hash +--TEMPLATE-- +{% with vars %} + {{ foo }}{{ bar }} +{% endwith %} +--DATA-- +return array('vars' => 'no-hash') +--EXCEPTION-- +Twig_Error_Runtime: Variables passed to the "with" tag must be a hash in "index.twig" at line 2. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/with_only.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/with_only.test new file mode 100644 index 0000000..6247617 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/with/with_only.test @@ -0,0 +1,10 @@ +--TEST-- +"with" tag with expression and only +--TEMPLATE-- +{% with {foo: 'foo', bar: 'BAZ'} only %} + {{ foo }}{{ bar }}{{ baz }} +{% endwith %} +--DATA-- +return array('foo' => 'baz', 'baz' => 'baz') +--EXCEPTION-- +Twig_Error_Runtime: Variable "baz" does not exist in "index.twig" at line 3. diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/array.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/array.test new file mode 100644 index 0000000..1429d37 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/array.test @@ -0,0 +1,24 @@ +--TEST-- +array index test +--TEMPLATE-- +{% for key, value in days %} +{{ key }} +{% endfor %} +--DATA-- +return array('days' => array( + 1 => array('money' => 9), + 2 => array('money' => 21), + 3 => array('money' => 38), + 4 => array('money' => 6), + 18 => array('money' => 6), + 19 => array('money' => 3), + 31 => array('money' => 11), +)); +--EXPECT-- +1 +2 +3 +4 +18 +19 +31 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/constant.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/constant.test new file mode 100644 index 0000000..60218ac --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/constant.test @@ -0,0 +1,14 @@ +--TEST-- +"const" test +--TEMPLATE-- +{{ 8 is constant('E_NOTICE') ? 'ok' : 'no' }} +{{ 'bar' is constant('TwigTestFoo::BAR_NAME') ? 'ok' : 'no' }} +{{ value is constant('TwigTestFoo::BAR_NAME') ? 'ok' : 'no' }} +{{ 2 is constant('ARRAY_AS_PROPS', object) ? 'ok' : 'no' }} +--DATA-- +return array('value' => 'bar', 'object' => new ArrayObject(array('hi'))); +--EXPECT-- +ok +ok +ok +ok \ No newline at end of file diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined.test new file mode 100644 index 0000000..d4e204e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined.test @@ -0,0 +1,129 @@ +--TEST-- +"defined" test +--TEMPLATE-- +{{ definedVar is defined ? 'ok' : 'ko' }} +{{ definedVar is not defined ? 'ko' : 'ok' }} +{{ undefinedVar is defined ? 'ko' : 'ok' }} +{{ undefinedVar is not defined ? 'ok' : 'ko' }} +{{ zeroVar is defined ? 'ok' : 'ko' }} +{{ nullVar is defined ? 'ok' : 'ko' }} +{{ nested.definedVar is defined ? 'ok' : 'ko' }} +{{ nested['definedVar'] is defined ? 'ok' : 'ko' }} +{{ nested.definedVar is not defined ? 'ko' : 'ok' }} +{{ nested.undefinedVar is defined ? 'ko' : 'ok' }} +{{ nested['undefinedVar'] is defined ? 'ko' : 'ok' }} +{{ nested.undefinedVar is not defined ? 'ok' : 'ko' }} +{{ nested.zeroVar is defined ? 'ok' : 'ko' }} +{{ nested.nullVar is defined ? 'ok' : 'ko' }} +{{ nested.definedArray.0 is defined ? 'ok' : 'ko' }} +{{ nested['definedArray'][0] is defined ? 'ok' : 'ko' }} +{{ object.foo is defined ? 'ok' : 'ko' }} +{{ object.undefinedMethod is defined ? 'ko' : 'ok' }} +{{ object.getFoo() is defined ? 'ok' : 'ko' }} +{{ object.getFoo('a') is defined ? 'ok' : 'ko' }} +{{ object.undefinedMethod() is defined ? 'ko' : 'ok' }} +{{ object.undefinedMethod('a') is defined ? 'ko' : 'ok' }} +{{ object.self.foo is defined ? 'ok' : 'ko' }} +{{ object.self.undefinedMethod is defined ? 'ko' : 'ok' }} +{{ object.undefinedMethod.self is defined ? 'ko' : 'ok' }} +{{ 0 is defined ? 'ok' : 'ko' }} +{{ "foo" is defined ? 'ok' : 'ko' }} +{{ true is defined ? 'ok' : 'ko' }} +{{ false is defined ? 'ok' : 'ko' }} +{{ null is defined ? 'ok' : 'ko' }} +{{ [1, 2] is defined ? 'ok' : 'ko' }} +{{ { foo: "bar" } is defined ? 'ok' : 'ko' }} +--DATA-- +return array( + 'definedVar' => 'defined', + 'zeroVar' => 0, + 'nullVar' => null, + 'nested' => array( + 'definedVar' => 'defined', + 'zeroVar' => 0, + 'nullVar' => null, + 'definedArray' => array(0), + ), + 'object' => new TwigTestFoo(), +); +--EXPECT-- +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +--DATA-- +return array( + 'definedVar' => 'defined', + 'zeroVar' => 0, + 'nullVar' => null, + 'nested' => array( + 'definedVar' => 'defined', + 'zeroVar' => 0, + 'nullVar' => null, + 'definedArray' => array(0), + ), + 'object' => new TwigTestFoo(), +); +--CONFIG-- +return array('strict_variables' => false) +--EXPECT-- +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok +ok diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_attribute.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_attribute.test new file mode 100644 index 0000000..4a5b8dc --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_attribute.test @@ -0,0 +1,35 @@ +--TEST-- +"defined" support for attribute +--TEMPLATE-- +{{ attribute(nested, "definedVar") is defined ? 'ok' : 'ko' }} +{{ attribute(nested, "undefinedVar") is not defined ? 'ok' : 'ko' }} +{{ attribute(nested, definedVarName) is defined ? 'ok' : 'ko' }} +{{ attribute(nested, undefinedVarName) is not defined ? 'ok' : 'ko' }} +--DATA-- +return array( + 'nested' => array( + 'definedVar' => 'defined', + ), + 'definedVarName' => 'definedVar', + 'undefinedVarName' => 'undefinedVar', +); +--EXPECT-- +ok +ok +ok +ok +--DATA-- +return array( + 'nested' => array( + 'definedVar' => 'defined', + ), + 'definedVarName' => 'definedVar', + 'undefinedVarName' => 'undefinedVar', +); +--CONFIG-- +return array('strict_variables' => false) +--EXPECT-- +ok +ok +ok +ok diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_blocks.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_blocks.test new file mode 100644 index 0000000..64d7d04 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_blocks.test @@ -0,0 +1,38 @@ +--TEST-- +"defined" support for blocks +--TEMPLATE-- +{% extends 'parent' %} +{% block icon %}icon{% endblock %} +{% block body %} + {{ parent() }} + {{ block('foo') is defined ? 'ok' : 'ko' }} + {{ block('footer') is defined ? 'ok' : 'ko' }} + {{ block('icon') is defined ? 'ok' : 'ko' }} + {{ block('block1') is defined ? 'ok' : 'ko' }} + {%- embed 'embed' %} + {% block content %}content{% endblock %} + {% endembed %} +{% endblock %} +{% use 'blocks' %} +--TEMPLATE(parent)-- +{% block body %} + {{ block('icon') is defined ? 'ok' : 'ko' -}} +{% endblock %} +{% block footer %}{% endblock %} +--TEMPLATE(embed)-- +{{ block('icon') is defined ? 'ok' : 'ko' }} +{{ block('content') is defined ? 'ok' : 'ko' }} +{{ block('block1') is defined ? 'ok' : 'ko' }} +--TEMPLATE(blocks)-- +{% block block1 %}{%endblock %} +--DATA-- +return array() +--EXPECT-- +ok + ko + ok + ok + ok +ko +ok +ko diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_blocks_with_template.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_blocks_with_template.test new file mode 100644 index 0000000..2c65165 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_blocks_with_template.test @@ -0,0 +1,17 @@ +--TEST-- +"defined" support for blocks with a template argument +--TEMPLATE-- +{{ block('foo', 'included.twig') is defined ? 'ok' : 'ko' }} +{{ block('foo', included_loaded) is defined ? 'ok' : 'ko' }} +{{ block('foo', included_loaded_internal) is defined ? 'ok' : 'ko' }} +--TEMPLATE(included.twig)-- +{% block foo %}FOO{% endblock %} +--DATA-- +return array( + 'included_loaded' => $twig->load('included.twig'), + 'included_loaded_internal' => $twig->loadTemplate('included.twig'), +) +--EXPECT-- +ok +ok +ok diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_constants.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_constants.test new file mode 100644 index 0000000..2fa9929 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_constants.test @@ -0,0 +1,14 @@ +--TEST-- +"defined" support for constants +--TEMPLATE-- +{{ constant('DATE_W3C') is defined ? 'ok' : 'ko' }} +{{ constant('ARRAY_AS_PROPS', object) is defined ? 'ok' : 'ko' }} +{{ constant('FOOBAR') is not defined ? 'ok' : 'ko' }} +{{ constant('FOOBAR', object) is not defined ? 'ok' : 'ko' }} +--DATA-- +return array('expect' => DATE_W3C, 'object' => new ArrayObject(array('hi'))); +--EXPECT-- +ok +ok +ok +ok diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/empty.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/empty.test new file mode 100644 index 0000000..a776d03 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/empty.test @@ -0,0 +1,45 @@ +--TEST-- +"empty" test +--TEMPLATE-- +{{ foo is empty ? 'ok' : 'ko' }} +{{ bar is empty ? 'ok' : 'ko' }} +{{ foobar is empty ? 'ok' : 'ko' }} +{{ array is empty ? 'ok' : 'ko' }} +{{ zero is empty ? 'ok' : 'ko' }} +{{ string is empty ? 'ok' : 'ko' }} +{{ countable_empty is empty ? 'ok' : 'ko' }} +{{ countable_not_empty is empty ? 'ok' : 'ko' }} +{{ markup_empty is empty ? 'ok' : 'ko' }} +{{ markup_not_empty is empty ? 'ok' : 'ko' }} +--DATA-- + +class CountableStub implements Countable +{ + private $items; + + public function __construct(array $items) + { + $this->items = $items; + } + + public function count() + { + return count($this->items); + } +} +return array( + 'foo' => '', 'bar' => null, 'foobar' => false, 'array' => array(), 'zero' => 0, 'string' => '0', + 'countable_empty' => new CountableStub(array()), 'countable_not_empty' => new CountableStub(array(1, 2)), + 'markup_empty' => new Twig_Markup('', 'UTF-8'), 'markup_not_empty' => new Twig_Markup('test', 'UTF-8'), +); +--EXPECT-- +ok +ok +ok +ok +ko +ko +ok +ko +ok +ko diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/even.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/even.test new file mode 100644 index 0000000..695b4c2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/even.test @@ -0,0 +1,14 @@ +--TEST-- +"even" test +--TEMPLATE-- +{{ 1 is even ? 'ko' : 'ok' }} +{{ 2 is even ? 'ok' : 'ko' }} +{{ 1 is not even ? 'ok' : 'ko' }} +{{ 2 is not even ? 'ko' : 'ok' }} +--DATA-- +return array() +--EXPECT-- +ok +ok +ok +ok diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/in.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/in.test new file mode 100644 index 0000000..545f51f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/in.test @@ -0,0 +1,128 @@ +--TEST-- +Twig supports the in operator +--TEMPLATE-- +{% if bar in foo %} +TRUE +{% endif %} +{% if not (bar in foo) %} +{% else %} +TRUE +{% endif %} +{% if bar not in foo %} +{% else %} +TRUE +{% endif %} +{% if 'a' in bar %} +TRUE +{% endif %} +{% if 'c' not in bar %} +TRUE +{% endif %} +{% if '' in bar %} +TRUE +{% endif %} +{% if '' in '' %} +TRUE +{% endif %} +{% if '0' not in '' %} +TRUE +{% endif %} +{% if 'a' not in '0' %} +TRUE +{% endif %} +{% if '0' in '0' %} +TRUE +{% endif %} + +{{ false in [0, 1] ? 'TRUE' : 'FALSE' }} +{{ true in [0, 1] ? 'TRUE' : 'FALSE' }} +{{ '0' in [0, 1] ? 'TRUE' : 'FALSE' }} +{{ '' in [0, 1] ? 'TRUE' : 'FALSE' }} +{{ 0 in ['', 1] ? 'TRUE' : 'FALSE' }} + +{{ '' in 'foo' ? 'TRUE' : 'FALSE' }} +{{ 0 in 'foo' ? 'TRUE' : 'FALSE' }} +{{ false in 'foo' ? 'TRUE' : 'FALSE' }} +{{ false in '100' ? 'TRUE' : 'FALSE' }} +{{ true in '100' ? 'TRUE' : 'FALSE' }} + +{{ [] in [true, false] ? 'TRUE' : 'FALSE' }} +{{ [] in [true, ''] ? 'TRUE' : 'FALSE' }} +{{ [] in [true, []] ? 'TRUE' : 'FALSE' }} + +{{ resource ? 'TRUE' : 'FALSE' }} +{{ resource in 'foo'~resource ? 'TRUE' : 'FALSE' }} +{{ object in 'stdClass' ? 'TRUE' : 'FALSE' }} +{{ [] in 'Array' ? 'TRUE' : 'FALSE' }} +{{ dir_object in 'foo'~dir_object ? 'TRUE' : 'FALSE' }} + +{{ ''~resource in resource ? 'TRUE' : 'FALSE' }} +{{ 'stdClass' in object ? 'TRUE' : 'FALSE' }} +{{ 'Array' in [] ? 'TRUE' : 'FALSE' }} +{{ ''~dir_object in dir_object ? 'TRUE' : 'FALSE' }} + +{{ resource in [''~resource] ? 'TRUE' : 'FALSE' }} +{{ resource in [resource + 1 - 1] ? 'TRUE' : 'FALSE' }} +{{ dir_object in [''~dir_object] ? 'TRUE' : 'FALSE' }} + +{{ 5 in 125 ? 'TRUE' : 'FALSE' }} +{{ 5 in '125' ? 'TRUE' : 'FALSE' }} +{{ '5' in 125 ? 'TRUE' : 'FALSE' }} +{{ '5' in '125' ? 'TRUE' : 'FALSE' }} + +{{ 5.5 in 125.5 ? 'TRUE' : 'FALSE' }} +{{ 5.5 in '125.5' ? 'TRUE' : 'FALSE' }} +{{ '5.5' in 125.5 ? 'TRUE' : 'FALSE' }} +--DATA-- +return array('bar' => 'bar', 'foo' => array('bar' => 'bar'), 'dir_object' => new SplFileInfo(dirname(__FILE__)), 'object' => new stdClass(), 'resource' => opendir(dirname(__FILE__))) +--EXPECT-- +TRUE +TRUE +TRUE +TRUE +TRUE +TRUE +TRUE +TRUE +TRUE +TRUE + +TRUE +TRUE +TRUE +TRUE +TRUE + +TRUE +FALSE +FALSE +FALSE +FALSE + +TRUE +FALSE +TRUE + +TRUE +FALSE +FALSE +FALSE +FALSE + +FALSE +FALSE +FALSE +FALSE + +FALSE +FALSE +FALSE + +FALSE +TRUE +FALSE +TRUE + +FALSE +TRUE +FALSE diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/in_with_objects.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/in_with_objects.test new file mode 100644 index 0000000..8e08061 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/in_with_objects.test @@ -0,0 +1,19 @@ +--TEST-- +Twig supports the in operator when using objects +--TEMPLATE-- +{% if object in object_list %} +TRUE +{% endif %} +--DATA-- +$foo = new TwigTestFoo(); +$foo1 = new TwigTestFoo(); + +$foo->position = $foo1; +$foo1->position = $foo; + +return array( + 'object' => $foo, + 'object_list' => array($foo1, $foo), +); +--EXPECT-- +TRUE diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/iterable.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/iterable.test new file mode 100644 index 0000000..ec52550 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/iterable.test @@ -0,0 +1,19 @@ +--TEST-- +"iterable" test +--TEMPLATE-- +{{ foo is iterable ? 'ok' : 'ko' }} +{{ traversable is iterable ? 'ok' : 'ko' }} +{{ obj is iterable ? 'ok' : 'ko' }} +{{ val is iterable ? 'ok' : 'ko' }} +--DATA-- +return array( + 'foo' => array(), + 'traversable' => new ArrayIterator(array()), + 'obj' => new stdClass(), + 'val' => 'test', +); +--EXPECT-- +ok +ok +ko +ko \ No newline at end of file diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/null_coalesce.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/null_coalesce.test new file mode 100644 index 0000000..3d148c8 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/null_coalesce.test @@ -0,0 +1,30 @@ +--TEST-- +Twig supports the ?? operator +--TEMPLATE-- +{{ 'OK' ?? 'KO' }} +{{ null ?? 'OK' }} +{{ bar ?? 'KO' }} +{{ baz ?? 'OK' }} +{{ foo.bar ?? 'KO' }} +{{ foo.missing ?? 'OK' }} +{{ foo.bar.baz.missing ?? 'OK' }} +{{ foo['bar'] ?? 'KO' }} +{{ foo['missing'] ?? 'OK' }} +{{ nope ?? nada ?? 'OK' }} +{{ 1 + nope ?? nada ?? 2 }} +{{ 1 + nope ?? 3 + nada ?? 2 }} +--DATA-- +return array('bar' => 'OK', 'foo' => array('bar' => 'OK')) +--EXPECT-- +OK +OK +OK +OK +OK +OK +OK +OK +OK +OK +3 +6 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/odd.test b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/odd.test new file mode 100644 index 0000000..1b8311e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/odd.test @@ -0,0 +1,10 @@ +--TEST-- +"odd" test +--TEMPLATE-- +{{ 1 is odd ? 'ok' : 'ko' }} +{{ 2 is odd ? 'ko' : 'ok' }} +--DATA-- +return array() +--EXPECT-- +ok +ok \ No newline at end of file diff --git a/src/vendor/twig/twig/test/Twig/Tests/IntegrationTest.php b/src/vendor/twig/twig/test/Twig/Tests/IntegrationTest.php new file mode 100644 index 0000000..0647891 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/IntegrationTest.php @@ -0,0 +1,254 @@ +position = 0; + } + + public function current() + { + return $this->array[$this->position]; + } + + public function key() + { + return 'a'; + } + + public function next() + { + ++$this->position; + } + + public function valid() + { + return isset($this->array[$this->position]); + } +} + +class TwigTestTokenParser_§ extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_Print(new Twig_Node_Expression_Constant('§', -1), -1); + } + + public function getTag() + { + return '§'; + } +} + +class TwigTestExtension extends Twig_Extension +{ + public function getTokenParsers() + { + return array( + new TwigTestTokenParser_§(), + ); + } + + public function getFilters() + { + return array( + new Twig_SimpleFilter('§', array($this, '§Filter')), + new Twig_SimpleFilter('escape_and_nl2br', array($this, 'escape_and_nl2br'), array('needs_environment' => true, 'is_safe' => array('html'))), + new Twig_SimpleFilter('nl2br', array($this, 'nl2br'), array('pre_escape' => 'html', 'is_safe' => array('html'))), + new Twig_SimpleFilter('escape_something', array($this, 'escape_something'), array('is_safe' => array('something'))), + new Twig_SimpleFilter('preserves_safety', array($this, 'preserves_safety'), array('preserves_safety' => array('html'))), + new Twig_SimpleFilter('static_call_string', 'TwigTestExtension::staticCall'), + new Twig_SimpleFilter('static_call_array', array('TwigTestExtension', 'staticCall')), + new Twig_SimpleFilter('magic_call', array($this, 'magicCall')), + new Twig_SimpleFilter('magic_call_string', 'TwigTestExtension::magicStaticCall'), + new Twig_SimpleFilter('magic_call_array', array('TwigTestExtension', 'magicStaticCall')), + new Twig_SimpleFilter('*_path', array($this, 'dynamic_path')), + new Twig_SimpleFilter('*_foo_*_bar', array($this, 'dynamic_foo')), + ); + } + + public function getFunctions() + { + return array( + new Twig_SimpleFunction('§', array($this, '§Function')), + new Twig_SimpleFunction('safe_br', array($this, 'br'), array('is_safe' => array('html'))), + new Twig_SimpleFunction('unsafe_br', array($this, 'br')), + new Twig_SimpleFunction('static_call_string', 'TwigTestExtension::staticCall'), + new Twig_SimpleFunction('static_call_array', array('TwigTestExtension', 'staticCall')), + new Twig_SimpleFunction('*_path', array($this, 'dynamic_path')), + new Twig_SimpleFunction('*_foo_*_bar', array($this, 'dynamic_foo')), + ); + } + + public function getTests() + { + return array( + new Twig_SimpleTest('multi word', array($this, 'is_multi_word')), + ); + } + + public function §Filter($value) + { + return "§{$value}§"; + } + + public function §Function($value) + { + return "§{$value}§"; + } + + /** + * nl2br which also escapes, for testing escaper filters. + */ + public function escape_and_nl2br($env, $value, $sep = '
    ') + { + return $this->nl2br(twig_escape_filter($env, $value, 'html'), $sep); + } + + /** + * nl2br only, for testing filters with pre_escape. + */ + public function nl2br($value, $sep = '
    ') + { + // not secure if $value contains html tags (not only entities) + // don't use + return str_replace("\n", "$sep\n", $value); + } + + public function dynamic_path($element, $item) + { + return $element.'/'.$item; + } + + public function dynamic_foo($foo, $bar, $item) + { + return $foo.'/'.$bar.'/'.$item; + } + + public function escape_something($value) + { + return strtoupper($value); + } + + public function preserves_safety($value) + { + return strtoupper($value); + } + + public static function staticCall($value) + { + return "*$value*"; + } + + public function br() + { + return '
    '; + } + + public function is_multi_word($value) + { + return false !== strpos($value, ' '); + } + + public function __call($method, $arguments) + { + if ('magicCall' !== $method) { + throw new BadMethodCallException('Unexpected call to __call'); + } + + return 'magic_'.$arguments[0]; + } + + public static function __callStatic($method, $arguments) + { + if ('magicStaticCall' !== $method) { + throw new BadMethodCallException('Unexpected call to __callStatic'); + } + + return 'static_magic_'.$arguments[0]; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/LegacyFixtures/autoescape/filename.legacy.test b/src/vendor/twig/twig/test/Twig/Tests/LegacyFixtures/autoescape/filename.legacy.test new file mode 100644 index 0000000..b091ad3 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/LegacyFixtures/autoescape/filename.legacy.test @@ -0,0 +1,18 @@ +--TEST-- +"filename" autoescape strategy +--TEMPLATE-- +{{ br -}} +{{ include('index.html.twig') -}} +{{ include('index.txt.twig') -}} +--TEMPLATE(index.html.twig)-- +{{ br -}} +--TEMPLATE(index.txt.twig)-- +{{ br -}} +--DATA-- +return array('br' => '
    ') +--CONFIG-- +return array('autoescape' => 'filename') +--EXPECT-- +<br /> +<br /> +
    diff --git a/src/vendor/twig/twig/test/Twig/Tests/LegacyFixtures/functions/undefined_block.legacy.test b/src/vendor/twig/twig/test/Twig/Tests/LegacyFixtures/functions/undefined_block.legacy.test new file mode 100644 index 0000000..62e24f0 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/LegacyFixtures/functions/undefined_block.legacy.test @@ -0,0 +1,12 @@ +--TEST-- +"block" function with undefined block +--TEMPLATE-- +{% extends "base.twig" %} +{% block foo %}{{ parent() }}{{ block('unknown') }}{{ block('bar') }}{% endblock %} +--TEMPLATE(base.twig)-- +{% block foo %}Foo{% endblock %} +{% block bar %}Bar{% endblock %} +--DATA-- +return array() +--EXPECT-- +FooBarBar diff --git a/src/vendor/twig/twig/test/Twig/Tests/LegacyFixtures/test.legacy.test b/src/vendor/twig/twig/test/Twig/Tests/LegacyFixtures/test.legacy.test new file mode 100644 index 0000000..d9c1d50 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/LegacyFixtures/test.legacy.test @@ -0,0 +1,8 @@ +--TEST-- +Old test classes usage +--TEMPLATE-- +{{ 'foo' is multi word ? 'yes' : 'no' }} +--DATA-- +return array() +--EXPECT-- +no diff --git a/src/vendor/twig/twig/test/Twig/Tests/LegacyIntegrationTest.php b/src/vendor/twig/twig/test/Twig/Tests/LegacyIntegrationTest.php new file mode 100644 index 0000000..055a617 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/LegacyIntegrationTest.php @@ -0,0 +1,54 @@ + new Twig_Test_Method($this, 'is_multi_word'), + ); + } + + public function is_multi_word($value) + { + return false !== strpos($value, ' '); + } + + public function getName() + { + return 'legacy_integration_test'; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/LexerTest.php b/src/vendor/twig/twig/test/Twig/Tests/LexerTest.php new file mode 100644 index 0000000..caf331e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/LexerTest.php @@ -0,0 +1,311 @@ +getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize('{{ foo }}', 'foo'); + $this->assertEquals('foo', $stream->getFilename()); + $this->assertEquals('{{ foo }}', $stream->getSource()); + } + + public function testNameLabelForTag() + { + $template = '{% § %}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + + $stream->expect(Twig_Token::BLOCK_START_TYPE); + $this->assertSame('§', $stream->expect(Twig_Token::NAME_TYPE)->getValue()); + } + + public function testNameLabelForFunction() + { + $template = '{{ §() }}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + + $stream->expect(Twig_Token::VAR_START_TYPE); + $this->assertSame('§', $stream->expect(Twig_Token::NAME_TYPE)->getValue()); + } + + public function testBracketsNesting() + { + $template = '{{ {"a":{"b":"c"}} }}'; + + $this->assertEquals(2, $this->countToken($template, Twig_Token::PUNCTUATION_TYPE, '{')); + $this->assertEquals(2, $this->countToken($template, Twig_Token::PUNCTUATION_TYPE, '}')); + } + + protected function countToken($template, $type, $value = null) + { + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + + $count = 0; + while (!$stream->isEOF()) { + $token = $stream->next(); + if ($type === $token->getType()) { + if (null === $value || $value === $token->getValue()) { + ++$count; + } + } + } + + return $count; + } + + public function testLineDirective() + { + $template = "foo\n" + ."bar\n" + ."{% line 10 %}\n" + ."{{\n" + ."baz\n" + ."}}\n"; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + + // foo\nbar\n + $this->assertSame(1, $stream->expect(Twig_Token::TEXT_TYPE)->getLine()); + // \n (after {% line %}) + $this->assertSame(10, $stream->expect(Twig_Token::TEXT_TYPE)->getLine()); + // {{ + $this->assertSame(11, $stream->expect(Twig_Token::VAR_START_TYPE)->getLine()); + // baz + $this->assertSame(12, $stream->expect(Twig_Token::NAME_TYPE)->getLine()); + } + + public function testLineDirectiveInline() + { + $template = "foo\n" + ."bar{% line 10 %}{{\n" + ."baz\n" + ."}}\n"; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + + // foo\nbar + $this->assertSame(1, $stream->expect(Twig_Token::TEXT_TYPE)->getLine()); + // {{ + $this->assertSame(10, $stream->expect(Twig_Token::VAR_START_TYPE)->getLine()); + // baz + $this->assertSame(11, $stream->expect(Twig_Token::NAME_TYPE)->getLine()); + } + + public function testLongComments() + { + $template = '{# '.str_repeat('*', 100000).' #}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $lexer->tokenize(new Twig_Source($template, 'index')); + + // should not throw an exception + } + + public function testLongVerbatim() + { + $template = '{% verbatim %}'.str_repeat('*', 100000).'{% endverbatim %}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $lexer->tokenize(new Twig_Source($template, 'index')); + + // should not throw an exception + } + + public function testLongVar() + { + $template = '{{ '.str_repeat('x', 100000).' }}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $lexer->tokenize(new Twig_Source($template, 'index')); + + // should not throw an exception + } + + public function testLongBlock() + { + $template = '{% '.str_repeat('x', 100000).' %}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $lexer->tokenize(new Twig_Source($template, 'index')); + + // should not throw an exception + } + + public function testBigNumbers() + { + $template = '{{ 922337203685477580700 }}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + $stream->next(); + $node = $stream->next(); + $this->assertEquals('922337203685477580700', $node->getValue()); + } + + public function testStringWithEscapedDelimiter() + { + $tests = array( + "{{ 'foo \' bar' }}" => 'foo \' bar', + '{{ "foo \" bar" }}' => 'foo " bar', + ); + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + foreach ($tests as $template => $expected) { + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + $stream->expect(Twig_Token::VAR_START_TYPE); + $stream->expect(Twig_Token::STRING_TYPE, $expected); + } + } + + public function testStringWithInterpolation() + { + $template = 'foo {{ "bar #{ baz + 1 }" }}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + $stream->expect(Twig_Token::TEXT_TYPE, 'foo '); + $stream->expect(Twig_Token::VAR_START_TYPE); + $stream->expect(Twig_Token::STRING_TYPE, 'bar '); + $stream->expect(Twig_Token::INTERPOLATION_START_TYPE); + $stream->expect(Twig_Token::NAME_TYPE, 'baz'); + $stream->expect(Twig_Token::OPERATOR_TYPE, '+'); + $stream->expect(Twig_Token::NUMBER_TYPE, '1'); + $stream->expect(Twig_Token::INTERPOLATION_END_TYPE); + $stream->expect(Twig_Token::VAR_END_TYPE); + } + + public function testStringWithEscapedInterpolation() + { + $template = '{{ "bar \#{baz+1}" }}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + $stream->expect(Twig_Token::VAR_START_TYPE); + $stream->expect(Twig_Token::STRING_TYPE, 'bar #{baz+1}'); + $stream->expect(Twig_Token::VAR_END_TYPE); + } + + public function testStringWithHash() + { + $template = '{{ "bar # baz" }}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + $stream->expect(Twig_Token::VAR_START_TYPE); + $stream->expect(Twig_Token::STRING_TYPE, 'bar # baz'); + $stream->expect(Twig_Token::VAR_END_TYPE); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unclosed """ + */ + public function testStringWithUnterminatedInterpolation() + { + $template = '{{ "bar #{x" }}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $lexer->tokenize(new Twig_Source($template, 'index')); + } + + public function testStringWithNestedInterpolations() + { + $template = '{{ "bar #{ "foo#{bar}" }" }}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + $stream->expect(Twig_Token::VAR_START_TYPE); + $stream->expect(Twig_Token::STRING_TYPE, 'bar '); + $stream->expect(Twig_Token::INTERPOLATION_START_TYPE); + $stream->expect(Twig_Token::STRING_TYPE, 'foo'); + $stream->expect(Twig_Token::INTERPOLATION_START_TYPE); + $stream->expect(Twig_Token::NAME_TYPE, 'bar'); + $stream->expect(Twig_Token::INTERPOLATION_END_TYPE); + $stream->expect(Twig_Token::INTERPOLATION_END_TYPE); + $stream->expect(Twig_Token::VAR_END_TYPE); + } + + public function testStringWithNestedInterpolationsInBlock() + { + $template = '{% foo "bar #{ "foo#{bar}" }" %}'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + $stream->expect(Twig_Token::BLOCK_START_TYPE); + $stream->expect(Twig_Token::NAME_TYPE, 'foo'); + $stream->expect(Twig_Token::STRING_TYPE, 'bar '); + $stream->expect(Twig_Token::INTERPOLATION_START_TYPE); + $stream->expect(Twig_Token::STRING_TYPE, 'foo'); + $stream->expect(Twig_Token::INTERPOLATION_START_TYPE); + $stream->expect(Twig_Token::NAME_TYPE, 'bar'); + $stream->expect(Twig_Token::INTERPOLATION_END_TYPE); + $stream->expect(Twig_Token::INTERPOLATION_END_TYPE); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + } + + public function testOperatorEndingWithALetterAtTheEndOfALine() + { + $template = "{{ 1 and\n0}}"; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $stream = $lexer->tokenize(new Twig_Source($template, 'index')); + $stream->expect(Twig_Token::VAR_START_TYPE); + $stream->expect(Twig_Token::NUMBER_TYPE, 1); + $stream->expect(Twig_Token::OPERATOR_TYPE, 'and'); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unclosed "variable" in "index" at line 3 + */ + public function testUnterminatedVariable() + { + $template = ' + +{{ + +bar + + +'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $lexer->tokenize(new Twig_Source($template, 'index')); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unclosed "block" in "index" at line 3 + */ + public function testUnterminatedBlock() + { + $template = ' + +{% + +bar + + +'; + + $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $lexer->tokenize(new Twig_Source($template, 'index')); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/ArrayTest.php b/src/vendor/twig/twig/test/Twig/Tests/Loader/ArrayTest.php new file mode 100644 index 0000000..fe009e2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/ArrayTest.php @@ -0,0 +1,111 @@ + 'bar')); + + $this->assertEquals('bar', $loader->getSource('foo')); + } + + /** + * @group legacy + * @expectedException Twig_Error_Loader + */ + public function testGetSourceWhenTemplateDoesNotExist() + { + $loader = new Twig_Loader_Array(array()); + + $loader->getSource('foo'); + } + + /** + * @expectedException Twig_Error_Loader + */ + public function testGetSourceContextWhenTemplateDoesNotExist() + { + $loader = new Twig_Loader_Array(array()); + + $loader->getSourceContext('foo'); + } + + public function testGetCacheKey() + { + $loader = new Twig_Loader_Array(array('foo' => 'bar')); + + $this->assertEquals('bar', $loader->getCacheKey('foo')); + } + + /** + * @expectedException Twig_Error_Loader + */ + public function testGetCacheKeyWhenTemplateDoesNotExist() + { + $loader = new Twig_Loader_Array(array()); + + $loader->getCacheKey('foo'); + } + + public function testSetTemplate() + { + $loader = new Twig_Loader_Array(array()); + $loader->setTemplate('foo', 'bar'); + + $this->assertEquals('bar', $loader->getSourceContext('foo')->getCode()); + } + + public function testIsFresh() + { + $loader = new Twig_Loader_Array(array('foo' => 'bar')); + $this->assertTrue($loader->isFresh('foo', time())); + } + + /** + * @expectedException Twig_Error_Loader + */ + public function testIsFreshWhenTemplateDoesNotExist() + { + $loader = new Twig_Loader_Array(array()); + + $loader->isFresh('foo', time()); + } + + public function testTemplateReference() + { + $name = new Twig_Test_Loader_TemplateReference('foo'); + $loader = new Twig_Loader_Array(array('foo' => 'bar')); + + $loader->getCacheKey($name); + $loader->getSourceContext($name); + $loader->isFresh($name, time()); + $loader->setTemplate($name, 'foobar'); + } +} + +class Twig_Test_Loader_TemplateReference +{ + private $name; + + public function __construct($name) + { + $this->name = $name; + } + + public function __toString() + { + return $this->name; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/ChainTest.php b/src/vendor/twig/twig/test/Twig/Tests/Loader/ChainTest.php new file mode 100644 index 0000000..17ec482 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/ChainTest.php @@ -0,0 +1,124 @@ + 'bar')), + new Twig_Loader_Array(array('foo' => 'foobar', 'bar' => 'foo')), + )); + + $this->assertEquals('bar', $loader->getSource('foo')); + $this->assertEquals('foo', $loader->getSource('bar')); + } + + public function testGetSourceContext() + { + $path = dirname(__FILE__).'/../Fixtures'; + $loader = new Twig_Loader_Chain(array( + new Twig_Loader_Array(array('foo' => 'bar')), + new Twig_Loader_Array(array('errors/index.html' => 'baz')), + new Twig_Loader_Filesystem(array($path)), + )); + + $this->assertEquals('foo', $loader->getSourceContext('foo')->getName()); + $this->assertSame('', $loader->getSourceContext('foo')->getPath()); + + $this->assertEquals('errors/index.html', $loader->getSourceContext('errors/index.html')->getName()); + $this->assertSame('', $loader->getSourceContext('errors/index.html')->getPath()); + $this->assertEquals('baz', $loader->getSourceContext('errors/index.html')->getCode()); + + $this->assertEquals('errors/base.html', $loader->getSourceContext('errors/base.html')->getName()); + $this->assertEquals(realpath($path.'/errors/base.html'), realpath($loader->getSourceContext('errors/base.html')->getPath())); + $this->assertNotEquals('baz', $loader->getSourceContext('errors/base.html')->getCode()); + } + + /** + * @expectedException Twig_Error_Loader + */ + public function testGetSourceContextWhenTemplateDoesNotExist() + { + $loader = new Twig_Loader_Chain(array()); + + $loader->getSourceContext('foo'); + } + + /** + * @group legacy + * @expectedException Twig_Error_Loader + */ + public function testGetSourceWhenTemplateDoesNotExist() + { + $loader = new Twig_Loader_Chain(array()); + + $loader->getSource('foo'); + } + + public function testGetCacheKey() + { + $loader = new Twig_Loader_Chain(array( + new Twig_Loader_Array(array('foo' => 'bar')), + new Twig_Loader_Array(array('foo' => 'foobar', 'bar' => 'foo')), + )); + + $this->assertEquals('bar', $loader->getCacheKey('foo')); + $this->assertEquals('foo', $loader->getCacheKey('bar')); + } + + /** + * @expectedException Twig_Error_Loader + */ + public function testGetCacheKeyWhenTemplateDoesNotExist() + { + $loader = new Twig_Loader_Chain(array()); + + $loader->getCacheKey('foo'); + } + + public function testAddLoader() + { + $loader = new Twig_Loader_Chain(); + $loader->addLoader(new Twig_Loader_Array(array('foo' => 'bar'))); + + $this->assertEquals('bar', $loader->getSourceContext('foo')->getCode()); + } + + public function testExists() + { + $loader1 = $this->getMockBuilder('Twig_ChainTestLoaderWithExistsInterface')->getMock(); + $loader1->expects($this->once())->method('exists')->will($this->returnValue(false)); + $loader1->expects($this->never())->method('getSourceContext'); + + // can be removed in 2.0 + $loader2 = $this->getMockBuilder('Twig_ChainTestLoaderInterface')->getMock(); + //$loader2 = $this->getMockBuilder(array('Twig_LoaderInterface', 'Twig_SourceContextLoaderInterface'))->getMock(); + $loader2->expects($this->once())->method('getSourceContext')->will($this->returnValue(new Twig_Source('content', 'index'))); + + $loader = new Twig_Loader_Chain(); + $loader->addLoader($loader1); + $loader->addLoader($loader2); + + $this->assertTrue($loader->exists('foo')); + } +} + +interface Twig_ChainTestLoaderInterface extends Twig_LoaderInterface, Twig_SourceContextLoaderInterface +{ +} + +interface Twig_ChainTestLoaderWithExistsInterface extends Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface +{ +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/FilesystemTest.php b/src/vendor/twig/twig/test/Twig/Tests/Loader/FilesystemTest.php new file mode 100644 index 0000000..c8cbaf7 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/FilesystemTest.php @@ -0,0 +1,226 @@ +assertEquals('errors/index.html', $loader->getSourceContext('errors/index.html')->getName()); + $this->assertEquals(realpath($path.'/errors/index.html'), realpath($loader->getSourceContext('errors/index.html')->getPath())); + } + + /** + * @dataProvider getSecurityTests + */ + public function testSecurity($template) + { + $loader = new Twig_Loader_Filesystem(array(dirname(__FILE__).'/../Fixtures')); + + try { + $loader->getCacheKey($template); + $this->fail(); + } catch (Twig_Error_Loader $e) { + $this->assertNotContains('Unable to find template', $e->getMessage()); + } + } + + public function getSecurityTests() + { + return array( + array("AutoloaderTest\0.php"), + array('..\\AutoloaderTest.php'), + array('..\\\\\\AutoloaderTest.php'), + array('../AutoloaderTest.php'), + array('..////AutoloaderTest.php'), + array('./../AutoloaderTest.php'), + array('.\\..\\AutoloaderTest.php'), + array('././././././../AutoloaderTest.php'), + array('.\\./.\\./.\\./../AutoloaderTest.php'), + array('foo/../../AutoloaderTest.php'), + array('foo\\..\\..\\AutoloaderTest.php'), + array('foo/../bar/../../AutoloaderTest.php'), + array('foo/bar/../../../AutoloaderTest.php'), + array('filters/../../AutoloaderTest.php'), + array('filters//..//..//AutoloaderTest.php'), + array('filters\\..\\..\\AutoloaderTest.php'), + array('filters\\\\..\\\\..\\\\AutoloaderTest.php'), + array('filters\\//../\\/\\..\\AutoloaderTest.php'), + array('/../AutoloaderTest.php'), + ); + } + + /** + * @dataProvider getBasePaths + */ + public function testPaths($basePath, $cacheKey, $rootPath) + { + $loader = new Twig_Loader_Filesystem(array($basePath.'/normal', $basePath.'/normal_bis'), $rootPath); + $loader->setPaths(array($basePath.'/named', $basePath.'/named_bis'), 'named'); + $loader->addPath($basePath.'/named_ter', 'named'); + $loader->addPath($basePath.'/normal_ter'); + $loader->prependPath($basePath.'/normal_final'); + $loader->prependPath($basePath.'/named/../named_quater', 'named'); + $loader->prependPath($basePath.'/named_final', 'named'); + + $this->assertEquals(array( + $basePath.'/normal_final', + $basePath.'/normal', + $basePath.'/normal_bis', + $basePath.'/normal_ter', + ), $loader->getPaths()); + $this->assertEquals(array( + $basePath.'/named_final', + $basePath.'/named/../named_quater', + $basePath.'/named', + $basePath.'/named_bis', + $basePath.'/named_ter', + ), $loader->getPaths('named')); + + // do not use realpath here as it would make the test unuseful + $this->assertEquals($cacheKey, str_replace('\\', '/', $loader->getCacheKey('@named/named_absolute.html'))); + $this->assertEquals("path (final)\n", $loader->getSourceContext('index.html')->getCode()); + $this->assertEquals("path (final)\n", $loader->getSourceContext('@__main__/index.html')->getCode()); + $this->assertEquals("named path (final)\n", $loader->getSourceContext('@named/index.html')->getCode()); + } + + public function getBasePaths() + { + return array( + array( + dirname(__FILE__).'/Fixtures', + 'test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html', + null, + ), + array( + dirname(__FILE__).'/Fixtures/../Fixtures', + 'test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html', + null, + ), + array( + 'test/Twig/Tests/Loader/Fixtures', + 'test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html', + getcwd(), + ), + array( + 'Fixtures', + 'Fixtures/named_quater/named_absolute.html', + getcwd().'/test/Twig/Tests/Loader', + ), + array( + 'Fixtures', + 'Fixtures/named_quater/named_absolute.html', + getcwd().'/test/../test/Twig/Tests/Loader', + ), + ); + } + + public function testEmptyConstructor() + { + $loader = new Twig_Loader_Filesystem(); + $this->assertEquals(array(), $loader->getPaths()); + } + + public function testGetNamespaces() + { + $loader = new Twig_Loader_Filesystem(sys_get_temp_dir()); + $this->assertEquals(array(Twig_Loader_Filesystem::MAIN_NAMESPACE), $loader->getNamespaces()); + + $loader->addPath(sys_get_temp_dir(), 'named'); + $this->assertEquals(array(Twig_Loader_Filesystem::MAIN_NAMESPACE, 'named'), $loader->getNamespaces()); + } + + public function testFindTemplateExceptionNamespace() + { + $basePath = dirname(__FILE__).'/Fixtures'; + + $loader = new Twig_Loader_Filesystem(array($basePath.'/normal')); + $loader->addPath($basePath.'/named', 'named'); + + try { + $loader->getSourceContext('@named/nowhere.html'); + } catch (Exception $e) { + $this->assertInstanceof('Twig_Error_Loader', $e); + $this->assertContains('Unable to find template "@named/nowhere.html"', $e->getMessage()); + } + } + + public function testFindTemplateWithCache() + { + $basePath = dirname(__FILE__).'/Fixtures'; + + $loader = new Twig_Loader_Filesystem(array($basePath.'/normal')); + $loader->addPath($basePath.'/named', 'named'); + + // prime the cache for index.html in the named namespace + $namedSource = $loader->getSourceContext('@named/index.html')->getCode(); + $this->assertEquals("named path\n", $namedSource); + + // get index.html from the main namespace + $this->assertEquals("path\n", $loader->getSourceContext('index.html')->getCode()); + } + + public function testLoadTemplateAndRenderBlockWithCache() + { + $loader = new Twig_Loader_Filesystem(array()); + $loader->addPath(dirname(__FILE__).'/Fixtures/themes/theme2'); + $loader->addPath(dirname(__FILE__).'/Fixtures/themes/theme1'); + $loader->addPath(dirname(__FILE__).'/Fixtures/themes/theme1', 'default_theme'); + + $twig = new Twig_Environment($loader); + + $template = $twig->loadTemplate('blocks.html.twig'); + $this->assertSame('block from theme 1', $template->renderBlock('b1', array())); + + $template = $twig->loadTemplate('blocks.html.twig'); + $this->assertSame('block from theme 2', $template->renderBlock('b2', array())); + } + + public function getArrayInheritanceTests() + { + return array( + 'valid array inheritance' => array('array_inheritance_valid_parent.html.twig'), + 'array inheritance with null first template' => array('array_inheritance_null_parent.html.twig'), + 'array inheritance with empty first template' => array('array_inheritance_empty_parent.html.twig'), + 'array inheritance with non-existent first template' => array('array_inheritance_nonexistent_parent.html.twig'), + ); + } + + /** + * @dataProvider getArrayInheritanceTests + * + * @param $templateName string Template name with array inheritance + */ + public function testArrayInheritance($templateName) + { + $loader = new Twig_Loader_Filesystem(array()); + $loader->addPath(dirname(__FILE__).'/Fixtures/inheritance'); + + $twig = new Twig_Environment($loader); + + $template = $twig->loadTemplate($templateName); + $this->assertSame('VALID Child', $template->renderBlock('body', array())); + } + + /** + * @requires PHP 5.3 + */ + public function testLoadTemplateFromPhar() + { + $loader = new Twig_Loader_Filesystem(array()); + // phar-sample.phar was created with the following script: + // $f = new Phar('phar-test.phar'); + // $f->addFromString('hello.twig', 'hello from phar'); + $loader->addPath('phar://'.dirname(__FILE__).'/Fixtures/phar/phar-sample.phar'); + $this->assertSame('hello from phar', $loader->getSourceContext('hello.twig')->getCode()); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_empty_parent.html.twig b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_empty_parent.html.twig new file mode 100644 index 0000000..6977ebf --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_empty_parent.html.twig @@ -0,0 +1,3 @@ +{% extends ['','parent.html.twig'] %} + +{% block body %}{{ parent() }} Child{% endblock %} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_nonexistent_parent.html.twig b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_nonexistent_parent.html.twig new file mode 100644 index 0000000..5b50a8b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_nonexistent_parent.html.twig @@ -0,0 +1,3 @@ +{% extends ['nonexistent.html.twig','parent.html.twig'] %} + +{% block body %}{{ parent() }} Child{% endblock %} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_null_parent.html.twig b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_null_parent.html.twig new file mode 100644 index 0000000..a16b3ad --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_null_parent.html.twig @@ -0,0 +1,3 @@ +{% extends [null,'parent.html.twig'] %} + +{% block body %}{{ parent() }} Child{% endblock %} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_valid_parent.html.twig b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_valid_parent.html.twig new file mode 100644 index 0000000..4940dad --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/array_inheritance_valid_parent.html.twig @@ -0,0 +1,3 @@ +{% extends ['parent.html.twig','spare_parent.html.twig'] %} + +{% block body %}{{ parent() }} Child{% endblock %} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/parent.html.twig b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/parent.html.twig new file mode 100644 index 0000000..d594c0e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/parent.html.twig @@ -0,0 +1 @@ +{% block body %}VALID{% endblock %} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/spare_parent.html.twig b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/spare_parent.html.twig new file mode 100644 index 0000000..70b7360 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/inheritance/spare_parent.html.twig @@ -0,0 +1 @@ +{% block body %}SPARE PARENT{% endblock %} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named/index.html b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named/index.html new file mode 100644 index 0000000..9e5449c --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named/index.html @@ -0,0 +1 @@ +named path diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_bis/index.html b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_bis/index.html new file mode 100644 index 0000000..d3a272b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_bis/index.html @@ -0,0 +1 @@ +named path (bis) diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_final/index.html b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_final/index.html new file mode 100644 index 0000000..9f05d15 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_final/index.html @@ -0,0 +1 @@ +named path (final) diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html new file mode 100644 index 0000000..b1fb5f5 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html @@ -0,0 +1 @@ +named path (quater) diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_ter/index.html b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_ter/index.html new file mode 100644 index 0000000..24fb68a --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/named_ter/index.html @@ -0,0 +1 @@ +named path (ter) diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal/index.html b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal/index.html new file mode 100644 index 0000000..e7a8fd4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal/index.html @@ -0,0 +1 @@ +path diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal_bis/index.html b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal_bis/index.html new file mode 100644 index 0000000..bfa9160 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal_bis/index.html @@ -0,0 +1 @@ +path (bis) diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal_final/index.html b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal_final/index.html new file mode 100644 index 0000000..73a089b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal_final/index.html @@ -0,0 +1 @@ +path (final) diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal_ter/index.html b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal_ter/index.html new file mode 100644 index 0000000..b7ad97d --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/normal_ter/index.html @@ -0,0 +1 @@ +path (ter) diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/phar/phar-sample.phar b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/phar/phar-sample.phar new file mode 100644 index 0000000000000000000000000000000000000000..092bbfae3e4c98b308dc98bf7653ead68ae3ee6e GIT binary patch literal 6786 zcmb_hOLN=E5q5IqWh?g_#w@G>Mx-dx&rq@~*-JkvlEqM5ZLJv>0s=!4E4&r}B}(hP zu&2!Q zBabh)K{9u1HGj%1KeC-Pb*?NknLDZ3WLchaC$wigw=YvCNqCxBjRw1SkGX-9Wj0^> zS)N(uK(g;(@4Ff;dObhNdFt{cf9VJO@V}w zadTVhIUOAiN4vF@=ZiGjt$i4sBa?mjz}5||unkU5M`lALde!||Y~|^KBX%S7L!RM;DW<2Eh9F$qo(Np_{kp$|P=Z;JZ%l z$5DIP`o_6%GB@>;oK%Lsr_$prJU?!WwWLdxx5%-|y&_{t%<+X}#s{Cbzg_t5d5&Tf zd&&J3MOczVqCA#|5~={jZxP8VJ@M2LltUpRR3*B2Wbm{EHM^{k$mX$oe(7BB))akg zPJH?5;bJM2ir=M_YC43=(a;h~mJ?MA(gj))6(u25hzg-qvxaM0n{&tGsa1dBxO3im z5l49%2R#7h?{wI4e8D|7;A!YYJj#PB z^Cq?CQ7o@WkI_(CZ^t4}*pV{?wjhf^nA6BG97K+p8HnLlkv7PE@n+b6fvNWO%7Bx4 z#0fzOLQ)|k{dA(1;ov;>G_^vrI4)HlI@%rfVCMmQ8|Un0yofxClDqSm)%U3J?EfKR zdn7?0*k11E0pF)_eIucW#MsuHdz09^k|OiYJ{f2+Ayr55Bzcq7OY&{72x%n*HDtM7 zN*Q4hN|zr^V+vE9tQxA5v585=qYO<8la!8E2T3;ur`H-?CA`N<1FdwJn&*f|vwSX( zbczr&|61mh3fJ+7ByH=H6nIEZelbqR#;PaM72=ZPc5NT^_>^_X-{PJ)H&K5|uqy)f zXY&C6b9>#t>Lj3IH@Ne2API}dtQVmI=2Lw!w5ZrpxBzWz`CBja`W)LGJ*yWt=wxw+mS-@9K5XOZu$ckZ6v4b!? zpIc^sI6N8l7#gTQ?f#|I;*1 zd+a*!C#+~N%+D}>bA};02{0jA6oknNd8@&+au#v!fn^$}LP^Vw!zAVC7#_o$%1JXB zlzK*#JVn)Sc6D;?ajj|H+sU<`bU*2CS;q#CC)eGNI4mx5aSnNy5C+gSaaZnQLf!Q< zJPZAta%D+SAj&pF7I6Ehrwb)wWoLj}^34ZL)c#N$03zt0C4LN#-Fk!FMe9`8wY*E( z1gf|#v{}_wM_Ic|EmT)TS!siYErmisu51*w@0szR$>{jy>FZ3E^~zEw6%4o2Y!kPVY@6E}eU&?ts3%e=FGiu1bQDC@ZjmFrmEB8^ z4(uR27f}&olkGfy{J6>VBT+a|0xR+W)x}!Pmh^Q4a0$JKay;o)Dj%on4yPPX0X_6Ltez1B^mjZ>a#xRo3j8g)V~Wo(A#dxoZD$eP?| z2B{XsN`P%8S|m&YE)}(gd8lS;?JA`oWYb7xzf`GS(fE9BW_@U>e-u z*z)leK?^x)0`7dzIHnq!+NW}*#N@H4a-%b`)P~3i#@aLyQM&Um#zb4s0W3dy^oT5r ze>M3)SS=nkb48a^Zv<&qeZ@keYqMIn84T!~?qCgG5A2gu9P3wa-U8d-GzNq$Fw}*U z%97{Ng=LP82jF@}1y)-3Zq|KbAjcFeFd$E{kS^5&%r-?0HRucS{%A@`4&@~=zTP7x zN~h>tS}7S=brAT!(gh`5)=X}YqP1j|W+MR_H8;!nL_fbm99WYHR01RbPc|@hG?&0G zCF#xsyuXY9I0dd4^tI9k4TS<4;lOwXYCa`|A{1m$02+c616;DuJ*7J;Rv7O9Kk~wY z7oWDaDC02BBXDdmE6u17mCY>}-Gv~{96g5Aa}@c|5~Np$C0vD=qCuE3=`32uxKM=K zDZf>-@hD%Qp%qR!?XyYy%x#}}?K9p!!70eif2a zY}5~RK6da`w1npMH<9?ph>5S@BPWn}l+lC$>cYd9aRR@1r*9Vz52J-!82Nj$#F)uGaeJ3q1ba)V<%e^m|15jfyCh>Q8eK-+j5~*&>u~YU3y&d@s+0 zTH(#@$DUn{H4Fue=;e27SQRK?7Q_?F{0(|OfrP29CNWdh-#9=OWQ7yK^Nby&p@3Id z^w_~73jBzkjz|_A^DSxmtBFRLQr8BEG+n_*YRqa=G@77)9aE^#bg-&a`zwFpz;hU~ zw*C6>Xk@=QIUeB8Eklslv;8M`?%eqW{{Kq-#xMNT@PE#OAl}X|{n=+&{rQLgenr2R zUq3wj2MORuaIk3_hXVQj>yQ8J|L^Zl{`Bz2U;pj!umAb+FF!2*{Y$XDdVc);|E1Ux AZ~y=R literal 0 HcmV?d00001 diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/themes/theme1/blocks.html.twig b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/themes/theme1/blocks.html.twig new file mode 100644 index 0000000..dd0cbc2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/themes/theme1/blocks.html.twig @@ -0,0 +1,3 @@ +{% block b1 %}block from theme 1{% endblock %} + +{% block b2 %}block from theme 1{% endblock %} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/themes/theme2/blocks.html.twig b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/themes/theme2/blocks.html.twig new file mode 100644 index 0000000..07cf9db --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Loader/Fixtures/themes/theme2/blocks.html.twig @@ -0,0 +1,3 @@ +{% use '@default_theme/blocks.html.twig' %} + +{% block b2 %}block from theme 2{% endblock %} diff --git a/src/vendor/twig/twig/test/Twig/Tests/NativeExtensionTest.php b/src/vendor/twig/twig/test/Twig/Tests/NativeExtensionTest.php new file mode 100644 index 0000000..a692be1 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/NativeExtensionTest.php @@ -0,0 +1,36 @@ +markTestSkipped('Skip under HHVM as the behavior is not the same as plain PHP (which is an edge case anyway)'); + } + + $twig = new Twig_Environment(new Twig_Loader_Array(array('index' => '{{ d1.date }}{{ d2.date }}')), array( + 'debug' => true, + 'cache' => false, + 'autoescape' => false, + )); + + $d1 = new DateTime(); + $d2 = new DateTime(); + $output = $twig->render('index', compact('d1', 'd2')); + + // If it fails, PHP will crash. + $this->assertEquals($output, $d1->date.$d2->date); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/AutoEscapeTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/AutoEscapeTest.php new file mode 100644 index 0000000..25d1602 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/AutoEscapeTest.php @@ -0,0 +1,32 @@ +assertEquals($body, $node->getNode('body')); + $this->assertTrue($node->getAttribute('value')); + } + + public function getTests() + { + $body = new Twig_Node(array(new Twig_Node_Text('foo', 1))); + $node = new Twig_Node_AutoEscape(true, $body, 1); + + return array( + array($node, "// line 1\necho \"foo\";"), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/BlockReferenceTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/BlockReferenceTest.php new file mode 100644 index 0000000..84dac9b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/BlockReferenceTest.php @@ -0,0 +1,31 @@ +assertEquals('foo', $node->getAttribute('name')); + } + + public function getTests() + { + return array( + array(new Twig_Node_BlockReference('foo', 1), <<displayBlock('foo', \$context, \$blocks); +EOF + ), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/BlockTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/BlockTest.php new file mode 100644 index 0000000..e7246dc --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/BlockTest.php @@ -0,0 +1,39 @@ +assertEquals($body, $node->getNode('body')); + $this->assertEquals('foo', $node->getAttribute('name')); + } + + public function getTests() + { + $body = new Twig_Node_Text('foo', 1); + $node = new Twig_Node_Block('foo', $body, 1); + + return array( + array($node, <<assertEquals($expr, $node->getNode('expr')); + } + + public function getTests() + { + $tests = array(); + + $expr = new Twig_Node_Expression_Constant('foo', 1); + $node = new Twig_Node_Do($expr, 1); + $tests[] = array($node, "// line 1\n\"foo\";"); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ArrayTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ArrayTest.php new file mode 100644 index 0000000..4f83ab1 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ArrayTest.php @@ -0,0 +1,37 @@ +assertEquals($foo, $node->getNode(1)); + } + + public function getTests() + { + $elements = array( + new Twig_Node_Expression_Constant('foo', 1), + new Twig_Node_Expression_Constant('bar', 1), + + new Twig_Node_Expression_Constant('bar', 1), + new Twig_Node_Expression_Constant('foo', 1), + ); + $node = new Twig_Node_Expression_Array($elements, 1); + + return array( + array($node, 'array("foo" => "bar", "bar" => "foo")'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/AssignNameTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/AssignNameTest.php new file mode 100644 index 0000000..bf365de --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/AssignNameTest.php @@ -0,0 +1,29 @@ +assertEquals('foo', $node->getAttribute('name')); + } + + public function getTests() + { + $node = new Twig_Node_Expression_AssignName('foo', 1); + + return array( + array($node, '$context["foo"]'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/AddTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/AddTest.php new file mode 100644 index 0000000..02310a1 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/AddTest.php @@ -0,0 +1,34 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 1); + $right = new Twig_Node_Expression_Constant(2, 1); + $node = new Twig_Node_Expression_Binary_Add($left, $right, 1); + + return array( + array($node, '(1 + 2)'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/AndTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/AndTest.php new file mode 100644 index 0000000..2df3c8e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/AndTest.php @@ -0,0 +1,34 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 1); + $right = new Twig_Node_Expression_Constant(2, 1); + $node = new Twig_Node_Expression_Binary_And($left, $right, 1); + + return array( + array($node, '(1 && 2)'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/ConcatTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/ConcatTest.php new file mode 100644 index 0000000..759e482 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/ConcatTest.php @@ -0,0 +1,34 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 1); + $right = new Twig_Node_Expression_Constant(2, 1); + $node = new Twig_Node_Expression_Binary_Concat($left, $right, 1); + + return array( + array($node, '(1 . 2)'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/DivTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/DivTest.php new file mode 100644 index 0000000..0e54b10 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/DivTest.php @@ -0,0 +1,34 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 1); + $right = new Twig_Node_Expression_Constant(2, 1); + $node = new Twig_Node_Expression_Binary_Div($left, $right, 1); + + return array( + array($node, '(1 / 2)'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php new file mode 100644 index 0000000..5813dce --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php @@ -0,0 +1,34 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 1); + $right = new Twig_Node_Expression_Constant(2, 1); + $node = new Twig_Node_Expression_Binary_FloorDiv($left, $right, 1); + + return array( + array($node, '(int) floor((1 / 2))'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/ModTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/ModTest.php new file mode 100644 index 0000000..4c663c7 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/ModTest.php @@ -0,0 +1,34 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 1); + $right = new Twig_Node_Expression_Constant(2, 1); + $node = new Twig_Node_Expression_Binary_Mod($left, $right, 1); + + return array( + array($node, '(1 % 2)'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/MulTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/MulTest.php new file mode 100644 index 0000000..e92c95e --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/MulTest.php @@ -0,0 +1,34 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 1); + $right = new Twig_Node_Expression_Constant(2, 1); + $node = new Twig_Node_Expression_Binary_Mul($left, $right, 1); + + return array( + array($node, '(1 * 2)'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/OrTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/OrTest.php new file mode 100644 index 0000000..ec37c83 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/OrTest.php @@ -0,0 +1,34 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 1); + $right = new Twig_Node_Expression_Constant(2, 1); + $node = new Twig_Node_Expression_Binary_Or($left, $right, 1); + + return array( + array($node, '(1 || 2)'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/SubTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/SubTest.php new file mode 100644 index 0000000..061cb27 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Binary/SubTest.php @@ -0,0 +1,34 @@ +assertEquals($left, $node->getNode('left')); + $this->assertEquals($right, $node->getNode('right')); + } + + public function getTests() + { + $left = new Twig_Node_Expression_Constant(1, 1); + $right = new Twig_Node_Expression_Constant(2, 1); + $node = new Twig_Node_Expression_Binary_Sub($left, $right, 1); + + return array( + array($node, '(1 - 2)'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/CallTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/CallTest.php new file mode 100644 index 0000000..28c8148 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/CallTest.php @@ -0,0 +1,147 @@ + 'function', 'name' => 'date')); + $this->assertEquals(array('U', null), $node->getArguments('date', array('format' => 'U', 'timestamp' => null))); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Positional arguments cannot be used after named arguments for function "date". + */ + public function testGetArgumentsWhenPositionalArgumentsAfterNamedArguments() + { + $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'date')); + $node->getArguments('date', array('timestamp' => 123456, 'Y-m-d')); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Argument "format" is defined twice for function "date". + */ + public function testGetArgumentsWhenArgumentIsDefinedTwice() + { + $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'date')); + $node->getArguments('date', array('Y-m-d', 'format' => 'U')); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unknown argument "unknown" for function "date(format, timestamp)". + */ + public function testGetArgumentsWithWrongNamedArgumentName() + { + $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'date')); + $node->getArguments('date', array('Y-m-d', 'timestamp' => null, 'unknown' => '')); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unknown arguments "unknown1", "unknown2" for function "date(format, timestamp)". + */ + public function testGetArgumentsWithWrongNamedArgumentNames() + { + $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'date')); + $node->getArguments('date', array('Y-m-d', 'timestamp' => null, 'unknown1' => '', 'unknown2' => '')); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Argument "case_sensitivity" could not be assigned for function "substr_compare(main_str, str, offset, length, case_sensitivity)" because it is mapped to an internal PHP function which cannot determine default value for optional argument "length". + */ + public function testResolveArgumentsWithMissingValueForOptionalArgument() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Skip under HHVM as the behavior is not the same as plain PHP (which is an edge case anyway)'); + } + + $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'substr_compare')); + $node->getArguments('substr_compare', array('abcd', 'bc', 'offset' => 1, 'case_sensitivity' => true)); + } + + public function testResolveArgumentsOnlyNecessaryArgumentsForCustomFunction() + { + $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'custom_function')); + + $this->assertEquals(array('arg1'), $node->getArguments(array($this, 'customFunction'), array('arg1' => 'arg1'))); + } + + public function testGetArgumentsForStaticMethod() + { + $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'custom_static_function')); + $this->assertEquals(array('arg1'), $node->getArguments(__CLASS__.'::customStaticFunction', array('arg1' => 'arg1'))); + } + + /** + * @expectedException LogicException + * @expectedExceptionMessage The last parameter of "Twig_Tests_Node_Expression_CallTest::customFunctionWithArbitraryArguments" for function "foo" must be an array with default value, eg. "array $arg = array()". + */ + public function testResolveArgumentsWithMissingParameterForArbitraryArguments() + { + $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'foo', 'is_variadic' => true)); + $node->getArguments(array($this, 'customFunctionWithArbitraryArguments'), array()); + } + + public static function customStaticFunction($arg1, $arg2 = 'default', $arg3 = array()) + { + } + + public function customFunction($arg1, $arg2 = 'default', $arg3 = array()) + { + } + + public function customFunctionWithArbitraryArguments() + { + } + + /** + * @expectedException LogicException + * @expectedExceptionMessageRegExp #^The last parameter of "custom_Twig_Tests_Node_Expression_CallTest_function" for function "foo" must be an array with default value, eg\. "array \$arg \= array\(\)"\.$# + */ + public function testResolveArgumentsWithMissingParameterForArbitraryArgumentsOnFunction() + { + $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'foo', 'is_variadic' => true)); + $node->getArguments('custom_Twig_Tests_Node_Expression_CallTest_function', array()); + } + + /** + * @expectedException LogicException + * @expectedExceptionMessageRegExp #^The last parameter of "CallableTestClass\:\:__invoke" for function "foo" must be an array with default value, eg\. "array \$arg \= array\(\)"\.$# + */ + public function testResolveArgumentsWithMissingParameterForArbitraryArgumentsOnObject() + { + $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'foo', 'is_variadic' => true)); + $node->getArguments(new CallableTestClass(), array()); + } +} + +class Twig_Tests_Node_Expression_Call extends Twig_Node_Expression_Call +{ + public function getArguments($callable, $arguments) + { + return parent::getArguments($callable, $arguments); + } +} + +class CallableTestClass +{ + public function __invoke($required) + { + } +} + +function custom_Twig_Tests_Node_Expression_CallTest_function($required) +{ +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ConditionalTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ConditionalTest.php new file mode 100644 index 0000000..a3e8bad --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ConditionalTest.php @@ -0,0 +1,38 @@ +assertEquals($expr1, $node->getNode('expr1')); + $this->assertEquals($expr2, $node->getNode('expr2')); + $this->assertEquals($expr3, $node->getNode('expr3')); + } + + public function getTests() + { + $tests = array(); + + $expr1 = new Twig_Node_Expression_Constant(1, 1); + $expr2 = new Twig_Node_Expression_Constant(2, 1); + $expr3 = new Twig_Node_Expression_Constant(3, 1); + $node = new Twig_Node_Expression_Conditional($expr1, $expr2, $expr3, 1); + $tests[] = array($node, '((1) ? (2) : (3))'); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ConstantTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ConstantTest.php new file mode 100644 index 0000000..2ff9318 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ConstantTest.php @@ -0,0 +1,30 @@ +assertEquals('foo', $node->getAttribute('value')); + } + + public function getTests() + { + $tests = array(); + + $node = new Twig_Node_Expression_Constant('foo', 1); + $tests[] = array($node, '"foo"'); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/FilterTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/FilterTest.php new file mode 100644 index 0000000..773375c --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/FilterTest.php @@ -0,0 +1,154 @@ +assertEquals($expr, $node->getNode('node')); + $this->assertEquals($name, $node->getNode('filter')); + $this->assertEquals($args, $node->getNode('arguments')); + } + + public function getTests() + { + $environment = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $environment->addFilter(new Twig_SimpleFilter('bar', 'bar', array('needs_environment' => true))); + $environment->addFilter(new Twig_SimpleFilter('barbar', 'twig_tests_filter_barbar', array('needs_context' => true, 'is_variadic' => true))); + + $tests = array(); + + $expr = new Twig_Node_Expression_Constant('foo', 1); + $node = $this->createFilter($expr, 'upper'); + $node = $this->createFilter($node, 'number_format', array(new Twig_Node_Expression_Constant(2, 1), new Twig_Node_Expression_Constant('.', 1), new Twig_Node_Expression_Constant(',', 1))); + + if (function_exists('mb_get_info')) { + $tests[] = array($node, 'twig_number_format_filter($this->env, twig_upper_filter($this->env, "foo"), 2, ".", ",")'); + } else { + $tests[] = array($node, 'twig_number_format_filter($this->env, strtoupper("foo"), 2, ".", ",")'); + } + + // named arguments + $date = new Twig_Node_Expression_Constant(0, 1); + $node = $this->createFilter($date, 'date', array( + 'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1), + 'format' => new Twig_Node_Expression_Constant('d/m/Y H:i:s P', 1), + )); + $tests[] = array($node, 'twig_date_format_filter($this->env, 0, "d/m/Y H:i:s P", "America/Chicago")'); + + // skip an optional argument + $date = new Twig_Node_Expression_Constant(0, 1); + $node = $this->createFilter($date, 'date', array( + 'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1), + )); + $tests[] = array($node, 'twig_date_format_filter($this->env, 0, null, "America/Chicago")'); + + // underscores vs camelCase for named arguments + $string = new Twig_Node_Expression_Constant('abc', 1); + $node = $this->createFilter($string, 'reverse', array( + 'preserve_keys' => new Twig_Node_Expression_Constant(true, 1), + )); + $tests[] = array($node, 'twig_reverse_filter($this->env, "abc", true)'); + $node = $this->createFilter($string, 'reverse', array( + 'preserveKeys' => new Twig_Node_Expression_Constant(true, 1), + )); + $tests[] = array($node, 'twig_reverse_filter($this->env, "abc", true)'); + + // filter as an anonymous function + if (PHP_VERSION_ID >= 50300) { + $node = $this->createFilter(new Twig_Node_Expression_Constant('foo', 1), 'anonymous'); + $tests[] = array($node, 'call_user_func_array($this->env->getFilter(\'anonymous\')->getCallable(), array("foo"))'); + } + + // needs environment + $node = $this->createFilter($string, 'bar'); + $tests[] = array($node, 'bar($this->env, "abc")', $environment); + + $node = $this->createFilter($string, 'bar', array(new Twig_Node_Expression_Constant('bar', 1))); + $tests[] = array($node, 'bar($this->env, "abc", "bar")', $environment); + + // arbitrary named arguments + $node = $this->createFilter($string, 'barbar'); + $tests[] = array($node, 'twig_tests_filter_barbar($context, "abc")', $environment); + + $node = $this->createFilter($string, 'barbar', array('foo' => new Twig_Node_Expression_Constant('bar', 1))); + $tests[] = array($node, 'twig_tests_filter_barbar($context, "abc", null, null, array("foo" => "bar"))', $environment); + + $node = $this->createFilter($string, 'barbar', array('arg2' => new Twig_Node_Expression_Constant('bar', 1))); + $tests[] = array($node, 'twig_tests_filter_barbar($context, "abc", null, "bar")', $environment); + + $node = $this->createFilter($string, 'barbar', array( + new Twig_Node_Expression_Constant('1', 1), + new Twig_Node_Expression_Constant('2', 1), + new Twig_Node_Expression_Constant('3', 1), + 'foo' => new Twig_Node_Expression_Constant('bar', 1), + )); + $tests[] = array($node, 'twig_tests_filter_barbar($context, "abc", "1", "2", array(0 => "3", "foo" => "bar"))', $environment); + + return $tests; + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unknown argument "foobar" for filter "date(format, timezone)" at line 1. + */ + public function testCompileWithWrongNamedArgumentName() + { + $date = new Twig_Node_Expression_Constant(0, 1); + $node = $this->createFilter($date, 'date', array( + 'foobar' => new Twig_Node_Expression_Constant('America/Chicago', 1), + )); + + $compiler = $this->getCompiler(); + $compiler->compile($node); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Value for argument "from" is required for filter "replace". + */ + public function testCompileWithMissingNamedArgument() + { + $value = new Twig_Node_Expression_Constant(0, 1); + $node = $this->createFilter($value, 'replace', array( + 'to' => new Twig_Node_Expression_Constant('foo', 1), + )); + + $compiler = $this->getCompiler(); + $compiler->compile($node); + } + + protected function createFilter($node, $name, array $arguments = array()) + { + $name = new Twig_Node_Expression_Constant($name, 1); + $arguments = new Twig_Node($arguments); + + return new Twig_Node_Expression_Filter($node, $name, $arguments, 1); + } + + protected function getEnvironment() + { + if (PHP_VERSION_ID >= 50300) { + return include 'PHP53/FilterInclude.php'; + } + + return parent::getEnvironment(); + } +} + +function twig_tests_filter_barbar($context, $string, $arg1 = null, $arg2 = null, array $args = array()) +{ +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/FunctionTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/FunctionTest.php new file mode 100644 index 0000000..2e82e2f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/FunctionTest.php @@ -0,0 +1,110 @@ +assertEquals($name, $node->getAttribute('name')); + $this->assertEquals($args, $node->getNode('arguments')); + } + + public function getTests() + { + $environment = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $environment->addFunction(new Twig_SimpleFunction('foo', 'foo', array())); + $environment->addFunction(new Twig_SimpleFunction('bar', 'bar', array('needs_environment' => true))); + $environment->addFunction(new Twig_SimpleFunction('foofoo', 'foofoo', array('needs_context' => true))); + $environment->addFunction(new Twig_SimpleFunction('foobar', 'foobar', array('needs_environment' => true, 'needs_context' => true))); + $environment->addFunction(new Twig_SimpleFunction('barbar', 'twig_tests_function_barbar', array('is_variadic' => true))); + + $tests = array(); + + $node = $this->createFunction('foo'); + $tests[] = array($node, 'foo()', $environment); + + $node = $this->createFunction('foo', array(new Twig_Node_Expression_Constant('bar', 1), new Twig_Node_Expression_Constant('foobar', 1))); + $tests[] = array($node, 'foo("bar", "foobar")', $environment); + + $node = $this->createFunction('bar'); + $tests[] = array($node, 'bar($this->env)', $environment); + + $node = $this->createFunction('bar', array(new Twig_Node_Expression_Constant('bar', 1))); + $tests[] = array($node, 'bar($this->env, "bar")', $environment); + + $node = $this->createFunction('foofoo'); + $tests[] = array($node, 'foofoo($context)', $environment); + + $node = $this->createFunction('foofoo', array(new Twig_Node_Expression_Constant('bar', 1))); + $tests[] = array($node, 'foofoo($context, "bar")', $environment); + + $node = $this->createFunction('foobar'); + $tests[] = array($node, 'foobar($this->env, $context)', $environment); + + $node = $this->createFunction('foobar', array(new Twig_Node_Expression_Constant('bar', 1))); + $tests[] = array($node, 'foobar($this->env, $context, "bar")', $environment); + + // named arguments + $node = $this->createFunction('date', array( + 'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1), + 'date' => new Twig_Node_Expression_Constant(0, 1), + )); + $tests[] = array($node, 'twig_date_converter($this->env, 0, "America/Chicago")'); + + // arbitrary named arguments + $node = $this->createFunction('barbar'); + $tests[] = array($node, 'twig_tests_function_barbar()', $environment); + + $node = $this->createFunction('barbar', array('foo' => new Twig_Node_Expression_Constant('bar', 1))); + $tests[] = array($node, 'twig_tests_function_barbar(null, null, array("foo" => "bar"))', $environment); + + $node = $this->createFunction('barbar', array('arg2' => new Twig_Node_Expression_Constant('bar', 1))); + $tests[] = array($node, 'twig_tests_function_barbar(null, "bar")', $environment); + + $node = $this->createFunction('barbar', array( + new Twig_Node_Expression_Constant('1', 1), + new Twig_Node_Expression_Constant('2', 1), + new Twig_Node_Expression_Constant('3', 1), + 'foo' => new Twig_Node_Expression_Constant('bar', 1), + )); + $tests[] = array($node, 'twig_tests_function_barbar("1", "2", array(0 => "3", "foo" => "bar"))', $environment); + + // function as an anonymous function + if (PHP_VERSION_ID >= 50300) { + $node = $this->createFunction('anonymous', array(new Twig_Node_Expression_Constant('foo', 1))); + $tests[] = array($node, 'call_user_func_array($this->env->getFunction(\'anonymous\')->getCallable(), array("foo"))'); + } + + return $tests; + } + + protected function createFunction($name, array $arguments = array()) + { + return new Twig_Node_Expression_Function($name, new Twig_Node($arguments), 1); + } + + protected function getEnvironment() + { + if (PHP_VERSION_ID >= 50300) { + return include 'PHP53/FunctionInclude.php'; + } + + return parent::getEnvironment(); + } +} + +function twig_tests_function_barbar($arg1 = null, $arg2 = null, array $args = array()) +{ +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/GetAttrTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/GetAttrTest.php new file mode 100644 index 0000000..2764478 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/GetAttrTest.php @@ -0,0 +1,50 @@ +addElement(new Twig_Node_Expression_Name('foo', 1)); + $args->addElement(new Twig_Node_Expression_Constant('bar', 1)); + $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_Template::ARRAY_CALL, 1); + + $this->assertEquals($expr, $node->getNode('node')); + $this->assertEquals($attr, $node->getNode('attribute')); + $this->assertEquals($args, $node->getNode('arguments')); + $this->assertEquals(Twig_Template::ARRAY_CALL, $node->getAttribute('type')); + } + + public function getTests() + { + $tests = array(); + + $expr = new Twig_Node_Expression_Name('foo', 1); + $attr = new Twig_Node_Expression_Constant('bar', 1); + $args = new Twig_Node_Expression_Array(array(), 1); + $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_Template::ANY_CALL, 1); + $tests[] = array($node, sprintf('%s%s, "bar", array())', $this->getAttributeGetter(), $this->getVariableGetter('foo', 1))); + + $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_Template::ARRAY_CALL, 1); + $tests[] = array($node, sprintf('%s%s, "bar", array(), "array")', $this->getAttributeGetter(), $this->getVariableGetter('foo', 1))); + + $args = new Twig_Node_Expression_Array(array(), 1); + $args->addElement(new Twig_Node_Expression_Name('foo', 1)); + $args->addElement(new Twig_Node_Expression_Constant('bar', 1)); + $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_Template::METHOD_CALL, 1); + $tests[] = array($node, sprintf('%s%s, "bar", array(0 => %s, 1 => "bar"), "method")', $this->getAttributeGetter(), $this->getVariableGetter('foo', 1), $this->getVariableGetter('foo'))); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/NameTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/NameTest.php new file mode 100644 index 0000000..70721a8 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/NameTest.php @@ -0,0 +1,43 @@ +assertEquals('foo', $node->getAttribute('name')); + } + + public function getTests() + { + $node = new Twig_Node_Expression_Name('foo', 1); + $context = new Twig_Node_Expression_Name('_context', 1); + + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('strict_variables' => true)); + $env1 = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('strict_variables' => false)); + + if (PHP_VERSION_ID >= 70000) { + $output = '($context["foo"] ?? $this->getContext($context, "foo"))'; + } elseif (PHP_VERSION_ID >= 50400) { + $output = '(isset($context["foo"]) ? $context["foo"] : $this->getContext($context, "foo"))'; + } else { + $output = '$this->getContext($context, "foo")'; + } + + return array( + array($node, "// line 1\n".$output, $env), + array($node, $this->getVariableGetter('foo', 1), $env1), + array($context, "// line 1\n\$context"), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/NullCoalesceTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/NullCoalesceTest.php new file mode 100644 index 0000000..a37490b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/NullCoalesceTest.php @@ -0,0 +1,31 @@ += 70000) { + $tests[] = array($node, "((// line 1\n\$context[\"foo\"]) ?? (2))"); + } elseif (PHP_VERSION_ID >= 50400) { + $tests[] = array($node, "(((// line 1\narray_key_exists(\"foo\", \$context) && !(null === (isset(\$context[\"foo\"]) ? \$context[\"foo\"] : null)))) ? ((isset(\$context[\"foo\"]) ? \$context[\"foo\"] : null)) : (2))"); + } else { + $tests[] = array($node, "(((// line 1\narray_key_exists(\"foo\", \$context) && !(null === \$this->getContext(\$context, \"foo\")))) ? (\$this->getContext(\$context, \"foo\")) : (2))"); + } + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/PHP53/FilterInclude.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/PHP53/FilterInclude.php new file mode 100644 index 0000000..b5394bc --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/PHP53/FilterInclude.php @@ -0,0 +1,6 @@ +addFilter(new Twig_SimpleFilter('anonymous', function () {})); + +return $env; diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/PHP53/FunctionInclude.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/PHP53/FunctionInclude.php new file mode 100644 index 0000000..e8f68c7 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/PHP53/FunctionInclude.php @@ -0,0 +1,6 @@ +addFunction(new Twig_SimpleFunction('anonymous', function () {})); + +return $env; diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/PHP53/TestInclude.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/PHP53/TestInclude.php new file mode 100644 index 0000000..9f818bc --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/PHP53/TestInclude.php @@ -0,0 +1,6 @@ +addTest(new Twig_SimpleTest('anonymous', function () {})); + +return $env; diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ParentTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ParentTest.php new file mode 100644 index 0000000..ab2bbe0 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/ParentTest.php @@ -0,0 +1,28 @@ +assertEquals('foo', $node->getAttribute('name')); + } + + public function getTests() + { + $tests = array(); + $tests[] = array(new Twig_Node_Expression_Parent('foo', 1), '$this->renderParentBlock("foo", $context, $blocks)'); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/TestTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/TestTest.php new file mode 100644 index 0000000..a5f96d2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/TestTest.php @@ -0,0 +1,82 @@ +assertEquals($expr, $node->getNode('node')); + $this->assertEquals($args, $node->getNode('arguments')); + $this->assertEquals($name, $node->getAttribute('name')); + } + + public function getTests() + { + $environment = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $environment->addTest(new Twig_SimpleTest('barbar', 'twig_tests_test_barbar', array('is_variadic' => true, 'need_context' => true))); + + $tests = array(); + + $expr = new Twig_Node_Expression_Constant('foo', 1); + $node = new Twig_Node_Expression_Test_Null($expr, 'null', new Twig_Node(array()), 1); + $tests[] = array($node, '(null === "foo")'); + + // test as an anonymous function + if (PHP_VERSION_ID >= 50300) { + $node = $this->createTest(new Twig_Node_Expression_Constant('foo', 1), 'anonymous', array(new Twig_Node_Expression_Constant('foo', 1))); + $tests[] = array($node, 'call_user_func_array($this->env->getTest(\'anonymous\')->getCallable(), array("foo", "foo"))'); + } + + // arbitrary named arguments + $string = new Twig_Node_Expression_Constant('abc', 1); + $node = $this->createTest($string, 'barbar'); + $tests[] = array($node, 'twig_tests_test_barbar("abc")', $environment); + + $node = $this->createTest($string, 'barbar', array('foo' => new Twig_Node_Expression_Constant('bar', 1))); + $tests[] = array($node, 'twig_tests_test_barbar("abc", null, null, array("foo" => "bar"))', $environment); + + $node = $this->createTest($string, 'barbar', array('arg2' => new Twig_Node_Expression_Constant('bar', 1))); + $tests[] = array($node, 'twig_tests_test_barbar("abc", null, "bar")', $environment); + + $node = $this->createTest($string, 'barbar', array( + new Twig_Node_Expression_Constant('1', 1), + new Twig_Node_Expression_Constant('2', 1), + new Twig_Node_Expression_Constant('3', 1), + 'foo' => new Twig_Node_Expression_Constant('bar', 1), + )); + $tests[] = array($node, 'twig_tests_test_barbar("abc", "1", "2", array(0 => "3", "foo" => "bar"))', $environment); + + return $tests; + } + + protected function createTest($node, $name, array $arguments = array()) + { + return new Twig_Node_Expression_Test($node, $name, new Twig_Node($arguments), 1); + } + + protected function getEnvironment() + { + if (PHP_VERSION_ID >= 50300) { + return include 'PHP53/TestInclude.php'; + } + + return parent::getEnvironment(); + } +} + +function twig_tests_test_barbar($string, $arg1 = null, $arg2 = null, array $args = array()) +{ +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Unary/NegTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Unary/NegTest.php new file mode 100644 index 0000000..b633371 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Unary/NegTest.php @@ -0,0 +1,32 @@ +assertEquals($expr, $node->getNode('node')); + } + + public function getTests() + { + $node = new Twig_Node_Expression_Constant(1, 1); + $node = new Twig_Node_Expression_Unary_Neg($node, 1); + + return array( + array($node, '-1'), + array(new Twig_Node_Expression_Unary_Neg($node, 1), '- -1'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Unary/NotTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Unary/NotTest.php new file mode 100644 index 0000000..d7c6f85 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Unary/NotTest.php @@ -0,0 +1,31 @@ +assertEquals($expr, $node->getNode('node')); + } + + public function getTests() + { + $node = new Twig_Node_Expression_Constant(1, 1); + $node = new Twig_Node_Expression_Unary_Not($node, 1); + + return array( + array($node, '!1'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Unary/PosTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Unary/PosTest.php new file mode 100644 index 0000000..057250f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/Expression/Unary/PosTest.php @@ -0,0 +1,31 @@ +assertEquals($expr, $node->getNode('node')); + } + + public function getTests() + { + $node = new Twig_Node_Expression_Constant(1, 1); + $node = new Twig_Node_Expression_Unary_Pos($node, 1); + + return array( + array($node, '+1'), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/ForTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/ForTest.php new file mode 100644 index 0000000..2bf4c7b --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/ForTest.php @@ -0,0 +1,191 @@ +setAttribute('with_loop', false); + + $this->assertEquals($keyTarget, $node->getNode('key_target')); + $this->assertEquals($valueTarget, $node->getNode('value_target')); + $this->assertEquals($seq, $node->getNode('seq')); + $this->assertTrue($node->getAttribute('ifexpr')); + $this->assertEquals('Twig_Node_If', get_class($node->getNode('body'))); + $this->assertEquals($body, $node->getNode('body')->getNode('tests')->getNode(1)->getNode(0)); + $this->assertFalse($node->hasNode('else')); + + $else = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1); + $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, 1); + $node->setAttribute('with_loop', false); + $this->assertEquals($else, $node->getNode('else')); + } + + public function getTests() + { + $tests = array(); + + $keyTarget = new Twig_Node_Expression_AssignName('key', 1); + $valueTarget = new Twig_Node_Expression_AssignName('item', 1); + $seq = new Twig_Node_Expression_Name('items', 1); + $ifexpr = null; + $body = new Twig_Node(array(new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1)), array(), 1); + $else = null; + $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, 1); + $node->setAttribute('with_loop', false); + + $tests[] = array($node, <<getVariableGetter('items')}); +foreach (\$context['_seq'] as \$context["key"] => \$context["item"]) { + echo {$this->getVariableGetter('foo')}; +} +\$_parent = \$context['_parent']; +unset(\$context['_seq'], \$context['_iterated'], \$context['key'], \$context['item'], \$context['_parent'], \$context['loop']); +\$context = array_intersect_key(\$context, \$_parent) + \$_parent; +EOF + ); + + $keyTarget = new Twig_Node_Expression_AssignName('k', 1); + $valueTarget = new Twig_Node_Expression_AssignName('v', 1); + $seq = new Twig_Node_Expression_Name('values', 1); + $ifexpr = null; + $body = new Twig_Node(array(new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1)), array(), 1); + $else = null; + $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, 1); + $node->setAttribute('with_loop', true); + + $tests[] = array($node, <<getVariableGetter('values')}); +\$context['loop'] = array( + 'parent' => \$context['_parent'], + 'index0' => 0, + 'index' => 1, + 'first' => true, +); +if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) { + \$length = count(\$context['_seq']); + \$context['loop']['revindex0'] = \$length - 1; + \$context['loop']['revindex'] = \$length; + \$context['loop']['length'] = \$length; + \$context['loop']['last'] = 1 === \$length; +} +foreach (\$context['_seq'] as \$context["k"] => \$context["v"]) { + echo {$this->getVariableGetter('foo')}; + ++\$context['loop']['index0']; + ++\$context['loop']['index']; + \$context['loop']['first'] = false; + if (isset(\$context['loop']['length'])) { + --\$context['loop']['revindex0']; + --\$context['loop']['revindex']; + \$context['loop']['last'] = 0 === \$context['loop']['revindex0']; + } +} +\$_parent = \$context['_parent']; +unset(\$context['_seq'], \$context['_iterated'], \$context['k'], \$context['v'], \$context['_parent'], \$context['loop']); +\$context = array_intersect_key(\$context, \$_parent) + \$_parent; +EOF + ); + + $keyTarget = new Twig_Node_Expression_AssignName('k', 1); + $valueTarget = new Twig_Node_Expression_AssignName('v', 1); + $seq = new Twig_Node_Expression_Name('values', 1); + $ifexpr = new Twig_Node_Expression_Constant(true, 1); + $body = new Twig_Node(array(new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1)), array(), 1); + $else = null; + $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, 1); + $node->setAttribute('with_loop', true); + + $tests[] = array($node, <<getVariableGetter('values')}); +\$context['loop'] = array( + 'parent' => \$context['_parent'], + 'index0' => 0, + 'index' => 1, + 'first' => true, +); +foreach (\$context['_seq'] as \$context["k"] => \$context["v"]) { + if (true) { + echo {$this->getVariableGetter('foo')}; + ++\$context['loop']['index0']; + ++\$context['loop']['index']; + \$context['loop']['first'] = false; + } +} +\$_parent = \$context['_parent']; +unset(\$context['_seq'], \$context['_iterated'], \$context['k'], \$context['v'], \$context['_parent'], \$context['loop']); +\$context = array_intersect_key(\$context, \$_parent) + \$_parent; +EOF + ); + + $keyTarget = new Twig_Node_Expression_AssignName('k', 1); + $valueTarget = new Twig_Node_Expression_AssignName('v', 1); + $seq = new Twig_Node_Expression_Name('values', 1); + $ifexpr = null; + $body = new Twig_Node(array(new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1)), array(), 1); + $else = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1); + $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, 1); + $node->setAttribute('with_loop', true); + + $tests[] = array($node, <<getVariableGetter('values')}); +\$context['_iterated'] = false; +\$context['loop'] = array( + 'parent' => \$context['_parent'], + 'index0' => 0, + 'index' => 1, + 'first' => true, +); +if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) { + \$length = count(\$context['_seq']); + \$context['loop']['revindex0'] = \$length - 1; + \$context['loop']['revindex'] = \$length; + \$context['loop']['length'] = \$length; + \$context['loop']['last'] = 1 === \$length; +} +foreach (\$context['_seq'] as \$context["k"] => \$context["v"]) { + echo {$this->getVariableGetter('foo')}; + \$context['_iterated'] = true; + ++\$context['loop']['index0']; + ++\$context['loop']['index']; + \$context['loop']['first'] = false; + if (isset(\$context['loop']['length'])) { + --\$context['loop']['revindex0']; + --\$context['loop']['revindex']; + \$context['loop']['last'] = 0 === \$context['loop']['revindex0']; + } +} +if (!\$context['_iterated']) { + echo {$this->getVariableGetter('foo')}; +} +\$_parent = \$context['_parent']; +unset(\$context['_seq'], \$context['_iterated'], \$context['k'], \$context['v'], \$context['_parent'], \$context['loop']); +\$context = array_intersect_key(\$context, \$_parent) + \$_parent; +EOF + ); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/IfTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/IfTest.php new file mode 100644 index 0000000..4ab0e4c --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/IfTest.php @@ -0,0 +1,88 @@ +assertEquals($t, $node->getNode('tests')); + $this->assertFalse($node->hasNode('else')); + + $else = new Twig_Node_Print(new Twig_Node_Expression_Name('bar', 1), 1); + $node = new Twig_Node_If($t, $else, 1); + $this->assertEquals($else, $node->getNode('else')); + } + + public function getTests() + { + $tests = array(); + + $t = new Twig_Node(array( + new Twig_Node_Expression_Constant(true, 1), + new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1), + ), array(), 1); + $else = null; + $node = new Twig_Node_If($t, $else, 1); + + $tests[] = array($node, <<getVariableGetter('foo')}; +} +EOF + ); + + $t = new Twig_Node(array( + new Twig_Node_Expression_Constant(true, 1), + new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1), + new Twig_Node_Expression_Constant(false, 1), + new Twig_Node_Print(new Twig_Node_Expression_Name('bar', 1), 1), + ), array(), 1); + $else = null; + $node = new Twig_Node_If($t, $else, 1); + + $tests[] = array($node, <<getVariableGetter('foo')}; +} elseif (false) { + echo {$this->getVariableGetter('bar')}; +} +EOF + ); + + $t = new Twig_Node(array( + new Twig_Node_Expression_Constant(true, 1), + new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1), + ), array(), 1); + $else = new Twig_Node_Print(new Twig_Node_Expression_Name('bar', 1), 1); + $node = new Twig_Node_If($t, $else, 1); + + $tests[] = array($node, <<getVariableGetter('foo')}; +} else { + echo {$this->getVariableGetter('bar')}; +} +EOF + ); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/ImportTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/ImportTest.php new file mode 100644 index 0000000..36525b2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/ImportTest.php @@ -0,0 +1,40 @@ +assertEquals($macro, $node->getNode('expr')); + $this->assertEquals($var, $node->getNode('var')); + } + + public function getTests() + { + $tests = array(); + + $macro = new Twig_Node_Expression_Constant('foo.twig', 1); + $var = new Twig_Node_Expression_AssignName('macro', 1); + $node = new Twig_Node_Import($macro, $var, 1); + + $tests[] = array($node, <<loadTemplate("foo.twig", null, 1); +EOF + ); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/IncludeTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/IncludeTest.php new file mode 100644 index 0000000..d801f33 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/IncludeTest.php @@ -0,0 +1,83 @@ +assertFalse($node->hasNode('variables')); + $this->assertEquals($expr, $node->getNode('expr')); + $this->assertFalse($node->getAttribute('only')); + + $vars = new Twig_Node_Expression_Array(array(new Twig_Node_Expression_Constant('foo', 1), new Twig_Node_Expression_Constant(true, 1)), 1); + $node = new Twig_Node_Include($expr, $vars, true, false, 1); + $this->assertEquals($vars, $node->getNode('variables')); + $this->assertTrue($node->getAttribute('only')); + } + + public function getTests() + { + $tests = array(); + + $expr = new Twig_Node_Expression_Constant('foo.twig', 1); + $node = new Twig_Node_Include($expr, null, false, false, 1); + $tests[] = array($node, <<loadTemplate("foo.twig", null, 1)->display(\$context); +EOF + ); + + $expr = new Twig_Node_Expression_Conditional( + new Twig_Node_Expression_Constant(true, 1), + new Twig_Node_Expression_Constant('foo', 1), + new Twig_Node_Expression_Constant('foo', 1), + 0 + ); + $node = new Twig_Node_Include($expr, null, false, false, 1); + $tests[] = array($node, <<loadTemplate(((true) ? ("foo") : ("foo")), null, 1)->display(\$context); +EOF + ); + + $expr = new Twig_Node_Expression_Constant('foo.twig', 1); + $vars = new Twig_Node_Expression_Array(array(new Twig_Node_Expression_Constant('foo', 1), new Twig_Node_Expression_Constant(true, 1)), 1); + $node = new Twig_Node_Include($expr, $vars, false, false, 1); + $tests[] = array($node, <<loadTemplate("foo.twig", null, 1)->display(array_merge(\$context, array("foo" => true))); +EOF + ); + + $node = new Twig_Node_Include($expr, $vars, true, false, 1); + $tests[] = array($node, <<loadTemplate("foo.twig", null, 1)->display(array("foo" => true)); +EOF + ); + + $node = new Twig_Node_Include($expr, $vars, true, true, 1); + $tests[] = array($node, <<loadTemplate("foo.twig", null, 1)->display(array("foo" => true)); +} catch (Twig_Error_Loader \$e) { + // ignore missing template +} +EOF + ); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/MacroTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/MacroTest.php new file mode 100644 index 0000000..c7edfa2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/MacroTest.php @@ -0,0 +1,74 @@ +assertEquals($body, $node->getNode('body')); + $this->assertEquals($arguments, $node->getNode('arguments')); + $this->assertEquals('foo', $node->getAttribute('name')); + } + + public function getTests() + { + $body = new Twig_Node_Text('foo', 1); + $arguments = new Twig_Node(array( + 'foo' => new Twig_Node_Expression_Constant(null, 1), + 'bar' => new Twig_Node_Expression_Constant('Foo', 1), + ), array(), 1); + $node = new Twig_Node_Macro('foo', $body, $arguments, 1); + + if (PHP_VERSION_ID >= 50600) { + $declaration = ', ...$__varargs__'; + $varargs = '$__varargs__'; + } else { + $declaration = ''; + $varargs = 'func_num_args() > 2 ? array_slice(func_get_args(), 2) : array()'; + } + + return array( + array($node, <<env->mergeGlobals(array( + "foo" => \$__foo__, + "bar" => \$__bar__, + "varargs" => $varargs, + )); + + \$blocks = array(); + + ob_start(); + try { + echo "foo"; + } catch (Exception \$e) { + ob_end_clean(); + + throw \$e; + } catch (Throwable \$e) { + ob_end_clean(); + + throw \$e; + } + + return ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset()); +} +EOF + ), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/ModuleTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/ModuleTest.php new file mode 100644 index 0000000..54a8989 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/ModuleTest.php @@ -0,0 +1,223 @@ +assertEquals($body, $node->getNode('body')); + $this->assertEquals($blocks, $node->getNode('blocks')); + $this->assertEquals($macros, $node->getNode('macros')); + $this->assertEquals($parent, $node->getNode('parent')); + $this->assertEquals($source->getName(), $node->getTemplateName()); + } + + public function getTests() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + + $tests = array(); + + $body = new Twig_Node_Text('foo', 1); + $extends = null; + $blocks = new Twig_Node(); + $macros = new Twig_Node(); + $traits = new Twig_Node(); + $source = new Twig_Source('{{ foo }}', 'foo.twig'); + + $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $source); + $tests[] = array($node, <<parent = false; + + \$this->blocks = array( + ); + } + + protected function doDisplay(array \$context, array \$blocks = array()) + { + // line 1 + echo "foo"; + } + + public function getTemplateName() + { + return "foo.twig"; + } + + public function getDebugInfo() + { + return array ( 19 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return \$this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Twig_Source("", "foo.twig", ""); + } +} +EOF + , $twig, true); + + $import = new Twig_Node_Import(new Twig_Node_Expression_Constant('foo.twig', 1), new Twig_Node_Expression_AssignName('macro', 1), 2); + + $body = new Twig_Node(array($import)); + $extends = new Twig_Node_Expression_Constant('layout.twig', 1); + + $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $source); + $tests[] = array($node, <<parent = \$this->loadTemplate("layout.twig", "foo.twig", 1); + \$this->blocks = array( + ); + } + + protected function doGetParent(array \$context) + { + return "layout.twig"; + } + + protected function doDisplay(array \$context, array \$blocks = array()) + { + // line 2 + \$context["macro"] = \$this->loadTemplate("foo.twig", "foo.twig", 2); + // line 1 + \$this->parent->display(\$context, array_merge(\$this->blocks, \$blocks)); + } + + public function getTemplateName() + { + return "foo.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 26 => 1, 24 => 2, 11 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return \$this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Twig_Source("", "foo.twig", ""); + } +} +EOF + , $twig, true); + + $set = new Twig_Node_Set(false, new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 4))), new Twig_Node(array(new Twig_Node_Expression_Constant('foo', 4))), 4); + $body = new Twig_Node(array($set)); + $extends = new Twig_Node_Expression_Conditional( + new Twig_Node_Expression_Constant(true, 2), + new Twig_Node_Expression_Constant('foo', 2), + new Twig_Node_Expression_Constant('foo', 2), + 2 + ); + + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('debug' => true)); + $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $source); + $tests[] = array($node, <<loadTemplate(((true) ? ("foo") : ("foo")), "foo.twig", 2); + } + + protected function doDisplay(array \$context, array \$blocks = array()) + { + // line 4 + \$context["foo"] = "foo"; + // line 2 + \$this->getParent(\$context)->display(\$context, array_merge(\$this->blocks, \$blocks)); + } + + public function getTemplateName() + { + return "foo.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 17 => 2, 15 => 4, 9 => 2,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return \$this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Twig_Source("{{ foo }}", "foo.twig", ""); + } +} +EOF + , $twig, true); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/PrintTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/PrintTest.php new file mode 100644 index 0000000..4e0990f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/PrintTest.php @@ -0,0 +1,29 @@ +assertEquals($expr, $node->getNode('expr')); + } + + public function getTests() + { + $tests = array(); + $tests[] = array(new Twig_Node_Print(new Twig_Node_Expression_Constant('foo', 1), 1), "// line 1\necho \"foo\";"); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/SandboxTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/SandboxTest.php new file mode 100644 index 0000000..56f4877 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/SandboxTest.php @@ -0,0 +1,44 @@ +assertEquals($body, $node->getNode('body')); + } + + public function getTests() + { + $tests = array(); + + $body = new Twig_Node_Text('foo', 1); + $node = new Twig_Node_Sandbox($body, 1); + + $tests[] = array($node, <<env->getExtension('Twig_Extension_Sandbox'); +if (!\$alreadySandboxed = \$sandbox->isSandboxed()) { + \$sandbox->enableSandbox(); +} +echo "foo"; +if (!\$alreadySandboxed) { + \$sandbox->disableSandbox(); +} +EOF + ); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/SandboxedPrintTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/SandboxedPrintTest.php new file mode 100644 index 0000000..8bc8a75 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/SandboxedPrintTest.php @@ -0,0 +1,33 @@ +assertEquals($expr, $node->getNode('expr')); + } + + public function getTests() + { + $tests = array(); + + $tests[] = array(new Twig_Node_SandboxedPrint(new Twig_Node_Expression_Constant('foo', 1), 1), <<env->getExtension('Twig_Extension_Sandbox')->ensureToStringAllowed("foo"); +EOF + ); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/SetTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/SetTest.php new file mode 100644 index 0000000..62ad280 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/SetTest.php @@ -0,0 +1,69 @@ +assertEquals($names, $node->getNode('names')); + $this->assertEquals($values, $node->getNode('values')); + $this->assertFalse($node->getAttribute('capture')); + } + + public function getTests() + { + $tests = array(); + + $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 1)), array(), 1); + $values = new Twig_Node(array(new Twig_Node_Expression_Constant('foo', 1)), array(), 1); + $node = new Twig_Node_Set(false, $names, $values, 1); + $tests[] = array($node, <<env->getCharset()); +EOF + ); + + $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 1)), array(), 1); + $values = new Twig_Node_Text('foo', 1); + $node = new Twig_Node_Set(true, $names, $values, 1); + $tests[] = array($node, <<env->getCharset()); +EOF + ); + + $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 1), new Twig_Node_Expression_AssignName('bar', 1)), array(), 1); + $values = new Twig_Node(array(new Twig_Node_Expression_Constant('foo', 1), new Twig_Node_Expression_Name('bar', 1)), array(), 1); + $node = new Twig_Node_Set(false, $names, $values, 1); + $tests[] = array($node, <<getVariableGetter('bar')}); +EOF + ); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/SpacelessTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/SpacelessTest.php new file mode 100644 index 0000000..222ca09 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/SpacelessTest.php @@ -0,0 +1,37 @@ +
    foo
    ', 1))); + $node = new Twig_Node_Spaceless($body, 1); + + $this->assertEquals($body, $node->getNode('body')); + } + + public function getTests() + { + $body = new Twig_Node(array(new Twig_Node_Text('
    foo
    ', 1))); + $node = new Twig_Node_Spaceless($body, 1); + + return array( + array($node, <<
    foo
    "; +echo trim(preg_replace('/>\s+<', ob_get_clean())); +EOF + ), + ); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Node/TextTest.php b/src/vendor/twig/twig/test/Twig/Tests/Node/TextTest.php new file mode 100644 index 0000000..ceaf67f --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Node/TextTest.php @@ -0,0 +1,28 @@ +assertEquals('foo', $node->getAttribute('data')); + } + + public function getTests() + { + $tests = array(); + $tests[] = array(new Twig_Node_Text('foo', 1), "// line 1\necho \"foo\";"); + + return $tests; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/NodeVisitor/OptimizerTest.php b/src/vendor/twig/twig/test/Twig/Tests/NodeVisitor/OptimizerTest.php new file mode 100644 index 0000000..95d9509 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/NodeVisitor/OptimizerTest.php @@ -0,0 +1,124 @@ +getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + + $stream = $env->parse($env->tokenize(new Twig_Source('{{ block("foo") }}', 'index'))); + + $node = $stream->getNode('body')->getNode(0); + + $this->assertEquals('Twig_Node_Expression_BlockReference', get_class($node)); + $this->assertTrue($node->getAttribute('output')); + } + + public function testRenderParentBlockOptimizer() + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + + $stream = $env->parse($env->tokenize(new Twig_Source('{% extends "foo" %}{% block content %}{{ parent() }}{% endblock %}', 'index'))); + + $node = $stream->getNode('blocks')->getNode('content')->getNode(0)->getNode('body'); + + $this->assertEquals('Twig_Node_Expression_Parent', get_class($node)); + $this->assertTrue($node->getAttribute('output')); + } + + public function testRenderVariableBlockOptimizer() + { + if (PHP_VERSION_ID >= 50400) { + return; + } + + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false)); + $stream = $env->parse($env->tokenize(new Twig_Source('{{ block(name|lower) }}', 'index'))); + + $node = $stream->getNode('body')->getNode(0)->getNode(1); + + $this->assertEquals('Twig_Node_Expression_BlockReference', get_class($node)); + $this->assertTrue($node->getAttribute('output')); + } + + /** + * @dataProvider getTestsForForOptimizer + */ + public function testForOptimizer($template, $expected) + { + $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false)); + + $stream = $env->parse($env->tokenize(new Twig_Source($template, 'index'))); + + foreach ($expected as $target => $withLoop) { + $this->assertTrue($this->checkForConfiguration($stream, $target, $withLoop), sprintf('variable %s is %soptimized', $target, $withLoop ? 'not ' : '')); + } + } + + public function getTestsForForOptimizer() + { + return array( + array('{% for i in foo %}{% endfor %}', array('i' => false)), + + array('{% for i in foo %}{{ loop.index }}{% endfor %}', array('i' => true)), + + array('{% for i in foo %}{% for j in foo %}{% endfor %}{% endfor %}', array('i' => false, 'j' => false)), + + array('{% for i in foo %}{% include "foo" %}{% endfor %}', array('i' => true)), + + array('{% for i in foo %}{% include "foo" only %}{% endfor %}', array('i' => false)), + + array('{% for i in foo %}{% include "foo" with { "foo": "bar" } only %}{% endfor %}', array('i' => false)), + + array('{% for i in foo %}{% include "foo" with { "foo": loop.index } only %}{% endfor %}', array('i' => true)), + + array('{% for i in foo %}{% for j in foo %}{{ loop.index }}{% endfor %}{% endfor %}', array('i' => false, 'j' => true)), + + array('{% for i in foo %}{% for j in foo %}{{ loop.parent.loop.index }}{% endfor %}{% endfor %}', array('i' => true, 'j' => true)), + + array('{% for i in foo %}{% set l = loop %}{% for j in foo %}{{ l.index }}{% endfor %}{% endfor %}', array('i' => true, 'j' => false)), + + array('{% for i in foo %}{% for j in foo %}{{ foo.parent.loop.index }}{% endfor %}{% endfor %}', array('i' => false, 'j' => false)), + + array('{% for i in foo %}{% for j in foo %}{{ loop["parent"].loop.index }}{% endfor %}{% endfor %}', array('i' => true, 'j' => true)), + + array('{% for i in foo %}{{ include("foo") }}{% endfor %}', array('i' => true)), + + array('{% for i in foo %}{{ include("foo", with_context = false) }}{% endfor %}', array('i' => false)), + + array('{% for i in foo %}{{ include("foo", with_context = true) }}{% endfor %}', array('i' => true)), + + array('{% for i in foo %}{{ include("foo", { "foo": "bar" }, with_context = false) }}{% endfor %}', array('i' => false)), + + array('{% for i in foo %}{{ include("foo", { "foo": loop.index }, with_context = false) }}{% endfor %}', array('i' => true)), + ); + } + + public function checkForConfiguration(Twig_NodeInterface $node = null, $target, $withLoop) + { + if (null === $node) { + return; + } + + foreach ($node as $n) { + if ($n instanceof Twig_Node_For) { + if ($target === $n->getNode('value_target')->getAttribute('name')) { + return $withLoop == $n->getAttribute('with_loop'); + } + } + + $ret = $this->checkForConfiguration($n, $target, $withLoop); + if (null !== $ret) { + return $ret; + } + } + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/ParserTest.php b/src/vendor/twig/twig/test/Twig/Tests/ParserTest.php new file mode 100644 index 0000000..3d0d769 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/ParserTest.php @@ -0,0 +1,196 @@ +getParser(); + $parser->setMacro('parent', $this->getMockBuilder('Twig_Node_Macro')->disableOriginalConstructor()->getMock()); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unknown "foo" tag. Did you mean "for" at line 1? + */ + public function testUnknownTag() + { + $stream = new Twig_TokenStream(array( + new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', 1), + new Twig_Token(Twig_Token::NAME_TYPE, 'foo', 1), + new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', 1), + new Twig_Token(Twig_Token::EOF_TYPE, '', 1), + )); + $parser = new Twig_Parser(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $parser->parse($stream); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage Unknown "foobar" tag at line 1. + */ + public function testUnknownTagWithoutSuggestions() + { + $stream = new Twig_TokenStream(array( + new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', 1), + new Twig_Token(Twig_Token::NAME_TYPE, 'foobar', 1), + new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', 1), + new Twig_Token(Twig_Token::EOF_TYPE, '', 1), + )); + $parser = new Twig_Parser(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock())); + $parser->parse($stream); + } + + /** + * @dataProvider getFilterBodyNodesData + */ + public function testFilterBodyNodes($input, $expected) + { + $parser = $this->getParser(); + + $this->assertEquals($expected, $parser->filterBodyNodes($input)); + } + + public function getFilterBodyNodesData() + { + return array( + array( + new Twig_Node(array(new Twig_Node_Text(' ', 1))), + new Twig_Node(array()), + ), + array( + $input = new Twig_Node(array(new Twig_Node_Set(false, new Twig_Node(), new Twig_Node(), 1))), + $input, + ), + array( + $input = new Twig_Node(array(new Twig_Node_Set(true, new Twig_Node(), new Twig_Node(array(new Twig_Node(array(new Twig_Node_Text('foo', 1))))), 1))), + $input, + ), + ); + } + + /** + * @dataProvider getFilterBodyNodesDataThrowsException + * @expectedException Twig_Error_Syntax + */ + public function testFilterBodyNodesThrowsException($input) + { + $parser = $this->getParser(); + + $parser->filterBodyNodes($input); + } + + public function getFilterBodyNodesDataThrowsException() + { + return array( + array(new Twig_Node_Text('foo', 1)), + array(new Twig_Node(array(new Twig_Node(array(new Twig_Node_Text('foo', 1)))))), + ); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedExceptionMessage A template that extends another one cannot start with a byte order mark (BOM); it must be removed at line 1 + */ + public function testFilterBodyNodesWithBOM() + { + $parser = $this->getParser(); + $parser->filterBodyNodes(new Twig_Node_Text(chr(0xEF).chr(0xBB).chr(0xBF), 1)); + } + + public function testParseIsReentrant() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array( + 'autoescape' => false, + 'optimizations' => 0, + )); + $twig->addTokenParser(new TestTokenParser()); + + $parser = new Twig_Parser($twig); + + $parser->parse(new Twig_TokenStream(array( + new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', 1), + new Twig_Token(Twig_Token::NAME_TYPE, 'test', 1), + new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', 1), + new Twig_Token(Twig_Token::VAR_START_TYPE, '', 1), + new Twig_Token(Twig_Token::NAME_TYPE, 'foo', 1), + new Twig_Token(Twig_Token::VAR_END_TYPE, '', 1), + new Twig_Token(Twig_Token::EOF_TYPE, '', 1), + ))); + + $this->assertNull($parser->getParent()); + } + + // The getVarName() must not depend on the template loaders, + // If this test does not throw any exception, that's good. + // see https://github.com/symfony/symfony/issues/4218 + public function testGetVarName() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array( + 'autoescape' => false, + 'optimizations' => 0, + )); + + $twig->parse($twig->tokenize(new Twig_Source(<<getMockBuilder('Twig_LoaderInterface')->getMock())); + $parser->setParent(new Twig_Node()); + $parser->stream = new Twig_TokenStream(array()); + + return $parser; + } +} + +class TestParser extends Twig_Parser +{ + public $stream; + + public function filterBodyNodes(Twig_NodeInterface $node) + { + return parent::filterBodyNodes($node); + } +} + +class TestTokenParser extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + // simulate the parsing of another template right in the middle of the parsing of the current template + $this->parser->parse(new Twig_TokenStream(array( + new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', 1), + new Twig_Token(Twig_Token::NAME_TYPE, 'extends', 1), + new Twig_Token(Twig_Token::STRING_TYPE, 'base', 1), + new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', 1), + new Twig_Token(Twig_Token::EOF_TYPE, '', 1), + ))); + + $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node(array()); + } + + public function getTag() + { + return 'test'; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/AbstractTest.php b/src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/AbstractTest.php new file mode 100644 index 0000000..da97f47 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/AbstractTest.php @@ -0,0 +1,101 @@ +getMockBuilder('Twig_Profiler_Profile')->disableOriginalConstructor()->getMock(); + + $profile->expects($this->any())->method('isRoot')->will($this->returnValue(true)); + $profile->expects($this->any())->method('getName')->will($this->returnValue('main')); + $profile->expects($this->any())->method('getDuration')->will($this->returnValue(1)); + $profile->expects($this->any())->method('getMemoryUsage')->will($this->returnValue(0)); + $profile->expects($this->any())->method('getPeakMemoryUsage')->will($this->returnValue(0)); + + $subProfiles = array( + $this->getIndexProfile( + array( + $this->getEmbeddedBlockProfile(), + $this->getEmbeddedTemplateProfile( + array( + $this->getIncludedTemplateProfile(), + ) + ), + $this->getMacroProfile(), + $this->getEmbeddedTemplateProfile( + array( + $this->getIncludedTemplateProfile(), + ) + ), + ) + ), + ); + + $profile->expects($this->any())->method('getProfiles')->will($this->returnValue($subProfiles)); + $profile->expects($this->any())->method('getIterator')->will($this->returnValue(new ArrayIterator($subProfiles))); + + return $profile; + } + + private function getIndexProfile(array $subProfiles = array()) + { + return $this->generateProfile('main', 1, true, 'template', 'index.twig', $subProfiles); + } + + private function getEmbeddedBlockProfile(array $subProfiles = array()) + { + return $this->generateProfile('body', 0.0001, false, 'block', 'embedded.twig', $subProfiles); + } + + private function getEmbeddedTemplateProfile(array $subProfiles = array()) + { + return $this->generateProfile('main', 0.0001, true, 'template', 'embedded.twig', $subProfiles); + } + + private function getIncludedTemplateProfile(array $subProfiles = array()) + { + return $this->generateProfile('main', 0.0001, true, 'template', 'included.twig', $subProfiles); + } + + private function getMacroProfile(array $subProfiles = array()) + { + return $this->generateProfile('foo', 0.0001, false, 'macro', 'index.twig', $subProfiles); + } + + /** + * @param string $name + * @param float $duration + * @param bool $isTemplate + * @param string $type + * @param string $templateName + * @param array $subProfiles + * + * @return Twig_Profiler_Profile + */ + private function generateProfile($name, $duration, $isTemplate, $type, $templateName, array $subProfiles = array()) + { + $profile = $this->getMockBuilder('Twig_Profiler_Profile')->disableOriginalConstructor()->getMock(); + + $profile->expects($this->any())->method('isRoot')->will($this->returnValue(false)); + $profile->expects($this->any())->method('getName')->will($this->returnValue($name)); + $profile->expects($this->any())->method('getDuration')->will($this->returnValue($duration)); + $profile->expects($this->any())->method('getMemoryUsage')->will($this->returnValue(0)); + $profile->expects($this->any())->method('getPeakMemoryUsage')->will($this->returnValue(0)); + $profile->expects($this->any())->method('isTemplate')->will($this->returnValue($isTemplate)); + $profile->expects($this->any())->method('getType')->will($this->returnValue($type)); + $profile->expects($this->any())->method('getTemplate')->will($this->returnValue($templateName)); + $profile->expects($this->any())->method('getProfiles')->will($this->returnValue($subProfiles)); + $profile->expects($this->any())->method('getIterator')->will($this->returnValue(new ArrayIterator($subProfiles))); + + return $profile; + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/BlackfireTest.php b/src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/BlackfireTest.php new file mode 100644 index 0000000..1a1b9d2 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/BlackfireTest.php @@ -0,0 +1,32 @@ +assertStringMatchesFormat(<<index.twig//1 %d %d %d +index.twig==>embedded.twig::block(body)//1 %d %d 0 +index.twig==>embedded.twig//2 %d %d %d +embedded.twig==>included.twig//2 %d %d %d +index.twig==>index.twig::macro(foo)//1 %d %d %d +EOF + , $dumper->dump($this->getProfile())); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/HtmlTest.php b/src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/HtmlTest.php new file mode 100644 index 0000000..66a68c4 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/HtmlTest.php @@ -0,0 +1,30 @@ +assertStringMatchesFormat(<<main %d.%dms/%d% +└ index.twig %d.%dms/%d% + └ embedded.twig::block(body) + └ embedded.twig + │ └ included.twig + └ index.twig::macro(foo) + └ embedded.twig + └ included.twig + +EOF + , $dumper->dump($this->getProfile())); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/TextTest.php b/src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/TextTest.php new file mode 100644 index 0000000..e2ea165 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/TextTest.php @@ -0,0 +1,30 @@ +assertStringMatchesFormat(<<dump($this->getProfile())); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/Profiler/ProfileTest.php b/src/vendor/twig/twig/test/Twig/Tests/Profiler/ProfileTest.php new file mode 100644 index 0000000..f786f06 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/Profiler/ProfileTest.php @@ -0,0 +1,100 @@ +assertEquals('template', $profile->getTemplate()); + $this->assertEquals('type', $profile->getType()); + $this->assertEquals('name', $profile->getName()); + } + + public function testIsRoot() + { + $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::ROOT); + $this->assertTrue($profile->isRoot()); + + $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::TEMPLATE); + $this->assertFalse($profile->isRoot()); + } + + public function testIsTemplate() + { + $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::TEMPLATE); + $this->assertTrue($profile->isTemplate()); + + $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::ROOT); + $this->assertFalse($profile->isTemplate()); + } + + public function testIsBlock() + { + $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::BLOCK); + $this->assertTrue($profile->isBlock()); + + $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::ROOT); + $this->assertFalse($profile->isBlock()); + } + + public function testIsMacro() + { + $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::MACRO); + $this->assertTrue($profile->isMacro()); + + $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::ROOT); + $this->assertFalse($profile->isMacro()); + } + + public function testGetAddProfile() + { + $profile = new Twig_Profiler_Profile(); + $profile->addProfile($a = new Twig_Profiler_Profile()); + $profile->addProfile($b = new Twig_Profiler_Profile()); + + $this->assertSame(array($a, $b), $profile->getProfiles()); + $this->assertSame(array($a, $b), iterator_to_array($profile)); + } + + public function testGetDuration() + { + $profile = new Twig_Profiler_Profile(); + usleep(1); + $profile->leave(); + + $this->assertTrue($profile->getDuration() > 0, sprintf('Expected duration > 0, got: %f', $profile->getDuration())); + } + + public function testSerialize() + { + $profile = new Twig_Profiler_Profile('template', 'type', 'name'); + $profile1 = new Twig_Profiler_Profile('template1', 'type1', 'name1'); + $profile->addProfile($profile1); + $profile->leave(); + $profile1->leave(); + + $profile2 = unserialize(serialize($profile)); + $profiles = $profile->getProfiles(); + $this->assertCount(1, $profiles); + $profile3 = $profiles[0]; + + $this->assertEquals($profile->getTemplate(), $profile2->getTemplate()); + $this->assertEquals($profile->getType(), $profile2->getType()); + $this->assertEquals($profile->getName(), $profile2->getName()); + $this->assertEquals($profile->getDuration(), $profile2->getDuration()); + + $this->assertEquals($profile1->getTemplate(), $profile3->getTemplate()); + $this->assertEquals($profile1->getType(), $profile3->getType()); + $this->assertEquals($profile1->getName(), $profile3->getName()); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/TemplateTest.php b/src/vendor/twig/twig/test/Twig/Tests/TemplateTest.php new file mode 100644 index 0000000..c31a9aa --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/TemplateTest.php @@ -0,0 +1,796 @@ +getMockForAbstractClass('Twig_Template', array(), '', false); + $template->displayBlock('foo', array(), array('foo' => array(new stdClass(), 'foo'))); + } + + /** + * @dataProvider getAttributeExceptions + */ + public function testGetAttributeExceptions($template, $message, $useExt) + { + $name = 'index_'.($useExt ? 1 : 0); + $templates = array( + $name => $template.$useExt, // appending $useExt makes the template content unique + ); + + $env = new Twig_Environment(new Twig_Loader_Array($templates), array('strict_variables' => true)); + if (!$useExt) { + $env->addNodeVisitor(new CExtDisablingNodeVisitor()); + } + $template = $env->loadTemplate($name); + + $context = array( + 'string' => 'foo', + 'null' => null, + 'empty_array' => array(), + 'array' => array('foo' => 'foo'), + 'array_access' => new Twig_TemplateArrayAccessObject(), + 'magic_exception' => new Twig_TemplateMagicPropertyObjectWithException(), + 'object' => new stdClass(), + ); + + try { + $template->render($context); + $this->fail('Accessing an invalid attribute should throw an exception.'); + } catch (Twig_Error_Runtime $e) { + $this->assertSame(sprintf($message, $name), $e->getMessage()); + } + } + + public function getAttributeExceptions() + { + $tests = array( + array('{{ string["a"] }}', 'Impossible to access a key ("a") on a string variable ("foo") in "%s" at line 1.', false), + array('{{ null["a"] }}', 'Impossible to access a key ("a") on a null variable in "%s" at line 1.', false), + array('{{ empty_array["a"] }}', 'Key "a" does not exist as the array is empty in "%s" at line 1.', false), + array('{{ array["a"] }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1.', false), + array('{{ array_access["a"] }}', 'Key "a" in object with ArrayAccess of class "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1.', false), + array('{{ string.a }}', 'Impossible to access an attribute ("a") on a string variable ("foo") in "%s" at line 1.', false), + array('{{ string.a() }}', 'Impossible to invoke a method ("a") on a string variable ("foo") in "%s" at line 1.', false), + array('{{ null.a }}', 'Impossible to access an attribute ("a") on a null variable in "%s" at line 1.', false), + array('{{ null.a() }}', 'Impossible to invoke a method ("a") on a null variable in "%s" at line 1.', false), + array('{{ empty_array.a }}', 'Key "a" does not exist as the array is empty in "%s" at line 1.', false), + array('{{ array.a }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1.', false), + array('{{ attribute(array, -10) }}', 'Key "-10" for array with keys "foo" does not exist in "%s" at line 1.', false), + array('{{ array_access.a }}', 'Neither the property "a" nor one of the methods "a()", "geta()"/"isa()" or "__call()" exist and have public access in class "Twig_TemplateArrayAccessObject" in "%s" at line 1.', false), + array('{% from _self import foo %}{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ foo(array_access) }}', 'Neither the property "missing_method" nor one of the methods "missing_method()", "getmissing_method()"/"ismissing_method()" or "__call()" exist and have public access in class "Twig_TemplateArrayAccessObject" in "%s" at line 1.', false), + array('{{ magic_exception.test }}', 'An exception has been thrown during the rendering of a template ("Hey! Don\'t try to isset me!") in "%s" at line 1.', false), + array('{{ object["a"] }}', 'Impossible to access a key "a" on an object of class "stdClass" that does not implement ArrayAccess interface in "%s" at line 1.', false), + ); + + if (function_exists('twig_template_get_attributes')) { + foreach (array_slice($tests, 0) as $test) { + $test[2] = true; + $tests[] = $test; + } + } + + return $tests; + } + + /** + * @dataProvider getGetAttributeWithSandbox + */ + public function testGetAttributeWithSandbox($object, $item, $allowed, $useExt) + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $policy = new Twig_Sandbox_SecurityPolicy(array(), array(), array(/*method*/), array(/*prop*/), array()); + $twig->addExtension(new Twig_Extension_Sandbox($policy, !$allowed)); + $template = new Twig_TemplateTest($twig, $useExt); + + try { + $template->getAttribute($object, $item, array(), 'any'); + + if (!$allowed) { + $this->fail(); + } + } catch (Twig_Sandbox_SecurityError $e) { + if ($allowed) { + $this->fail(); + } + + $this->assertContains('is not allowed', $e->getMessage()); + } + } + + public function getGetAttributeWithSandbox() + { + $tests = array( + array(new Twig_TemplatePropertyObject(), 'defined', false, false), + array(new Twig_TemplatePropertyObject(), 'defined', true, false), + array(new Twig_TemplateMethodObject(), 'defined', false, false), + array(new Twig_TemplateMethodObject(), 'defined', true, false), + ); + + if (function_exists('twig_template_get_attributes')) { + foreach (array_slice($tests, 0) as $test) { + $test[3] = true; + $tests[] = $test; + } + } + + return $tests; + } + + /** + * @dataProvider getGetAttributeWithTemplateAsObject + * @group legacy + */ + public function testGetAttributeWithTemplateAsObject($useExt) + { + // to be removed in 2.0 + $twig = new Twig_Environment($this->getMockBuilder('Twig_TemplateTestLoaderInterface')->getMock()); + //$twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface', 'Twig_SourceContextLoaderInterface')->getMock()); + + $template = new Twig_TemplateTest($twig, $useExt, 'index.twig'); + $template1 = new Twig_TemplateTest($twig, false, 'index1.twig'); + + $this->assertInstanceof('Twig_Markup', $template->getAttribute($template1, 'string')); + $this->assertEquals('some_string', $template->getAttribute($template1, 'string')); + + $this->assertInstanceof('Twig_Markup', $template->getAttribute($template1, 'true')); + $this->assertEquals('1', $template->getAttribute($template1, 'true')); + + $this->assertInstanceof('Twig_Markup', $template->getAttribute($template1, 'zero')); + $this->assertEquals('0', $template->getAttribute($template1, 'zero')); + + $this->assertNotInstanceof('Twig_Markup', $template->getAttribute($template1, 'empty')); + $this->assertSame('', $template->getAttribute($template1, 'empty')); + + $this->assertFalse($template->getAttribute($template1, 'env', array(), Twig_Template::ANY_CALL, true)); + $this->assertFalse($template->getAttribute($template1, 'environment', array(), Twig_Template::ANY_CALL, true)); + $this->assertFalse($template->getAttribute($template1, 'getEnvironment', array(), Twig_Template::METHOD_CALL, true)); + $this->assertFalse($template->getAttribute($template1, 'displayWithErrorHandling', array(), Twig_Template::METHOD_CALL, true)); + } + + public function getGetAttributeWithTemplateAsObject() + { + $bools = array( + array(false), + ); + + if (function_exists('twig_template_get_attributes')) { + $bools[] = array(true); + } + + return $bools; + } + + /** + * @group legacy + * @expectedDeprecation Calling "getString" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. + * @expectedDeprecation Calling "getString" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. + * @expectedDeprecation Calling "getTrue" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. + * @expectedDeprecation Calling "getTrue" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. + * @expectedDeprecation Calling "getZero" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. + * @expectedDeprecation Calling "getZero" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. + * @expectedDeprecation Calling "getEmpty" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. + * @expectedDeprecation Calling "getEmpty" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. + * @expectedDeprecation Calling "renderBlock" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use block("name") instead). + * @expectedDeprecation Calling "displayBlock" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use block("name") instead). + * @expectedDeprecation Calling "hasBlock" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use "block("name") is defined" instead). + * @expectedDeprecation Calling "render" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use include("index.twig") instead). + * @expectedDeprecation Calling "display" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use include("index.twig") instead). + * @expectedDeprecation Calling "renderBlock" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use block("name", template) instead). + * @expectedDeprecation Calling "displayBlock" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use block("name", template) instead). + * @expectedDeprecation Calling "hasBlock" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use "block("name", template) is defined" instead). + * @expectedDeprecation Calling "render" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use include("index1.twig") instead). + * @expectedDeprecation Calling "display" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use include("index1.twig") instead). + */ + public function testGetAttributeWithTemplateAsObjectForDeprecations() + { + // to be removed in 2.0 + $twig = new Twig_Environment($this->getMockBuilder('Twig_TemplateTestLoaderInterface')->getMock()); + //$twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface', 'Twig_SourceContextLoaderInterface')->getMock()); + + $template = new Twig_TemplateTest($twig, false, 'index.twig'); + $template1 = new Twig_TemplateTest($twig, false, 'index1.twig'); + + $this->assertInstanceof('Twig_Markup', $template->getAttribute($template1, 'string')); + $this->assertEquals('some_string', $template->getAttribute($template1, 'string')); + + $this->assertInstanceof('Twig_Markup', $template->getAttribute($template1, 'true')); + $this->assertEquals('1', $template->getAttribute($template1, 'true')); + + $this->assertInstanceof('Twig_Markup', $template->getAttribute($template1, 'zero')); + $this->assertEquals('0', $template->getAttribute($template1, 'zero')); + + $this->assertNotInstanceof('Twig_Markup', $template->getAttribute($template1, 'empty')); + $this->assertSame('', $template->getAttribute($template1, 'empty')); + + $blocks = array('name' => array($template1, 'block_name')); + + // trigger some deprecation notice messages to check them with @expectedDeprecation + $template->getAttribute($template, 'renderBlock', array('name', array(), $blocks)); + $template->getAttribute($template, 'displayBlock', array('name', array(), $blocks)); + $template->getAttribute($template, 'hasBlock', array('name', array())); + $template->getAttribute($template, 'render', array(array())); + $template->getAttribute($template, 'display', array(array())); + + $template->getAttribute($template1, 'renderBlock', array('name', array(), $blocks)); + $template->getAttribute($template1, 'displayBlock', array('name', array(), $blocks)); + $template->getAttribute($template1, 'hasBlock', array('name', array())); + $template->getAttribute($template1, 'render', array(array())); + $template->getAttribute($template1, 'display', array(array())); + + $this->assertFalse($template->getAttribute($template1, 'env', array(), Twig_Template::ANY_CALL, true)); + $this->assertFalse($template->getAttribute($template1, 'environment', array(), Twig_Template::ANY_CALL, true)); + $this->assertFalse($template->getAttribute($template1, 'getEnvironment', array(), Twig_Template::METHOD_CALL, true)); + $this->assertFalse($template->getAttribute($template1, 'displayWithErrorHandling', array(), Twig_Template::METHOD_CALL, true)); + } + + /** + * @group legacy + * @expectedDeprecation Silent display of undefined block "unknown" in template "index.twig" is deprecated since version 1.29 and will throw an exception in 2.0. + * @expectedDeprecation Silent display of undefined block "unknown" in template "index.twig" is deprecated since version 1.29 and will throw an exception in 2.0. + */ + public function testRenderBlockWithUndefinedBlock() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_TemplateTestLoaderInterface')->getMock()); + + $template = new Twig_TemplateTest($twig, false, 'index.twig'); + $template->renderBlock('unknown', array()); + $template->displayBlock('unknown', array()); + } + + /** + * @dataProvider getTestsDependingOnExtensionAvailability + */ + public function testGetAttributeOnArrayWithConfusableKey($useExt = false) + { + $template = new Twig_TemplateTest( + new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()), + $useExt + ); + + $array = array('Zero', 'One', -1 => 'MinusOne', '' => 'EmptyString', '1.5' => 'FloatButString', '01' => 'IntegerButStringWithLeadingZeros'); + + $this->assertSame('Zero', $array[false]); + $this->assertSame('One', $array[true]); + $this->assertSame('One', $array[1.5]); + $this->assertSame('One', $array['1']); + $this->assertSame('MinusOne', $array[-1.5]); + $this->assertSame('FloatButString', $array['1.5']); + $this->assertSame('IntegerButStringWithLeadingZeros', $array['01']); + $this->assertSame('EmptyString', $array[null]); + + $this->assertSame('Zero', $template->getAttribute($array, false), 'false is treated as 0 when accessing an array (equals PHP behavior)'); + $this->assertSame('One', $template->getAttribute($array, true), 'true is treated as 1 when accessing an array (equals PHP behavior)'); + $this->assertSame('One', $template->getAttribute($array, 1.5), 'float is casted to int when accessing an array (equals PHP behavior)'); + $this->assertSame('One', $template->getAttribute($array, '1'), '"1" is treated as integer 1 when accessing an array (equals PHP behavior)'); + $this->assertSame('MinusOne', $template->getAttribute($array, -1.5), 'negative float is casted to int when accessing an array (equals PHP behavior)'); + $this->assertSame('FloatButString', $template->getAttribute($array, '1.5'), '"1.5" is treated as-is when accessing an array (equals PHP behavior)'); + $this->assertSame('IntegerButStringWithLeadingZeros', $template->getAttribute($array, '01'), '"01" is treated as-is when accessing an array (equals PHP behavior)'); + $this->assertSame('EmptyString', $template->getAttribute($array, null), 'null is treated as "" when accessing an array (equals PHP behavior)'); + } + + public function getTestsDependingOnExtensionAvailability() + { + if (function_exists('twig_template_get_attributes')) { + return array(array(false), array(true)); + } + + return array(array(false)); + } + + /** + * @dataProvider getGetAttributeTests + */ + public function testGetAttribute($defined, $value, $object, $item, $arguments, $type, $useExt = false) + { + $template = new Twig_TemplateTest(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()), $useExt); + + $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type)); + } + + /** + * @dataProvider getGetAttributeTests + */ + public function testGetAttributeStrict($defined, $value, $object, $item, $arguments, $type, $useExt = false, $exceptionMessage = null) + { + $template = new Twig_TemplateTest(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('strict_variables' => true)), $useExt); + + if ($defined) { + $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type)); + } else { + try { + $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type)); + + throw new Exception('Expected Twig_Error_Runtime exception.'); + } catch (Twig_Error_Runtime $e) { + if (null !== $exceptionMessage) { + $this->assertSame($exceptionMessage, $e->getMessage()); + } + } + } + } + + /** + * @dataProvider getGetAttributeTests + */ + public function testGetAttributeDefined($defined, $value, $object, $item, $arguments, $type, $useExt = false) + { + $template = new Twig_TemplateTest(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()), $useExt); + + $this->assertEquals($defined, $template->getAttribute($object, $item, $arguments, $type, true)); + } + + /** + * @dataProvider getGetAttributeTests + */ + public function testGetAttributeDefinedStrict($defined, $value, $object, $item, $arguments, $type, $useExt = false) + { + $template = new Twig_TemplateTest(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('strict_variables' => true)), $useExt); + + $this->assertEquals($defined, $template->getAttribute($object, $item, $arguments, $type, true)); + } + + /** + * @dataProvider getTestsDependingOnExtensionAvailability + */ + public function testGetAttributeCallExceptions($useExt = false) + { + $template = new Twig_TemplateTest(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()), $useExt); + + $object = new Twig_TemplateMagicMethodExceptionObject(); + + $this->assertNull($template->getAttribute($object, 'foo')); + } + + public function getGetAttributeTests() + { + $array = array( + 'defined' => 'defined', + 'zero' => 0, + 'null' => null, + '1' => 1, + 'bar' => true, + 'baz' => 'baz', + '09' => '09', + '+4' => '+4', + ); + + $objectArray = new Twig_TemplateArrayAccessObject(); + $stdObject = (object) $array; + $magicPropertyObject = new Twig_TemplateMagicPropertyObject(); + $propertyObject = new Twig_TemplatePropertyObject(); + $propertyObject1 = new Twig_TemplatePropertyObjectAndIterator(); + $propertyObject2 = new Twig_TemplatePropertyObjectAndArrayAccess(); + $propertyObject3 = new Twig_TemplatePropertyObjectDefinedWithUndefinedValue(); + $methodObject = new Twig_TemplateMethodObject(); + $magicMethodObject = new Twig_TemplateMagicMethodObject(); + + $anyType = Twig_Template::ANY_CALL; + $methodType = Twig_Template::METHOD_CALL; + $arrayType = Twig_Template::ARRAY_CALL; + + $basicTests = array( + // array(defined, value, property to fetch) + array(true, 'defined', 'defined'), + array(false, null, 'undefined'), + array(false, null, 'protected'), + array(true, 0, 'zero'), + array(true, 1, 1), + array(true, 1, 1.0), + array(true, null, 'null'), + array(true, true, 'bar'), + array(true, 'baz', 'baz'), + array(true, '09', '09'), + array(true, '+4', '+4'), + ); + $testObjects = array( + // array(object, type of fetch) + array($array, $arrayType), + array($objectArray, $arrayType), + array($stdObject, $anyType), + array($magicPropertyObject, $anyType), + array($methodObject, $methodType), + array($methodObject, $anyType), + array($propertyObject, $anyType), + array($propertyObject1, $anyType), + array($propertyObject2, $anyType), + ); + + $tests = array(); + foreach ($testObjects as $testObject) { + foreach ($basicTests as $test) { + // properties cannot be numbers + if (($testObject[0] instanceof stdClass || $testObject[0] instanceof Twig_TemplatePropertyObject) && is_numeric($test[2])) { + continue; + } + + if ('+4' === $test[2] && $methodObject === $testObject[0]) { + continue; + } + + $tests[] = array($test[0], $test[1], $testObject[0], $test[2], array(), $testObject[1]); + } + } + + // additional properties tests + $tests = array_merge($tests, array( + array(true, null, $propertyObject3, 'foo', array(), $anyType), + )); + + // additional method tests + $tests = array_merge($tests, array( + array(true, 'defined', $methodObject, 'defined', array(), $methodType), + array(true, 'defined', $methodObject, 'DEFINED', array(), $methodType), + array(true, 'defined', $methodObject, 'getDefined', array(), $methodType), + array(true, 'defined', $methodObject, 'GETDEFINED', array(), $methodType), + array(true, 'static', $methodObject, 'static', array(), $methodType), + array(true, 'static', $methodObject, 'getStatic', array(), $methodType), + + array(true, '__call_undefined', $magicMethodObject, 'undefined', array(), $methodType), + array(true, '__call_UNDEFINED', $magicMethodObject, 'UNDEFINED', array(), $methodType), + )); + + // add the same tests for the any type + foreach ($tests as $test) { + if ($anyType !== $test[5]) { + $test[5] = $anyType; + $tests[] = $test; + } + } + + $methodAndPropObject = new Twig_TemplateMethodAndPropObject(); + + // additional method tests + $tests = array_merge($tests, array( + array(true, 'a', $methodAndPropObject, 'a', array(), $anyType), + array(true, 'a', $methodAndPropObject, 'a', array(), $methodType), + array(false, null, $methodAndPropObject, 'a', array(), $arrayType), + + array(true, 'b_prop', $methodAndPropObject, 'b', array(), $anyType), + array(true, 'b', $methodAndPropObject, 'B', array(), $anyType), + array(true, 'b', $methodAndPropObject, 'b', array(), $methodType), + array(true, 'b', $methodAndPropObject, 'B', array(), $methodType), + array(false, null, $methodAndPropObject, 'b', array(), $arrayType), + + array(false, null, $methodAndPropObject, 'c', array(), $anyType), + array(false, null, $methodAndPropObject, 'c', array(), $methodType), + array(false, null, $methodAndPropObject, 'c', array(), $arrayType), + )); + + // tests when input is not an array or object + $tests = array_merge($tests, array( + array(false, null, 42, 'a', array(), $anyType, false, 'Impossible to access an attribute ("a") on a integer variable ("42") in "index.twig".'), + array(false, null, 'string', 'a', array(), $anyType, false, 'Impossible to access an attribute ("a") on a string variable ("string") in "index.twig".'), + array(false, null, array(), 'a', array(), $anyType, false, 'Key "a" does not exist as the array is empty in "index.twig".'), + )); + + // add twig_template_get_attributes tests + + if (function_exists('twig_template_get_attributes')) { + foreach (array_slice($tests, 0) as $test) { + $test = array_pad($test, 7, null); + $test[6] = true; + $tests[] = $test; + } + } + + return $tests; + } +} + +class Twig_TemplateTest extends Twig_Template +{ + protected $useExtGetAttribute = false; + private $name; + + public function __construct(Twig_Environment $env, $useExtGetAttribute = false, $name = 'index.twig') + { + parent::__construct($env); + $this->useExtGetAttribute = $useExtGetAttribute; + self::$cache = array(); + $this->name = $name; + } + + public function getZero() + { + return 0; + } + + public function getEmpty() + { + return ''; + } + + public function getString() + { + return 'some_string'; + } + + public function getTrue() + { + return true; + } + + public function getTemplateName() + { + return $this->name; + } + + public function getDebugInfo() + { + return array(); + } + + protected function doGetParent(array $context) + { + return false; + } + + protected function doDisplay(array $context, array $blocks = array()) + { + } + + public function getAttribute($object, $item, array $arguments = array(), $type = Twig_Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) + { + if ($this->useExtGetAttribute) { + return twig_template_get_attributes($this, $object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck); + } else { + return parent::getAttribute($object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck); + } + } + + public function block_name($context, array $blocks = array()) + { + } +} + +class Twig_TemplateArrayAccessObject implements ArrayAccess +{ + protected $protected = 'protected'; + + public $attributes = array( + 'defined' => 'defined', + 'zero' => 0, + 'null' => null, + '1' => 1, + 'bar' => true, + 'baz' => 'baz', + '09' => '09', + '+4' => '+4', + ); + + public function offsetExists($name) + { + return array_key_exists($name, $this->attributes); + } + + public function offsetGet($name) + { + return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null; + } + + public function offsetSet($name, $value) + { + } + + public function offsetUnset($name) + { + } +} + +class Twig_TemplateMagicPropertyObject +{ + public $defined = 'defined'; + + public $attributes = array( + 'zero' => 0, + 'null' => null, + '1' => 1, + 'bar' => true, + 'baz' => 'baz', + '09' => '09', + '+4' => '+4', + ); + + protected $protected = 'protected'; + + public function __isset($name) + { + return array_key_exists($name, $this->attributes); + } + + public function __get($name) + { + return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null; + } +} + +class Twig_TemplateMagicPropertyObjectWithException +{ + public function __isset($key) + { + throw new Exception('Hey! Don\'t try to isset me!'); + } +} + +class Twig_TemplatePropertyObject +{ + public $defined = 'defined'; + public $zero = 0; + public $null = null; + public $bar = true; + public $baz = 'baz'; + + protected $protected = 'protected'; +} + +class Twig_TemplatePropertyObjectAndIterator extends Twig_TemplatePropertyObject implements IteratorAggregate +{ + public function getIterator() + { + return new ArrayIterator(array('foo', 'bar')); + } +} + +class Twig_TemplatePropertyObjectAndArrayAccess extends Twig_TemplatePropertyObject implements ArrayAccess +{ + private $data = array(); + + public function offsetExists($offset) + { + return array_key_exists($offset, $this->data); + } + + public function offsetGet($offset) + { + return $this->offsetExists($offset) ? $this->data[$offset] : 'n/a'; + } + + public function offsetSet($offset, $value) + { + } + + public function offsetUnset($offset) + { + } +} + +class Twig_TemplatePropertyObjectDefinedWithUndefinedValue +{ + public $foo; + + public function __construct() + { + $this->foo = @$notExist; + } +} + +class Twig_TemplateMethodObject +{ + public function getDefined() + { + return 'defined'; + } + + public function get1() + { + return 1; + } + + public function get09() + { + return '09'; + } + + public function getZero() + { + return 0; + } + + public function getNull() + { + } + + public function isBar() + { + return true; + } + + public function isBaz() + { + return 'should never be returned'; + } + + public function getBaz() + { + return 'baz'; + } + + protected function getProtected() + { + return 'protected'; + } + + public static function getStatic() + { + return 'static'; + } +} + +class Twig_TemplateMethodAndPropObject +{ + private $a = 'a_prop'; + + public function getA() + { + return 'a'; + } + + public $b = 'b_prop'; + + public function getB() + { + return 'b'; + } + + private $c = 'c_prop'; + + private function getC() + { + return 'c'; + } +} + +class Twig_TemplateMagicMethodObject +{ + public function __call($method, $arguments) + { + return '__call_'.$method; + } +} + +class Twig_TemplateMagicMethodExceptionObject +{ + public function __call($method, $arguments) + { + throw new BadMethodCallException(sprintf('Unknown method "%s".', $method)); + } +} + +class CExtDisablingNodeVisitor implements Twig_NodeVisitorInterface +{ + public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Expression_GetAttr) { + $node->setAttribute('disable_c_ext', true); + } + + return $node; + } + + public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + { + return $node; + } + + public function getPriority() + { + return 0; + } +} + +// to be removed in 2.0 +interface Twig_TemplateTestLoaderInterface extends Twig_LoaderInterface, Twig_SourceContextLoaderInterface +{ +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/TemplateWrapperTest.php b/src/vendor/twig/twig/test/Twig/Tests/TemplateWrapperTest.php new file mode 100644 index 0000000..9cd1ced --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/TemplateWrapperTest.php @@ -0,0 +1,64 @@ + '{% block foo %}{% endblock %}', + 'index_with_use' => '{% use "imported" %}{% block foo %}{% endblock %}', + 'index_with_extends' => '{% extends "extended" %}{% block foo %}{% endblock %}', + 'imported' => '{% block imported %}{% endblock %}', + 'extended' => '{% block extended %}{% endblock %}', + ))); + + $wrapper = new Twig_TemplateWrapper($twig, $twig->loadTemplate('index')); + $this->assertTrue($wrapper->hasBlock('foo')); + $this->assertFalse($wrapper->hasBlock('bar')); + $this->assertEquals(array('foo'), $wrapper->getBlockNames()); + + $wrapper = new Twig_TemplateWrapper($twig, $twig->loadTemplate('index_with_use')); + $this->assertTrue($wrapper->hasBlock('foo')); + $this->assertTrue($wrapper->hasBlock('imported')); + $this->assertEquals(array('imported', 'foo'), $wrapper->getBlockNames()); + + $wrapper = new Twig_TemplateWrapper($twig, $twig->loadTemplate('index_with_extends')); + $this->assertTrue($wrapper->hasBlock('foo')); + $this->assertTrue($wrapper->hasBlock('extended')); + $this->assertEquals(array('foo', 'extended'), $wrapper->getBlockNames()); + } + + public function testRenderBlock() + { + $twig = new Twig_Environment(new Twig_Loader_Array(array( + 'index' => '{% block foo %}{{ foo }}{{ bar }}{% endblock %}', + ))); + $twig->addGlobal('bar', 'BAR'); + + $wrapper = new Twig_TemplateWrapper($twig, $twig->loadTemplate('index')); + $this->assertEquals('FOOBAR', $wrapper->renderBlock('foo', array('foo' => 'FOO'))); + } + + public function testDisplayBlock() + { + $twig = new Twig_Environment(new Twig_Loader_Array(array( + 'index' => '{% block foo %}{{ foo }}{{ bar }}{% endblock %}', + ))); + $twig->addGlobal('bar', 'BAR'); + + $wrapper = new Twig_TemplateWrapper($twig, $twig->loadTemplate('index')); + + ob_start(); + $wrapper->displayBlock('foo', array('foo' => 'FOO')); + + $this->assertEquals('FOOBAR', ob_get_clean()); + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/TokenStreamTest.php b/src/vendor/twig/twig/test/Twig/Tests/TokenStreamTest.php new file mode 100644 index 0000000..beaa71d --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/TokenStreamTest.php @@ -0,0 +1,82 @@ +assertEquals('foo', $stream->getFilename()); + $this->assertEquals('{{ foo }}', $stream->getSource()); + $this->assertEquals('foo', $stream->getSourceContext()->getName()); + $this->assertEquals('{{ foo }}', $stream->getSourceContext()->getCode()); + } + + public function testNext() + { + $stream = new Twig_TokenStream(self::$tokens); + $repr = array(); + while (!$stream->isEOF()) { + $token = $stream->next(); + + $repr[] = $token->getValue(); + } + $this->assertEquals('1, 2, 3, 4, 5, 6, 7', implode(', ', $repr), '->next() advances the pointer and returns the current token'); + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedMessage Unexpected end of template + */ + public function testEndOfTemplateNext() + { + $stream = new Twig_TokenStream(array( + new Twig_Token(Twig_Token::BLOCK_START_TYPE, 1, 1), + )); + while (!$stream->isEOF()) { + $stream->next(); + } + } + + /** + * @expectedException Twig_Error_Syntax + * @expectedMessage Unexpected end of template + */ + public function testEndOfTemplateLook() + { + $stream = new Twig_TokenStream(array( + new Twig_Token(Twig_Token::BLOCK_START_TYPE, 1, 1), + )); + while (!$stream->isEOF()) { + $stream->look(); + $stream->next(); + } + } +} diff --git a/src/vendor/twig/twig/test/Twig/Tests/escapingTest.php b/src/vendor/twig/twig/test/Twig/Tests/escapingTest.php new file mode 100644 index 0000000..e5b9093 --- /dev/null +++ b/src/vendor/twig/twig/test/Twig/Tests/escapingTest.php @@ -0,0 +1,320 @@ + ''', + '"' => '"', + '<' => '<', + '>' => '>', + '&' => '&', + ); + + protected $htmlAttrSpecialChars = array( + '\'' => ''', + /* Characters beyond ASCII value 255 to unicode escape */ + 'Ā' => 'Ā', + /* Immune chars excluded */ + ',' => ',', + '.' => '.', + '-' => '-', + '_' => '_', + /* Basic alnums excluded */ + 'a' => 'a', + 'A' => 'A', + 'z' => 'z', + 'Z' => 'Z', + '0' => '0', + '9' => '9', + /* Basic control characters and null */ + "\r" => ' ', + "\n" => ' ', + "\t" => ' ', + "\0" => '�', // should use Unicode replacement char + /* Encode chars as named entities where possible */ + '<' => '<', + '>' => '>', + '&' => '&', + '"' => '"', + /* Encode spaces for quoteless attribute protection */ + ' ' => ' ', + ); + + protected $jsSpecialChars = array( + /* HTML special chars - escape without exception to hex */ + '<' => '\\x3C', + '>' => '\\x3E', + '\'' => '\\x27', + '"' => '\\x22', + '&' => '\\x26', + /* Characters beyond ASCII value 255 to unicode escape */ + 'Ā' => '\\u0100', + /* Immune chars excluded */ + ',' => ',', + '.' => '.', + '_' => '_', + /* Basic alnums excluded */ + 'a' => 'a', + 'A' => 'A', + 'z' => 'z', + 'Z' => 'Z', + '0' => '0', + '9' => '9', + /* Basic control characters and null */ + "\r" => '\\x0D', + "\n" => '\\x0A', + "\t" => '\\x09', + "\0" => '\\x00', + /* Encode spaces for quoteless attribute protection */ + ' ' => '\\x20', + ); + + protected $urlSpecialChars = array( + /* HTML special chars - escape without exception to percent encoding */ + '<' => '%3C', + '>' => '%3E', + '\'' => '%27', + '"' => '%22', + '&' => '%26', + /* Characters beyond ASCII value 255 to hex sequence */ + 'Ā' => '%C4%80', + /* Punctuation and unreserved check */ + ',' => '%2C', + '.' => '.', + '_' => '_', + '-' => '-', + ':' => '%3A', + ';' => '%3B', + '!' => '%21', + /* Basic alnums excluded */ + 'a' => 'a', + 'A' => 'A', + 'z' => 'z', + 'Z' => 'Z', + '0' => '0', + '9' => '9', + /* Basic control characters and null */ + "\r" => '%0D', + "\n" => '%0A', + "\t" => '%09', + "\0" => '%00', + /* PHP quirks from the past */ + ' ' => '%20', + '~' => '~', + '+' => '%2B', + ); + + protected $cssSpecialChars = array( + /* HTML special chars - escape without exception to hex */ + '<' => '\\3C ', + '>' => '\\3E ', + '\'' => '\\27 ', + '"' => '\\22 ', + '&' => '\\26 ', + /* Characters beyond ASCII value 255 to unicode escape */ + 'Ā' => '\\100 ', + /* Immune chars excluded */ + ',' => '\\2C ', + '.' => '\\2E ', + '_' => '\\5F ', + /* Basic alnums excluded */ + 'a' => 'a', + 'A' => 'A', + 'z' => 'z', + 'Z' => 'Z', + '0' => '0', + '9' => '9', + /* Basic control characters and null */ + "\r" => '\\D ', + "\n" => '\\A ', + "\t" => '\\9 ', + "\0" => '\\0 ', + /* Encode spaces for quoteless attribute protection */ + ' ' => '\\20 ', + ); + + protected $env; + + protected function setUp() + { + $this->env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + } + + public function testHtmlEscapingConvertsSpecialChars() + { + foreach ($this->htmlSpecialChars as $key => $value) { + $this->assertEquals($value, twig_escape_filter($this->env, $key, 'html'), 'Failed to escape: '.$key); + } + } + + public function testHtmlAttributeEscapingConvertsSpecialChars() + { + foreach ($this->htmlAttrSpecialChars as $key => $value) { + $this->assertEquals($value, twig_escape_filter($this->env, $key, 'html_attr'), 'Failed to escape: '.$key); + } + } + + public function testJavascriptEscapingConvertsSpecialChars() + { + foreach ($this->jsSpecialChars as $key => $value) { + $this->assertEquals($value, twig_escape_filter($this->env, $key, 'js'), 'Failed to escape: '.$key); + } + } + + public function testJavascriptEscapingReturnsStringIfZeroLength() + { + $this->assertEquals('', twig_escape_filter($this->env, '', 'js')); + } + + public function testJavascriptEscapingReturnsStringIfContainsOnlyDigits() + { + $this->assertEquals('123', twig_escape_filter($this->env, '123', 'js')); + } + + public function testCssEscapingConvertsSpecialChars() + { + foreach ($this->cssSpecialChars as $key => $value) { + $this->assertEquals($value, twig_escape_filter($this->env, $key, 'css'), 'Failed to escape: '.$key); + } + } + + public function testCssEscapingReturnsStringIfZeroLength() + { + $this->assertEquals('', twig_escape_filter($this->env, '', 'css')); + } + + public function testCssEscapingReturnsStringIfContainsOnlyDigits() + { + $this->assertEquals('123', twig_escape_filter($this->env, '123', 'css')); + } + + public function testUrlEscapingConvertsSpecialChars() + { + foreach ($this->urlSpecialChars as $key => $value) { + $this->assertEquals($value, twig_escape_filter($this->env, $key, 'url'), 'Failed to escape: '.$key); + } + } + + /** + * Range tests to confirm escaped range of characters is within OWASP recommendation. + */ + + /** + * Only testing the first few 2 ranges on this prot. function as that's all these + * other range tests require. + */ + public function testUnicodeCodepointConversionToUtf8() + { + $expected = ' ~ޙ'; + $codepoints = array(0x20, 0x7e, 0x799); + $result = ''; + foreach ($codepoints as $value) { + $result .= $this->codepointToUtf8($value); + } + $this->assertEquals($expected, $result); + } + + /** + * Convert a Unicode Codepoint to a literal UTF-8 character. + * + * @param int $codepoint Unicode codepoint in hex notation + * + * @return string UTF-8 literal string + */ + protected function codepointToUtf8($codepoint) + { + if ($codepoint < 0x80) { + return chr($codepoint); + } + if ($codepoint < 0x800) { + return chr($codepoint >> 6 & 0x3f | 0xc0) + .chr($codepoint & 0x3f | 0x80); + } + if ($codepoint < 0x10000) { + return chr($codepoint >> 12 & 0x0f | 0xe0) + .chr($codepoint >> 6 & 0x3f | 0x80) + .chr($codepoint & 0x3f | 0x80); + } + if ($codepoint < 0x110000) { + return chr($codepoint >> 18 & 0x07 | 0xf0) + .chr($codepoint >> 12 & 0x3f | 0x80) + .chr($codepoint >> 6 & 0x3f | 0x80) + .chr($codepoint & 0x3f | 0x80); + } + throw new Exception('Codepoint requested outside of Unicode range.'); + } + + public function testJavascriptEscapingEscapesOwaspRecommendedRanges() + { + $immune = array(',', '.', '_'); // Exceptions to escaping ranges + for ($chr = 0; $chr < 0xFF; ++$chr) { + if ($chr >= 0x30 && $chr <= 0x39 + || $chr >= 0x41 && $chr <= 0x5A + || $chr >= 0x61 && $chr <= 0x7A) { + $literal = $this->codepointToUtf8($chr); + $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'js')); + } else { + $literal = $this->codepointToUtf8($chr); + if (in_array($literal, $immune)) { + $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'js')); + } else { + $this->assertNotEquals( + $literal, + twig_escape_filter($this->env, $literal, 'js'), + "$literal should be escaped!"); + } + } + } + } + + public function testHtmlAttributeEscapingEscapesOwaspRecommendedRanges() + { + $immune = array(',', '.', '-', '_'); // Exceptions to escaping ranges + for ($chr = 0; $chr < 0xFF; ++$chr) { + if ($chr >= 0x30 && $chr <= 0x39 + || $chr >= 0x41 && $chr <= 0x5A + || $chr >= 0x61 && $chr <= 0x7A) { + $literal = $this->codepointToUtf8($chr); + $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'html_attr')); + } else { + $literal = $this->codepointToUtf8($chr); + if (in_array($literal, $immune)) { + $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'html_attr')); + } else { + $this->assertNotEquals( + $literal, + twig_escape_filter($this->env, $literal, 'html_attr'), + "$literal should be escaped!"); + } + } + } + } + + public function testCssEscapingEscapesOwaspRecommendedRanges() + { + // CSS has no exceptions to escaping ranges + for ($chr = 0; $chr < 0xFF; ++$chr) { + if ($chr >= 0x30 && $chr <= 0x39 + || $chr >= 0x41 && $chr <= 0x5A + || $chr >= 0x61 && $chr <= 0x7A) { + $literal = $this->codepointToUtf8($chr); + $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'css')); + } else { + $literal = $this->codepointToUtf8($chr); + $this->assertNotEquals( + $literal, + twig_escape_filter($this->env, $literal, 'css'), + "$literal should be escaped!"); + } + } + } +} diff --git a/src/vendor/twig/twig/test/bootstrap.php b/src/vendor/twig/twig/test/bootstrap.php new file mode 100644 index 0000000..aecb976 --- /dev/null +++ b/src/vendor/twig/twig/test/bootstrap.php @@ -0,0 +1,13 @@ +