From 4f8532faace9c803d19420b73e3ecedda2fa5261 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 8 Sep 2020 14:44:34 +0200 Subject: [PATCH] add plug --- .../admin-addon-user-manager/.gitattributes | 2 + .../admin-addon-user-manager/CHANGELOG.md | 314 ++++++++++ user/plugins/admin-addon-user-manager/LICENSE | 21 + .../admin-addon-user-manager/README.md | 39 ++ .../admin-addon-user-manager.php | 157 +++++ .../admin-addon-user-manager.yaml | 4 + .../admin/pages/group-manager.md | 7 + .../admin/pages/user-expert.md | 7 + .../admin/pages/user-manager.md | 7 + .../assets/groups/style.css | 3 + .../assets/users/style.css | 227 +++++++ .../admin-addon-user-manager/blueprints.yaml | 58 ++ .../blueprints/user/aaum-account.yaml | 20 + .../blueprints/user/account-raw.yaml | 16 + .../blueprints/user/group.yaml | 43 ++ .../admin-addon-user-manager/composer.json | 10 + .../admin-addon-user-manager/composer.lock | 570 +++++++++++++++++ .../admin-addon-user-manager/docs/filter.md | 67 ++ .../languages/cs.yaml | 34 ++ .../languages/en.yaml | 34 ++ .../languages/es.yaml | 34 ++ .../languages/fr.yaml | 34 ++ .../languages/no.yaml | 34 ++ .../languages/pt.yaml | 34 ++ .../languages/ru.yaml | 34 ++ .../languages/sr.yaml | 34 ++ .../languages/uk.yaml | 34 ++ .../languages/zh.yaml | 34 ++ .../admin-addon-user-manager/modals.yaml | 93 +++ .../admin-addon-user-manager/src/Dot.php | 108 ++++ .../admin-addon-user-manager/src/Group.php | 164 +++++ .../src/Groups/Manager.php | 242 ++++++++ .../admin-addon-user-manager/src/Manager.php | 58 ++ .../src/Pagination/ArrayPagination.php | 81 +++ .../src/Pagination/Pagination.php | 17 + .../src/Users/ExpertManager.php | 100 +++ .../src/Users/Manager.php | 432 +++++++++++++ .../forms/fields/button/button.html.twig | 25 + .../templates/group-manager.html.twig | 170 ++++++ .../templates/user-expert.html.twig | 28 + .../templates/user-manager-macros.html.twig | 31 + .../templates/user-manager.html.twig | 199 ++++++ .../vendor/autoload.php | 7 + .../vendor/composer/ClassLoader.php | 445 ++++++++++++++ .../vendor/composer/LICENSE | 21 + .../vendor/composer/autoload_classmap.php | 16 + .../vendor/composer/autoload_files.php | 10 + .../vendor/composer/autoload_namespaces.php | 9 + .../vendor/composer/autoload_psr4.php | 19 + .../vendor/composer/autoload_real.php | 70 +++ .../vendor/composer/autoload_static.php | 97 +++ .../vendor/composer/installed.json | 574 ++++++++++++++++++ .../vendor/paragonie/random_compat/LICENSE | 22 + .../paragonie/random_compat/build-phar.sh | 5 + .../paragonie/random_compat/composer.json | 34 ++ .../dist/random_compat.phar.pubkey | 5 + .../dist/random_compat.phar.pubkey.asc | 11 + .../paragonie/random_compat/lib/random.php | 32 + .../random_compat/other/build_phar.php | 57 ++ .../random_compat/psalm-autoload.php | 9 + .../vendor/paragonie/random_compat/psalm.xml | 19 + .../vendor/psr/cache/CHANGELOG.md | 16 + .../vendor/psr/cache/LICENSE.txt | 19 + .../vendor/psr/cache/README.md | 9 + .../vendor/psr/cache/composer.json | 25 + .../vendor/psr/cache/src/CacheException.php | 10 + .../psr/cache/src/CacheItemInterface.php | 105 ++++ .../psr/cache/src/CacheItemPoolInterface.php | 138 +++++ .../cache/src/InvalidArgumentException.php | 13 + .../vendor/psr/container/.gitignore | 3 + .../vendor/psr/container/LICENSE | 21 + .../vendor/psr/container/README.md | 5 + .../vendor/psr/container/composer.json | 27 + .../src/ContainerExceptionInterface.php | 13 + .../psr/container/src/ContainerInterface.php | 37 ++ .../src/NotFoundExceptionInterface.php | 13 + .../vendor/psr/log/LICENSE | 19 + .../vendor/psr/log/Psr/Log/AbstractLogger.php | 128 ++++ .../log/Psr/Log/InvalidArgumentException.php | 7 + .../vendor/psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 18 + .../psr/log/Psr/Log/LoggerAwareTrait.php | 26 + .../psr/log/Psr/Log/LoggerInterface.php | 125 ++++ .../vendor/psr/log/Psr/Log/LoggerTrait.php | 142 +++++ .../vendor/psr/log/Psr/Log/NullLogger.php | 30 + .../vendor/psr/log/Psr/Log/Test/DummyTest.php | 18 + .../log/Psr/Log/Test/LoggerInterfaceTest.php | 138 +++++ .../psr/log/Psr/Log/Test/TestLogger.php | 147 +++++ .../vendor/psr/log/README.md | 58 ++ .../vendor/psr/log/composer.json | 26 + .../vendor/symfony/cache-contracts/.gitignore | 3 + .../cache-contracts/CacheInterface.php | 57 ++ .../symfony/cache-contracts/CacheTrait.php | 76 +++ .../cache-contracts/CallbackInterface.php | 30 + .../symfony/cache-contracts/ItemInterface.php | 65 ++ .../vendor/symfony/cache-contracts/LICENSE | 19 + .../vendor/symfony/cache-contracts/README.md | 9 + .../TagAwareCacheInterface.php | 38 ++ .../symfony/cache-contracts/composer.json | 34 ++ .../symfony/cache/Adapter/AbstractAdapter.php | 203 +++++++ .../cache/Adapter/AbstractTagAwareAdapter.php | 323 ++++++++++ .../cache/Adapter/AdapterInterface.php | 49 ++ .../symfony/cache/Adapter/ApcuAdapter.php | 27 + .../symfony/cache/Adapter/ArrayAdapter.php | 171 ++++++ .../symfony/cache/Adapter/ChainAdapter.php | 332 ++++++++++ .../symfony/cache/Adapter/DoctrineAdapter.php | 27 + .../cache/Adapter/FilesystemAdapter.php | 29 + .../Adapter/FilesystemTagAwareAdapter.php | 239 ++++++++ .../cache/Adapter/MemcachedAdapter.php | 37 ++ .../symfony/cache/Adapter/NullAdapter.php | 156 +++++ .../symfony/cache/Adapter/PdoAdapter.php | 54 ++ .../symfony/cache/Adapter/PhpArrayAdapter.php | 332 ++++++++++ .../symfony/cache/Adapter/PhpFilesAdapter.php | 38 ++ .../symfony/cache/Adapter/ProxyAdapter.php | 269 ++++++++ .../symfony/cache/Adapter/Psr16Adapter.php | 86 +++ .../symfony/cache/Adapter/RedisAdapter.php | 30 + .../cache/Adapter/RedisTagAwareAdapter.php | 292 +++++++++ .../cache/Adapter/SimpleCacheAdapter.php | 21 + .../symfony/cache/Adapter/TagAwareAdapter.php | 429 +++++++++++++ .../Adapter/TagAwareAdapterInterface.php | 33 + .../cache/Adapter/TraceableAdapter.php | 302 +++++++++ .../Adapter/TraceableTagAwareAdapter.php | 38 ++ .../vendor/symfony/cache/CHANGELOG.md | 73 +++ .../vendor/symfony/cache/CacheItem.php | 202 ++++++ .../DataCollector/CacheDataCollector.php | 194 ++++++ .../CacheCollectorPass.php | 72 +++ .../CachePoolClearerPass.php | 48 ++ .../DependencyInjection/CachePoolPass.php | 227 +++++++ .../CachePoolPrunerPass.php | 60 ++ .../vendor/symfony/cache/DoctrineProvider.php | 114 ++++ .../cache/Exception/CacheException.php | 25 + .../Exception/InvalidArgumentException.php | 25 + .../cache/Exception/LogicException.php | 25 + .../vendor/symfony/cache/LICENSE | 19 + .../vendor/symfony/cache/LockRegistry.php | 154 +++++ .../cache/Marshaller/DefaultMarshaller.php | 99 +++ .../cache/Marshaller/DeflateMarshaller.php | 53 ++ .../cache/Marshaller/MarshallerInterface.php | 40 ++ .../cache/Marshaller/TagAwareMarshaller.php | 89 +++ .../symfony/cache/PruneableInterface.php | 23 + .../vendor/symfony/cache/Psr16Cache.php | 277 +++++++++ .../vendor/symfony/cache/README.md | 18 + .../symfony/cache/ResettableInterface.php | 21 + .../symfony/cache/Simple/AbstractCache.php | 199 ++++++ .../vendor/symfony/cache/Simple/ApcuCache.php | 31 + .../symfony/cache/Simple/ArrayCache.php | 167 +++++ .../symfony/cache/Simple/ChainCache.php | 271 +++++++++ .../symfony/cache/Simple/DoctrineCache.php | 34 ++ .../symfony/cache/Simple/FilesystemCache.php | 36 ++ .../symfony/cache/Simple/MemcachedCache.php | 34 ++ .../vendor/symfony/cache/Simple/NullCache.php | 104 ++++ .../vendor/symfony/cache/Simple/PdoCache.php | 59 ++ .../symfony/cache/Simple/PhpArrayCache.php | 256 ++++++++ .../symfony/cache/Simple/PhpFilesCache.php | 45 ++ .../vendor/symfony/cache/Simple/Psr6Cache.php | 23 + .../symfony/cache/Simple/RedisCache.php | 35 ++ .../symfony/cache/Simple/TraceableCache.php | 258 ++++++++ .../cache/Traits/AbstractAdapterTrait.php | 155 +++++ .../symfony/cache/Traits/AbstractTrait.php | 303 +++++++++ .../vendor/symfony/cache/Traits/ApcuTrait.php | 121 ++++ .../symfony/cache/Traits/ArrayTrait.php | 183 ++++++ .../symfony/cache/Traits/ContractsTrait.php | 97 +++ .../symfony/cache/Traits/DoctrineTrait.php | 98 +++ .../cache/Traits/FilesystemCommonTrait.php | 185 ++++++ .../symfony/cache/Traits/FilesystemTrait.php | 124 ++++ .../symfony/cache/Traits/MemcachedTrait.php | 325 ++++++++++ .../vendor/symfony/cache/Traits/PdoTrait.php | 446 ++++++++++++++ .../symfony/cache/Traits/PhpArrayTrait.php | 169 ++++++ .../symfony/cache/Traits/PhpFilesTrait.php | 313 ++++++++++ .../symfony/cache/Traits/ProxyTrait.php | 43 ++ .../cache/Traits/RedisClusterProxy.php | 63 ++ .../symfony/cache/Traits/RedisProxy.php | 65 ++ .../symfony/cache/Traits/RedisTrait.php | 511 ++++++++++++++++ .../vendor/symfony/cache/composer.json | 59 ++ .../symfony/expression-language/.gitignore | 3 + .../symfony/expression-language/CHANGELOG.md | 12 + .../symfony/expression-language/Compiler.php | 146 +++++ .../expression-language/Expression.php | 40 ++ .../ExpressionFunction.php | 100 +++ .../ExpressionFunctionProviderInterface.php | 23 + .../ExpressionLanguage.php | 178 ++++++ .../symfony/expression-language/LICENSE | 19 + .../symfony/expression-language/Lexer.php | 103 ++++ .../Node/ArgumentsNode.php | 40 ++ .../expression-language/Node/ArrayNode.php | 118 ++++ .../expression-language/Node/BinaryNode.php | 170 ++++++ .../Node/ConditionalNode.php | 56 ++ .../expression-language/Node/ConstantNode.php | 81 +++ .../expression-language/Node/FunctionNode.php | 67 ++ .../expression-language/Node/GetAttrNode.php | 114 ++++ .../expression-language/Node/NameNode.php | 45 ++ .../symfony/expression-language/Node/Node.php | 110 ++++ .../expression-language/Node/UnaryNode.php | 66 ++ .../expression-language/ParsedExpression.php | 40 ++ .../symfony/expression-language/Parser.php | 380 ++++++++++++ .../ParserCache/ArrayParserCache.php | 42 ++ .../ParserCache/ParserCacheAdapter.php | 120 ++++ .../ParserCache/ParserCacheInterface.php | 41 ++ .../symfony/expression-language/README.md | 15 + .../Resources/bin/generate_operator_regex.php | 27 + .../SerializedParsedExpression.php | 37 ++ .../expression-language/SyntaxError.php | 41 ++ .../Tests/ExpressionFunctionTest.php | 41 ++ .../Tests/ExpressionLanguageTest.php | 308 ++++++++++ .../Tests/ExpressionTest.php | 28 + .../Tests/Fixtures/TestProvider.php | 41 ++ .../expression-language/Tests/LexerTest.php | 129 ++++ .../Tests/Node/AbstractNodeTest.php | 50 ++ .../Tests/Node/ArgumentsNodeTest.php | 36 ++ .../Tests/Node/ArrayNodeTest.php | 73 +++ .../Tests/Node/BinaryNodeTest.php | 166 +++++ .../Tests/Node/ConditionalNodeTest.php | 42 ++ .../Tests/Node/ConstantNodeTest.php | 60 ++ .../Tests/Node/FunctionNodeTest.php | 52 ++ .../Tests/Node/GetAttrNodeTest.php | 78 +++ .../Tests/Node/NameNodeTest.php | 38 ++ .../Tests/Node/NodeTest.php | 41 ++ .../Tests/Node/UnaryNodeTest.php | 48 ++ .../Tests/ParsedExpressionTest.php | 29 + .../ParserCache/ParserCacheAdapterTest.php | 140 +++++ .../expression-language/Tests/ParserTest.php | 237 ++++++++ .../symfony/expression-language/Token.php | 66 ++ .../expression-language/TokenStream.php | 97 +++ .../symfony/expression-language/composer.json | 35 ++ .../expression-language/phpunit.xml.dist | 30 + .../vendor/symfony/polyfill-php70/LICENSE | 19 + .../vendor/symfony/polyfill-php70/Php70.php | 74 +++ .../vendor/symfony/polyfill-php70/README.md | 28 + .../Resources/stubs/ArithmeticError.php | 5 + .../Resources/stubs/AssertionError.php | 5 + .../Resources/stubs/DivisionByZeroError.php | 5 + .../polyfill-php70/Resources/stubs/Error.php | 5 + .../Resources/stubs/ParseError.php | 5 + ...SessionUpdateTimestampHandlerInterface.php | 23 + .../Resources/stubs/TypeError.php | 5 + .../symfony/polyfill-php70/bootstrap.php | 27 + .../symfony/polyfill-php70/composer.json | 33 + .../symfony/service-contracts/.gitignore | 3 + .../vendor/symfony/service-contracts/LICENSE | 19 + .../symfony/service-contracts/README.md | 9 + .../service-contracts/ResetInterface.php | 30 + .../service-contracts/ServiceLocatorTrait.php | 122 ++++ .../ServiceProviderInterface.php | 36 ++ .../ServiceSubscriberInterface.php | 53 ++ .../ServiceSubscriberTrait.php | 63 ++ .../Test/ServiceLocatorTest.php | 92 +++ .../symfony/service-contracts/composer.json | 34 ++ .../vendor/symfony/var-exporter/CHANGELOG.md | 7 + .../Exception/ClassNotFoundException.php | 20 + .../Exception/ExceptionInterface.php | 16 + .../NotInstantiableTypeException.php | 20 + .../symfony/var-exporter/Instantiator.php | 94 +++ .../var-exporter/Internal/Exporter.php | 405 ++++++++++++ .../var-exporter/Internal/Hydrator.php | 151 +++++ .../var-exporter/Internal/Reference.php | 30 + .../var-exporter/Internal/Registry.php | 136 +++++ .../symfony/var-exporter/Internal/Values.php | 27 + .../vendor/symfony/var-exporter/LICENSE | 19 + .../vendor/symfony/var-exporter/README.md | 38 ++ .../symfony/var-exporter/VarExporter.php | 114 ++++ .../vendor/symfony/var-exporter/composer.json | 36 ++ user/plugins/advanced-pagecache/CHANGELOG.md | 53 ++ user/plugins/advanced-pagecache/LICENSE | 22 + user/plugins/advanced-pagecache/README.md | 56 ++ .../advanced-pagecache/advanced-pagecache.php | 103 ++++ .../advanced-pagecache.yaml | 13 + .../advanced-pagecache/blueprints.yaml | 103 ++++ user/plugins/aura/CHANGELOG.md | 52 ++ user/plugins/aura/LICENSE | 21 + user/plugins/aura/README.md | 42 ++ .../aura/assets/demo-composition-min.png | Bin 0 -> 135930 bytes user/plugins/aura/aura.php | 192 ++++++ user/plugins/aura/aura.yaml | 1 + user/plugins/aura/blueprints.yaml | 137 +++++ user/plugins/aura/blueprints/default.yaml | 61 ++ user/plugins/aura/classes/aura.php | 443 ++++++++++++++ user/plugins/aura/classes/image.php | 12 + user/plugins/aura/classes/organization.php | 11 + user/plugins/aura/classes/person.php | 12 + user/plugins/aura/classes/webpage.php | 16 + user/plugins/aura/classes/website.php | 9 + 281 files changed, 24260 insertions(+) create mode 100644 user/plugins/admin-addon-user-manager/.gitattributes create mode 100644 user/plugins/admin-addon-user-manager/CHANGELOG.md create mode 100644 user/plugins/admin-addon-user-manager/LICENSE create mode 100644 user/plugins/admin-addon-user-manager/README.md create mode 100644 user/plugins/admin-addon-user-manager/admin-addon-user-manager.php create mode 100644 user/plugins/admin-addon-user-manager/admin-addon-user-manager.yaml create mode 100644 user/plugins/admin-addon-user-manager/admin/pages/group-manager.md create mode 100644 user/plugins/admin-addon-user-manager/admin/pages/user-expert.md create mode 100644 user/plugins/admin-addon-user-manager/admin/pages/user-manager.md create mode 100644 user/plugins/admin-addon-user-manager/assets/groups/style.css create mode 100644 user/plugins/admin-addon-user-manager/assets/users/style.css create mode 100644 user/plugins/admin-addon-user-manager/blueprints.yaml create mode 100644 user/plugins/admin-addon-user-manager/blueprints/user/aaum-account.yaml create mode 100644 user/plugins/admin-addon-user-manager/blueprints/user/account-raw.yaml create mode 100644 user/plugins/admin-addon-user-manager/blueprints/user/group.yaml create mode 100644 user/plugins/admin-addon-user-manager/composer.json create mode 100644 user/plugins/admin-addon-user-manager/composer.lock create mode 100644 user/plugins/admin-addon-user-manager/docs/filter.md create mode 100644 user/plugins/admin-addon-user-manager/languages/cs.yaml create mode 100644 user/plugins/admin-addon-user-manager/languages/en.yaml create mode 100644 user/plugins/admin-addon-user-manager/languages/es.yaml create mode 100644 user/plugins/admin-addon-user-manager/languages/fr.yaml create mode 100644 user/plugins/admin-addon-user-manager/languages/no.yaml create mode 100644 user/plugins/admin-addon-user-manager/languages/pt.yaml create mode 100644 user/plugins/admin-addon-user-manager/languages/ru.yaml create mode 100644 user/plugins/admin-addon-user-manager/languages/sr.yaml create mode 100644 user/plugins/admin-addon-user-manager/languages/uk.yaml create mode 100644 user/plugins/admin-addon-user-manager/languages/zh.yaml create mode 100644 user/plugins/admin-addon-user-manager/modals.yaml create mode 100644 user/plugins/admin-addon-user-manager/src/Dot.php create mode 100644 user/plugins/admin-addon-user-manager/src/Group.php create mode 100644 user/plugins/admin-addon-user-manager/src/Groups/Manager.php create mode 100644 user/plugins/admin-addon-user-manager/src/Manager.php create mode 100644 user/plugins/admin-addon-user-manager/src/Pagination/ArrayPagination.php create mode 100644 user/plugins/admin-addon-user-manager/src/Pagination/Pagination.php create mode 100644 user/plugins/admin-addon-user-manager/src/Users/ExpertManager.php create mode 100644 user/plugins/admin-addon-user-manager/src/Users/Manager.php create mode 100644 user/plugins/admin-addon-user-manager/templates/forms/fields/button/button.html.twig create mode 100644 user/plugins/admin-addon-user-manager/templates/group-manager.html.twig create mode 100644 user/plugins/admin-addon-user-manager/templates/user-expert.html.twig create mode 100644 user/plugins/admin-addon-user-manager/templates/user-manager-macros.html.twig create mode 100644 user/plugins/admin-addon-user-manager/templates/user-manager.html.twig create mode 100644 user/plugins/admin-addon-user-manager/vendor/autoload.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/composer/ClassLoader.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/composer/LICENSE create mode 100644 user/plugins/admin-addon-user-manager/vendor/composer/autoload_classmap.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/composer/autoload_files.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/composer/autoload_namespaces.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/composer/autoload_psr4.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/composer/autoload_real.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/composer/autoload_static.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/composer/installed.json create mode 100644 user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/LICENSE create mode 100644 user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/build-phar.sh create mode 100644 user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/composer.json create mode 100644 user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey create mode 100644 user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc create mode 100644 user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/lib/random.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/other/build_phar.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/psalm-autoload.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/psalm.xml create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/cache/CHANGELOG.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/cache/LICENSE.txt create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/cache/README.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/cache/composer.json create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/cache/src/CacheException.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/cache/src/CacheItemInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/cache/src/CacheItemPoolInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/cache/src/InvalidArgumentException.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/container/.gitignore create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/container/LICENSE create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/container/README.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/container/composer.json create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/container/src/ContainerExceptionInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/container/src/ContainerInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/container/src/NotFoundExceptionInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/LICENSE create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/AbstractLogger.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/LogLevel.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/LoggerInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/LoggerTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/NullLogger.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/Test/DummyTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/Test/TestLogger.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/README.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/psr/log/composer.json create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/.gitignore create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/CacheInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/CacheTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/CallbackInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/ItemInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/LICENSE create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/README.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/TagAwareCacheInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/composer.json create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/AbstractAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/AdapterInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ApcuAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ArrayAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ChainAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/DoctrineAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/FilesystemAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/MemcachedAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/NullAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/PdoAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/PhpArrayAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/PhpFilesAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ProxyAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/Psr16Adapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/RedisAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/SimpleCacheAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TagAwareAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TraceableAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/CHANGELOG.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/CacheItem.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/DataCollector/CacheDataCollector.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CachePoolPass.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/DoctrineProvider.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Exception/CacheException.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Exception/InvalidArgumentException.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Exception/LogicException.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/LICENSE create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/LockRegistry.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/DefaultMarshaller.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/DeflateMarshaller.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/MarshallerInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/PruneableInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Psr16Cache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/README.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/ResettableInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/AbstractCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/ApcuCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/ArrayCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/ChainCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/DoctrineCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/FilesystemCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/MemcachedCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/NullCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/PdoCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/PhpArrayCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/PhpFilesCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/Psr6Cache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/RedisCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/TraceableCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/AbstractAdapterTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/AbstractTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ApcuTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ArrayTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ContractsTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/DoctrineTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/FilesystemCommonTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/FilesystemTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/MemcachedTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/PdoTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/PhpArrayTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/PhpFilesTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ProxyTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/RedisClusterProxy.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/RedisProxy.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/RedisTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/cache/composer.json create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/.gitignore create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/CHANGELOG.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Compiler.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Expression.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ExpressionFunction.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ExpressionFunctionProviderInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ExpressionLanguage.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/LICENSE create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Lexer.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ArgumentsNode.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ArrayNode.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/BinaryNode.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ConditionalNode.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ConstantNode.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/FunctionNode.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/GetAttrNode.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/NameNode.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/Node.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/UnaryNode.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParsedExpression.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Parser.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParserCache/ArrayParserCache.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParserCache/ParserCacheAdapter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParserCache/ParserCacheInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/README.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Resources/bin/generate_operator_regex.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/SerializedParsedExpression.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/SyntaxError.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ExpressionFunctionTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ExpressionLanguageTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ExpressionTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Fixtures/TestProvider.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/LexerTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/AbstractNodeTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ArgumentsNodeTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ArrayNodeTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/BinaryNodeTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ConditionalNodeTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ConstantNodeTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/FunctionNodeTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/GetAttrNodeTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/NameNodeTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/NodeTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/UnaryNodeTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ParsedExpressionTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ParserCache/ParserCacheAdapterTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ParserTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Token.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/TokenStream.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/composer.json create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/phpunit.xml.dist create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/LICENSE create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Php70.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/README.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Resources/stubs/Error.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/bootstrap.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/composer.json create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/.gitignore create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/LICENSE create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/README.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ResetInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceLocatorTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceProviderInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceSubscriberInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceSubscriberTrait.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/composer.json create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/CHANGELOG.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Exception/ExceptionInterface.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Instantiator.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Exporter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Hydrator.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Reference.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Registry.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Values.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/LICENSE create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/README.md create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/VarExporter.php create mode 100644 user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/composer.json create mode 100644 user/plugins/advanced-pagecache/CHANGELOG.md create mode 100644 user/plugins/advanced-pagecache/LICENSE create mode 100644 user/plugins/advanced-pagecache/README.md create mode 100644 user/plugins/advanced-pagecache/advanced-pagecache.php create mode 100644 user/plugins/advanced-pagecache/advanced-pagecache.yaml create mode 100644 user/plugins/advanced-pagecache/blueprints.yaml create mode 100644 user/plugins/aura/CHANGELOG.md create mode 100644 user/plugins/aura/LICENSE create mode 100644 user/plugins/aura/README.md create mode 100644 user/plugins/aura/assets/demo-composition-min.png create mode 100644 user/plugins/aura/aura.php create mode 100644 user/plugins/aura/aura.yaml create mode 100644 user/plugins/aura/blueprints.yaml create mode 100644 user/plugins/aura/blueprints/default.yaml create mode 100644 user/plugins/aura/classes/aura.php create mode 100644 user/plugins/aura/classes/image.php create mode 100644 user/plugins/aura/classes/organization.php create mode 100644 user/plugins/aura/classes/person.php create mode 100644 user/plugins/aura/classes/webpage.php create mode 100644 user/plugins/aura/classes/website.php diff --git a/user/plugins/admin-addon-user-manager/.gitattributes b/user/plugins/admin-addon-user-manager/.gitattributes new file mode 100644 index 0000000..2ce3627 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/.gitattributes @@ -0,0 +1,2 @@ +vendor/* linguist-generated=true +composer.lock linguist-generated=true \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/CHANGELOG.md b/user/plugins/admin-addon-user-manager/CHANGELOG.md new file mode 100644 index 0000000..aba3303 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/CHANGELOG.md @@ -0,0 +1,314 @@ +# v2.3.0 +## 05/04/2020 + +1. [](#new) + * Chinese translation (Thanks: https://github.com/dallaslu PR #68) + +2. [](#improved) + * Replace deprecated User::load (#66) + * Updated dependencies + * Better compatibility with v1.7 + +# v2.2.1 +## 12/11/2019 + +1. [](#bugfix) + * Fix YAML linting error (#63) + +# v2.2.0 +## 10/11/2019 + +1. [](#new) + * Brazilian Portuguese translation (Thanks: https://github.com/diegomagikal PR #61) + * Added "Enabled" toggle to user editor + +2. [](#bugfix) + * Fixed avatar upload + +# v2.1.8 +## 28/05/2019 + +1. [](#new) + * French translation (Thanks: https://github.com/Miaourt PR #58) + +2. [](#bugfix) + * Fixed a problem with saving groups (#59) + +# v2.1.7 +## 13/02/2019 + +1. [](#new) + * Serbian translation (Thanks: https://github.com/tomaja-linuxo PR #47) + * Russian translation (Thanks: https://github.com/Lufog-git PR #50) + * Ukrainian translation (Thanks: https://github.com/Lufog-git PR #51) + +2. [](#improved) + * Fixed some non-translatable strings (Thanks: https://github.com/Lufog-git PR #52) + +# v2.1.6 +## 06/06/2018 + +1. [](#bugfix) + * Fixed error when using 'Login As' feature with an user without admin permissions (#43) + +# v2.1.5 +## 09/04/2018 + +1. [](#bugfix) + * Fixed error when rendering front-end (#40) + +# v2.1.4 +## 09/04/2018 + +1. [](#improved) + * Moved 'site.login' permission to the front of permission list. (#36) + +# v2.1.3 +## 02/04/2018 + +1. [](#improved) + * Validate user object on save + +2. [](#bugfix) + * Fixed unset user permissions being pushed into the access array with an empty string value. Causing inherited permissions to be overwritten. (#38) + +# v2.1.2 +## 29/03/2018 + +1. [](#new) + * Norwegian translation (Thanks: https://github.com/achwell PR #37) + +# v2.1.1 +## 22/03/2018 + +1. [](#improved) + * Added 'site.login' permission to the permission list. (#36) + +# v2.1.0 +## 14/03/2018 + +1. [](#new) + * Czech translation (Thanks: https://github.com/07pepa Issue #29) + * Spanish translation (Thanks: https://github.com/filisko PR #31) + +2. [](#bugfix) + * Fixed user editor using wrong task when saving, which caused save error when you didn't have 'admin.super'. (#34) + * Added a temporary fix for user editor's permission area. The toggles moved below the permission's name at a specific width. (#22) + * Minor bugfixes + +# v2.0.3 +## 27/02/2018 + +1. [](#improved) + * Added missing translations + +# v2.0.2 +## 27/01/2018 + +1. [](#bugfix) + * Fixed wrong redirection after deleting an user (#28) + * Added missing translation for user delete confirmation + +# v2.0.1 +## 01/01/2018 + +1. [](#bugfix) + * Fixed admin links not working when something is changed in the form (#27) + +# v2.0.0 +## 29/12/2017 + +1. [](#new) + * 'Login As' button + +2. [](#bugfix) + * Fixed being redirected to the deleted user, now redirects to the user manager + * The delete button now shows up when editing the user + * Avatar upload now works + +# v1.9.1 +## 29/12/2017 + +1. [](#bugfix) + * Fixed 'Memory leak when using non-ascii character (?) to create group' (#26) + * Fixed being redirected to the deleted group, now redirects to the group manager + +# v1.9.0 +## 02/12/2017 + +1. [](#improved) + * Using custom blueprint for user editing (#23) + * Using custom request handler for saving user data (#23) + +# v1.8.1 +## 18/09/2017 + +1. [](#improved) + * Added username validating (#21) + +# v1.8.0 +## 14/08/2017 + +1. [](#new) + * Custom permissions (#18) + * User Expert editor (#19) + +# v1.7.1 +## 08/08/2017 + +1. [](#new) + * Permissions input in the bulk modal now accepts new values too + +# v1.7.0 +## 08/08/2017 + +1. [](#new) + * User permissions bulk actions + +# v1.6.1 +## 08/06/2017 + +1. [](#bugfix) + * Fixed removing of user from group not working. (#16, #17 Moonlight63 ) + +# v1.6.0 +## 07/31/2017 + +1. [](#new) + * User bulk actions + * Group bulk actions + +# v1.5.4 +## 07/28/2017 + +1. [](#bugfix) + * Fixed groups.yaml is not created when saving a group for the first time + +# v1.5.3 +## 07/28/2017 + +1. [](#bugfix) + * Fixed group creating not working properly + +# v1.5.2 +## 07/28/2017 + +1. [](#bugfix) + * Fixed an error which appeared when there are no groups.yaml + +# v1.5.1 +## 07/28/2017 + +1. [](#bugfix) + * Better PHP compatibility + +# v1.5.0 +## 07/28/2017 + +1. [](#new) + * Filter users + * Filter groups + +# v1.4.3 +## 07/27/2017 + +1. [](#new) + * Users count are now shown at group manager + * Users now can be added and/or removed from the group you are currently editing + +# v1.4.2 +## 07/27/2017 + +1. [](#improved) + * Pagination performance improvement + +2. [](#bugfix) + * Fixed group name is not shown when editing a group + +# v1.4.1 +## 07/27/2017 + +1. [](#improved) + * Permissions support + +# v1.4.0 +## 07/27/2017 + +1. [](#feature) + * Groups management! + +# v1.3.4 +## 07/24/2017 + +1. [](#improved) + * Plugin is now compatible with Grav Admin Styles Plugin + +# v1.3.3 +## 07/20/2017 + +1. [](#new) + * Users to be shown per page is now configurable + * Default list style is now configurable + +2. [](#improved) + * Refactored code a bit + +# v1.3.2 +## 07/20/2017 + +1. [](#improved) + * Performance is improved when admin cache is disabled + +1. [](#bugfix) + * Fixed plugin not working when admin cache is disabled + +# v1.3.1 +## 07/19/2017 + +1. [](#improved) + * Redirects to last URL after user delete + * Prevents cache refresh after user delete (performance improvement) + +2. [](#bugfix) + * Params are now validated + +# v1.3.0 +## 07/19/2017 + +1. [](#improved) + * Pagination is now more user friendly + * Users are cached (better performance) + +# v1.2.0 +## 07/19/2017 + +1. [](#feature) + * Added pagination + * Added list style + +# v1.1.2 +## 07/19/2017 + +1. [](#improved) + * No more page jumping because of avatars loading + +# v1.1.1 +## 07/06/2017 + +1. [](#bugfix) + * Plugin is now compatible with PHP 5.5 + +# v1.1.0 +## 07/03/2017 + +1. [](#new) + * Delete users from the UI + +2. [](#improved) + * Revamped UI + +# v1.0.0 +## 06/27/2017 + +1. [](#new) + * ChangeLog started... diff --git a/user/plugins/admin-addon-user-manager/LICENSE b/user/plugins/admin-addon-user-manager/LICENSE new file mode 100644 index 0000000..f5412ab --- /dev/null +++ b/user/plugins/admin-addon-user-manager/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Dávid Szabó + +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/user/plugins/admin-addon-user-manager/README.md b/user/plugins/admin-addon-user-manager/README.md new file mode 100644 index 0000000..7330da2 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/README.md @@ -0,0 +1,39 @@ +# Admin Addon User Manager Plugin + +The **Admin Addon User Manager** Plugin is for [Grav CMS](http://github.com/getgrav/grav). A simple admin panel extension which adds the option to manage users and groups. + +## Installation + +Installing the Admin Addon User Manager plugin can be done in one of two ways. The GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file. + +### GPM Installation (Preferred) + +The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's terminal (also called the command line). From the root of your Grav install type: + + bin/gpm install admin-addon-user-manager + +This will install the Admin Addon User Manager plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/admin-addon-user-manager`. + +### Manual Installation + +To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `admin-addon-user-manager`. You can find these files on [GitHub](https://github.com/d-vid-szab-/grav-plugin-admin-addon-user-manager) or via [GetGrav.org](http://getgrav.org/downloads/plugins#extras). + +You should now have all the plugin files under + + /your/site/grav/user/plugins/admin-addon-user-manager + +> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav) and the [Admin](https://github.com/getgrav/grav-plugin-admin) plugin to operate. + +## Configuration + +Before configuring this plugin, you should copy the `user/plugins/admin-addon-user-manager/admin-addon-user-manager.yaml` to `user/config/plugins/admin-addon-user-manager.yaml` and only edit that copy. + +Here is the default configuration and an explanation of available options: + +```yaml +enabled: true +``` + +## Usage + +Install the plugin and you are good to go. 'User Manager' and 'Group Manager' will appear in the sidebar of the admin panel. diff --git a/user/plugins/admin-addon-user-manager/admin-addon-user-manager.php b/user/plugins/admin-addon-user-manager/admin-addon-user-manager.php new file mode 100644 index 0000000..5a9ccfa --- /dev/null +++ b/user/plugins/admin-addon-user-manager/admin-addon-user-manager.php @@ -0,0 +1,157 @@ +name; + + return ($key !== null) ? $pluginKey . '.' . $key : $pluginKey; + } + + public function getPluginConfigValue($key = null, $default = null) { + return $this->config->get($this->getPluginConfigKey($key), $default); + } + + public function getConfigValue($key, $default = null) { + return $this->config->get($key, $default); + } + + public function getPreviousUrl() { + return $this->grav['session']->{$this->name . '.previous_url'}; + } + + public function getModalsConfiguration() { + return CompiledYamlFile::instance(__DIR__ . DS . 'modals.yaml')->content(); + } + + public static function getSubscribedEvents() { + return [ + 'onPluginsInitialized' => ['onPluginsInitialized', 0], + 'onAdminRegisterPermissions' => ['onAdminRegisterPermissions', 1000] + ]; + } + + public function onPluginsInitialized() { + if (!$this->isAdmin() || !$this->grav['user']->authenticated) { + return; + } + + $this->grav['locator']->addPath('blueprints', '', __DIR__ . DS . 'blueprints'); + + include __DIR__ . DS . 'vendor' . DS . 'autoload.php'; + + $this->managers[] = new UsersManager($this->grav, $this); + $this->managers[] = new GroupsManager($this->grav, $this); + $this->managers[] = new UsersExpertManager($this->grav, $this); + + $this->enable([ + 'onAdminTwigTemplatePaths' => ['onAdminTwigTemplatePaths', -10], + 'onTwigSiteVariables' => ['onTwigSiteVariables', 0], + 'onAdminMenu' => ['onAdminMenu', 0], + 'onAssetsInitialized' => ['onAssetsInitialized', 0], + 'onAdminTaskExecute' => ['onAdminTaskExecute', 0], + ]); + + $this->registerPermissions(); + } + + public function onAssetsInitialized() { + $assets = $this->grav['assets']; + + foreach ($this->managers as $manager) { + $manager->initializeAssets($assets); + } + } + + public function onAdminMenu() { + $twig = $this->grav['twig']; + $twig->plugins_hooked_nav = (isset($twig->plugins_hooked_nav)) ? $twig->plugins_hooked_nav : []; + + foreach ($this->managers as $manager) { + $nav = $manager->getNav(); + if ($nav) { + $twig->plugins_hooked_nav[$nav['label']] = $nav; + } + } + } + + public function onAdminTwigTemplatePaths($e) { + $paths = $e['paths']; + $paths[] = __DIR__ . DS . 'templates'; + $e['paths'] = $paths; + } + + public function onTwigSiteVariables() { + $page = $this->grav['page']; + $twig = $this->grav['twig']; + $session = $this->grav['session']; + $uri = $this->grav['uri']; + + foreach ($this->managers as $manager) { + if ($page->slug() === $manager->getLocation() && $this->grav['admin']->authorize(['admin.super', $manager->getRequiredPermission()])) { + $session->{$this->name . '.previous_url'} = $uri->route() . $uri->params(); + + $page = $this->grav['admin']->page(true); + $page->id('aaum-' . implode('/', $uri->paths())); + + $twig->twig_vars['context'] = $page; + + $vars = $manager->handleRequest(); + $twig->twig_vars = array_merge($twig->twig_vars, $vars); + + return true; + } + } + } + + public function onAdminTaskExecute($e) { + foreach ($this->managers as $manager) { + if ($this->grav['admin']->authorize(['admin.super', $manager->getRequiredPermission()])) { + $result = $manager->handleTask($e); + + if ($result) { + return true; + } + } + } + + return false; + } + + public function registerPermissions() { + foreach ($this->managers as $manager) { + $this->grav['admin']->addPermissions([$manager->getRequiredPermission() => 'boolean']); + } + + // Custom permissions + $customPermissions = $this->getPluginConfigValue('custom_permissions', []); + foreach ($customPermissions as $permission) { + $this->grav['admin']->addPermissions([$permission => 'boolean']); + } + } + + public function onAdminRegisterPermissions() { + if (!$this->isAdmin() || !$this->grav['user']->authenticated) { + return; + } + + $this->grav['admin']->addPermissions(['site.login' => 'boolean']); + } + +} diff --git a/user/plugins/admin-addon-user-manager/admin-addon-user-manager.yaml b/user/plugins/admin-addon-user-manager/admin-addon-user-manager.yaml new file mode 100644 index 0000000..953d197 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/admin-addon-user-manager.yaml @@ -0,0 +1,4 @@ +enabled: true +default_list_style: list +pagination: + per_page: 20 \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/admin/pages/group-manager.md b/user/plugins/admin-addon-user-manager/admin/pages/group-manager.md new file mode 100644 index 0000000..41522a7 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/admin/pages/group-manager.md @@ -0,0 +1,7 @@ +--- +title: Group Manager +access: + admin.groups: true + admin.login: true + admin.super: true +--- diff --git a/user/plugins/admin-addon-user-manager/admin/pages/user-expert.md b/user/plugins/admin-addon-user-manager/admin/pages/user-expert.md new file mode 100644 index 0000000..80753c8 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/admin/pages/user-expert.md @@ -0,0 +1,7 @@ +--- +title: User Expert +access: + admin.users_expert: true + admin.login: true + admin.super: true +--- diff --git a/user/plugins/admin-addon-user-manager/admin/pages/user-manager.md b/user/plugins/admin-addon-user-manager/admin/pages/user-manager.md new file mode 100644 index 0000000..c1c6ed1 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/admin/pages/user-manager.md @@ -0,0 +1,7 @@ +--- +title: User Manager +access: + admin.users: true + admin.login: true + admin.super: true +--- diff --git a/user/plugins/admin-addon-user-manager/assets/groups/style.css b/user/plugins/admin-addon-user-manager/assets/groups/style.css new file mode 100644 index 0000000..f2c7027 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/assets/groups/style.css @@ -0,0 +1,3 @@ +.admin-addon-user-manager--group.admin-addon-user-manager--list .user__username { + flex: 1 0 auto; +} \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/assets/users/style.css b/user/plugins/admin-addon-user-manager/assets/users/style.css new file mode 100644 index 0000000..611a22a --- /dev/null +++ b/user/plugins/admin-addon-user-manager/assets/users/style.css @@ -0,0 +1,227 @@ +#admin-main .admin-block .admin-addon-user-manager h2 { + text-align: center; + font-size: 2rem; +} + +/** + * Style chooser + */ +.admin-addon-user-manager-style { + float: right; + overflow: hidden; + font-size: 1.5em; + margin: 0 16px; +} + +.admin-addon-user-manager-style i { + padding-left: 8px; +} + +.admin-addon-user-manager .cell--header { + font-weight: bold; +} + +/** + * Pagination + */ +.admin-addon-user-manager-pagination { + margin: 16px auto; + padding: 0; + text-align: center; +} + +.admin-addon-user-manager-pagination ul { + list-style: none; + margin: 0 auto; + overflow: hidden; + clear: both; + display: inline-block; + padding: 0; +} + +.admin-addon-user-manager-pagination li { + padding: 4px 8px; + border: 1px solid rgba(0, 0, 0, .24); + float: left; +} + +.admin-addon-user-manager-pagination li.current { + background: #0082ba; + color: white; +} + +.admin-addon-user-manager-pagination li + li { + border-left: 0; +} + +/** + * Grid style + */ +.admin-addon-user-manager--grid { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + +.admin-addon-user-manager--grid .cell { + flex: 0 0 100%; +} + +.admin-addon-user-manager--grid .user { + margin: 16px; + border-radius: 2px; + box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24); +} + +.admin-addon-user-manager--grid .user__avatar { + display: block; + width: 100%; + height: 0; + position: relative; + padding-top: 100%; + overflow: hidden; +} + +.admin-addon-user-manager--grid .user__avatar img { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: auto; + max-width: 100%; + display: block; + border-radius: 2px 2px 0 0; + object-fit: cover; +} + +.admin-addon-user-manager--grid .user__username { + padding: 8px; +} + +.admin-addon-user-manager--grid .user__email { + padding: 0 8px 8px 8px; +} + +.admin-addon-user-manager--grid .user__actions { + padding: 8px; + border-top: 1px solid rgba(0, 0, 0, .14); + overflow: hidden; + display: flex; + justify-content: flex-end; +} + +.admin-addon-user-manager--grid .user__actions > a { + text-transform: uppercase; +} + +.admin-addon-user-manager--grid .user__actions > a + a { + padding-left: 8px; +} + +#admin-main .admin-addon-user-manager--grid .user__actions > a.delete { + color: #f44336; +} + +#admin-main .admin-addon-user-manager--grid .user__actions > a.delete:hover { + color: #f6685e; +} + +@media only screen and (min-width: 480px) { + .admin-addon-user-manager--grid .cell { + flex: 0 0 50%; + } +} + +@media only screen and (min-width: 768px) { + .admin-addon-user-manager--grid .cell { + flex: 0 0 33.333%; + } +} + +@media only screen and (min-width: 1400px) { + .admin-addon-user-manager--grid .cell { + flex: 0 0 20%; + } +} + +/** + * List style + */ +.admin-addon-user-manager--list { + width: 100%; + display: flex; + flex-direction: column; + padding: 16px; +} + +.admin-addon-user-manager--list .cell + .cell { + border-top: 1px solid rgba(0, 0, 0, .24); + padding-top: 8px; + margin-top: 8px; +} + +.admin-addon-user-manager--list .user { + display: flex; + flex-direction: row; +} + +.admin-addon-user-manager--list .user > * { + padding: 0 8px; +} + +.admin-addon-user-manager--list .user__checkbox { + flex: 0 0 auto; +} + +.admin-addon-user-manager--list .user__username { + flex: 0 0 25%; +} + +.admin-addon-user-manager--list .user__email { + flex: 1 0 auto; +} + +.admin-addon-user-manager--list .user__actions { + flex: 0 0 20%; +} + +/** + * Filter + */ +.admin-addon-user-manager-filter { + display: flex; +} + +.admin-addon-user-manager-filter__input { + flex: 1 0 auto; +} + +.admin-addon-user-manager-filter__input .form-data { + padding-right: 1rem; +} + +.admin-addon-user-manager-filter__help { + flex: 0 0 auto; + padding-right: 2rem; +} + +#admin-main .admin-block .admin-addon-user-manager-filter__help .button { + padding-top: .425rem; + padding-bottom: .425rem; +} + +/** + * Bulk + */ +.admin-addon-user-manager-bulk-action { + margin-left: 16px; +} + +.button.warning:hover { + background: red; +} + +.remodal[data-remodal-id="modal-admin-addon-user-manager-bulk"] input[type="checkbox"] { + display: none; +} \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/blueprints.yaml b/user/plugins/admin-addon-user-manager/blueprints.yaml new file mode 100644 index 0000000..8782664 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/blueprints.yaml @@ -0,0 +1,58 @@ +name: Admin Addon User Manager +version: 2.3.0 +description: A simple admin panel extension which adds the option to manage users and groups +icon: plug +author: + name: Dávid Szabó + email: david.szabo97@gmail.com +homepage: https://github.com/david-szabo97/grav-plugin-admin-addon-user-manager +keywords: grav, plugin, admin, media +bugs: https://github.com/david-szabo97/grav-plugin-admin-addon-user-manager/issues +docs: https://github.com/david-szabo97/grav-plugin-admin-addon-user-manager/blob/master/README.md +license: MIT + +dependencies: + - { name: grav, version: '>=1.0.0' } + - { name: admin, version: '>=1.0.0' } + +form: + validation: strict + fields: + enabled: + type: toggle + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + default_list_style: + label: PLUGIN_ADMIN_ADDON_USER_MANAGER.DEFAULT_LIST_STYLE + type: select + options: + grid: PLUGIN_ADMIN_ADDON_USER_MANAGER.GRID + list: PLUGIN_ADMIN_ADDON_USER_MANAGER.LIST + + pagination.per_page: + label: PLUGIN_ADMIN_ADDON_USER_MANAGER.USERS_PER_PAGE + type: select + options: + 10: 10 + 20: 20 + 30: 30 + 40: 40 + 50: 50 + 60: 60 + 70: 70 + 80: 80 + 90: 90 + 100: 100 + + custom_permissions: + label: PLUGIN_ADMIN_ADDON_USER_MANAGER.CUSTOM_PERMISSIONS + type: array + placeholder_value: PLUGIN_ADMIN_ADDON_USER_MANAGER.CUSTOM_PERMISSIONS_PLACEHOLDER + value_only: true \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/blueprints/user/aaum-account.yaml b/user/plugins/admin-addon-user-manager/blueprints/user/aaum-account.yaml new file mode 100644 index 0000000..0770bf6 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/blueprints/user/aaum-account.yaml @@ -0,0 +1,20 @@ +extends@: + type: user/account + context: blueprints:// + +form: + fields: + security: + unset-security@: true + state: + ordering@: content + type: toggle + label: PLUGIN_ADMIN.ENABLED + classes: twofa-toggle + highlight: enabled + default: enabled + options: + enabled: PLUGIN_ADMIN.YES + disabled: PLUGIN_ADMIN.NO + validate: + type: string \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/blueprints/user/account-raw.yaml b/user/plugins/admin-addon-user-manager/blueprints/user/account-raw.yaml new file mode 100644 index 0000000..facaa4f --- /dev/null +++ b/user/plugins/admin-addon-user-manager/blueprints/user/account-raw.yaml @@ -0,0 +1,16 @@ +title: User +form: + fields: + raw: + type: editor + label: Raw + autofocus: true + codemirror: + mode: 'yaml' + indentUnit: 4 + autofocus: true + indentWithTabs: false + lineNumbers: true + styleActiveLine: true + gutters: ['CodeMirror-lint-markers'] + lint: true \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/blueprints/user/group.yaml b/user/plugins/admin-addon-user-manager/blueprints/user/group.yaml new file mode 100644 index 0000000..0abadb8 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/blueprints/user/group.yaml @@ -0,0 +1,43 @@ +title: Group +form: + validation: loose + + fields: + groupname: + type: text + size: large + label: PLUGIN_ADMIN.NAME + readonly: true + + readableName: + type: text + size: large + label: PLUGIN_ADMIN_ADDON_USER_MANAGER.READABLE_NAME + + description: + type: text + size: large + label: PLUGIN_ADMIN.DESCRIPTION + + icon: + type: text + size: small + label: PLUGIN_ADMIN_ADDON_USER_MANAGER.ICON + + access: + type: permissions + label: PLUGIN_ADMIN.PERMISSIONS + ignore_empty: true + validate: + type: array + + users: + type: select + multiple: true + size: large + label: PLUGIN_ADMIN_ADDON_USER_MANAGER.USERS + data-options@: '\AdminAddonUserManager\Users\Manager::userNames' + classes: fancy + help: PLUGIN_ADMIN_ADDON_USER_MANAGER.USERS_HELP + validate: + type: commalist \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/composer.json b/user/plugins/admin-addon-user-manager/composer.json new file mode 100644 index 0000000..aa7ac23 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/composer.json @@ -0,0 +1,10 @@ +{ + "autoload": { + "psr-4": { + "AdminAddonUserManager\\": "src/" + } + }, + "require": { + "symfony/expression-language": "^3.3" + } +} diff --git a/user/plugins/admin-addon-user-manager/composer.lock b/user/plugins/admin-addon-user-manager/composer.lock new file mode 100644 index 0000000..b5278cd --- /dev/null +++ b/user/plugins/admin-addon-user-manager/composer.lock @@ -0,0 +1,570 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a47ed7fe21ccca95f49cff0e9e3b871e", + "packages": [ + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "time": "2018-07-02T15:55:56+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "symfony/cache", + "version": "v4.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "f777b570291aebe51081b9827e05f3a747665e87" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/f777b570291aebe51081b9827e05f3a747665e87", + "reference": "f777b570291aebe51081b9827e05f3a747665e87", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/cache": "~1.0", + "psr/log": "~1.0", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/service-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.2|^5.0" + }, + "conflict": { + "doctrine/dbal": "<2.5", + "symfony/dependency-injection": "<3.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0", + "symfony/cache-implementation": "1.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.5", + "predis/predis": "~1.1", + "psr/simple-cache": "^1.0", + "symfony/config": "^4.2|^5.0", + "symfony/dependency-injection": "^3.4|^4.1|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "time": "2020-03-27T16:54:36+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16", + "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "psr/cache": "^1.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-11-18T17:27:11+00:00" + }, + { + "name": "symfony/expression-language", + "version": "v3.4.39", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "206165f46c660f3231df0afbdeec6a62f81afc59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/206165f46c660f3231df0afbdeec6a62f81afc59", + "reference": "206165f46c660f3231df0afbdeec6a62f81afc59", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/cache": "~3.1|~4.0", + "symfony/polyfill-php70": "~1.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ExpressionLanguage Component", + "homepage": "https://symfony.com", + "time": "2020-03-16T08:31:04+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "2a18e37a489803559284416df58c71ccebe50bf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/2a18e37a489803559284416df58c71ccebe50bf0", + "reference": "2a18e37a489803559284416df58c71ccebe50bf0", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0|~9.99", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2020-02-27T09:26:54+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "144c5e51266b281231e947b51223ba14acf1a749" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", + "reference": "144c5e51266b281231e947b51223ba14acf1a749", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-11-18T17:27:11+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v5.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "ffd29a70370e466343e33154b5df197a07a13afa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/ffd29a70370e466343e33154b5df197a07a13afa", + "reference": "ffd29a70370e466343e33154b5df197a07a13afa", + "shasum": "" + }, + "require": { + "php": "^7.2.5" + }, + "require-dev": { + "symfony/var-dumper": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "time": "2020-03-27T16:56:45+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/user/plugins/admin-addon-user-manager/docs/filter.md b/user/plugins/admin-addon-user-manager/docs/filter.md new file mode 100644 index 0000000..5ad9cac --- /dev/null +++ b/user/plugins/admin-addon-user-manager/docs/filter.md @@ -0,0 +1,67 @@ +# Filter expression + +## Users +### Available variables +* `user.username` username of the user +* `user.email` email of the user +* `user.fullName` full name of the user +* `user.title` title of the user +* `user.language` language of the user +* `user.groups` array of the groups the user is in +* `user.access` an array which contains the permissions of the user + +### Available methods +* `user.authorize('example.permission')` checks whether user has access to the given permission or not (take groups into account) + +### Examples +* filter users by permissions + ``` + user.authorize('admin.super') + ``` +* show users who are in the 'paid' group + ``` + 'paid' in user.groups + ``` +* show users without groups + ``` + count(user.groups) > 0 + ``` +* show users with access to 'admin.users' + ``` + group.authorize('admin.users') and groups.users > 0 +* show users with gmail email provider + ``` + user.email matches '/@gmail.com/' + ``` + +## Groups +### Available variables +* `group.groupname` name of the group +* `group.readableName` readable name of the group +* `group.description` description of the group +* `group.icon` icon of the group +* `group.access` an array which contains the permissions of the group + +### Available methods +* `group.authorize('example.permission')` checks whether group has access to the given permission or not + +### Examples +* filter groups by permissions + ``` + group.authorize('admin.super') + ``` +* show groups with more than 5 users + ``` + group.users > 5 + ``` +* show empty groups + ``` + group.users == 0 + ``` +* show groups with access to 'admin.users' and not empty + ``` + group.authorize('admin.users') and groups.users > 0 +* show groups which contains 'admin' in its description + ``` + group.description matches '/admin/' + ``` \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/languages/cs.yaml b/user/plugins/admin-addon-user-manager/languages/cs.yaml new file mode 100644 index 0000000..c68cc17 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/languages/cs.yaml @@ -0,0 +1,34 @@ +PLUGIN_ADMIN_ADDON_USER_MANAGER: + USER_MANAGER: Správce uživatelů + GROUP_MANAGER: Správce skupin + FILTER_ERROR: Chyba filtru + FILTER_USERS: Filtruj uživatele + FILTER_GROUPS: Filtruj skupiny + NO_RESULTS: Žádné shody + EXPERT: Expert + PAGINATION_SUMMARY: Zobrazeno %d - %d z %d + BULK_ACTION: Hromadné akce + ACTIONS: Akce + CUSTOM_PERMISSIONS: Specifická opravnění + CUSTOM_PERMISSIONS_PLACEHOLDER: "opravnění (např. 'site.login')" + USERS_PER_PAGE: Uživatelů na stránku + DEFAULT_LIST_STYLE: Výchozí styl listu + GRID: Mřížka + LIST: Stránka + GROUP: Skupina + GROUPS: Skupiny + READABLE_NAME: Zobrazené jméno + ICON: Ikona + ADD_GROUP: Přidat skupinu + USERS: Uživatelé + HELP: Pomoc + BULK_DELETE_USER: Delete selected users + BULK_DELETE: Delete + BULK_USER_GROUP: Bulk user actions related to groups + BULK_ADD_GROUP: Add selected users to selected groups + BULK_REMOVE_GROUP: Odeber vybrané uživatele + BULK_DELETE_GROUP: Odeber vybrané skupiny + BULK_ADD_PERMISSIONS: Přidej vybraná opravnění vybraným + BULK_REMOVE_PERMISSIONS: Odeber vybraná opravnění vybraným uživatelům + LOGIN_AS: Přihlaš se jako + USER_CONFIRM_DELETE: Opravdu chcete smazat tohoto uživatele? diff --git a/user/plugins/admin-addon-user-manager/languages/en.yaml b/user/plugins/admin-addon-user-manager/languages/en.yaml new file mode 100644 index 0000000..d937e48 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/languages/en.yaml @@ -0,0 +1,34 @@ +PLUGIN_ADMIN_ADDON_USER_MANAGER: + USER_MANAGER: Users Manager + GROUP_MANAGER: Groups Manager + FILTER_ERROR: Filter Error + FILTER_USERS: Filter users + FILTER_GROUPS: Filter groups + NO_RESULTS: No results + EXPERT: Expert + PAGINATION_SUMMARY: Showing %d - %d of %d + BULK_ACTION: Bulk Action + ACTIONS: Actions + CUSTOM_PERMISSIONS: Custom permissions + CUSTOM_PERMISSIONS_PLACEHOLDER: "Permission (e.g. 'site.login')" + USERS_PER_PAGE: Users per page + DEFAULT_LIST_STYLE: Default list style + GRID: Grid + LIST: List + GROUP: Group + GROUPS: Groups + READABLE_NAME: Readable name + ICON: Icon + ADD_GROUP: Add Group + USERS: Users + HELP: Help + BULK_DELETE_USER: Delete selected users + BULK_DELETE: Delete + BULK_USER_GROUP: Bulk user actions related to groups + BULK_ADD_GROUP: Add selected users to selected groups + BULK_REMOVE_GROUP: Remove selected users from selected groups + BULK_DELETE_GROUP: Delete selected groups + BULK_ADD_PERMISSIONS: Add selected permissions to selected users + BULK_REMOVE_PERMISSIONS: Remove selected permissions from selected users + LOGIN_AS: Login as + USER_CONFIRM_DELETE: Are you sure you want to delete this user? diff --git a/user/plugins/admin-addon-user-manager/languages/es.yaml b/user/plugins/admin-addon-user-manager/languages/es.yaml new file mode 100644 index 0000000..e602423 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/languages/es.yaml @@ -0,0 +1,34 @@ +PLUGIN_ADMIN_ADDON_USER_MANAGER: + USER_MANAGER: Usuarios + GROUP_MANAGER: Grupos + FILTER_ERROR: Error de filtro + FILTER_USERS: Filtrar usuarios + FILTER_GROUPS: Filtrar grupos + NO_RESULTS: Sin resultados + EXPERT: Experto + PAGINATION_SUMMARY: Mostrando %d - %d de %d + BULK_ACTION: Acciones en masa + ACTIONS: Acciones + CUSTOM_PERMISSIONS: Permisos customizados + CUSTOM_PERMISSIONS_PLACEHOLDER: "Permiso (e.g. 'site.login')" + USERS_PER_PAGE: Usuarios por página + DEFAULT_LIST_STYLE: Estilo de lista predeterminado + GRID: Grid + LIST: Lista + GROUP: Grupo + GROUPS: Grupos + READABLE_NAME: Nombre legible + ICON: Icono + ADD_GROUP: Añadir Grupo + USERS: Usuarios + HELP: Ayuda + BULK_DELETE_USER: Eliminar usuarios seleccionados + BULK_DELETE: Eliminar + BULK_USER_GROUP: Acciones masivas de usuarios relacionadas con grupos + BULK_ADD_GROUP: Agregar usuarios seleccionados a los grupos seleccionados + BULK_REMOVE_GROUP: Eliminar usuarios seleccionados de los grupos seleccionados + BULK_DELETE_GROUP: Eliminar grupos seleccionados + BULK_ADD_PERMISSIONS: Añadir permisos seleccionados a los usuarios seleccionados + BULK_REMOVE_PERMISSIONS: Eliminar permisos seleccionados de los usuarios seleccionados + LOGIN_AS: Entrar como + USER_CONFIRM_DELETE: ¿Estás seguro de que deseas eliminar este usuario? diff --git a/user/plugins/admin-addon-user-manager/languages/fr.yaml b/user/plugins/admin-addon-user-manager/languages/fr.yaml new file mode 100644 index 0000000..6c9a03f --- /dev/null +++ b/user/plugins/admin-addon-user-manager/languages/fr.yaml @@ -0,0 +1,34 @@ +PLUGIN_ADMIN_ADDON_USER_MANAGER: + USER_MANAGER: Utilisateurs + GROUP_MANAGER: Groupes + FILTER_ERROR: Erreur de filtre + FILTER_USERS: Filtrer les utilisateurs + FILTER_GROUPS: Filtrer les groupes + NO_RESULTS: Pas de resultats + EXPERT: Expert + PAGINATION_SUMMARY: Afficher %d - %d de %d + BULK_ACTION: Action en bloc + ACTIONS: Actions + CUSTOM_PERMISSIONS: Permissions personnalisées + CUSTOM_PERMISSIONS_PLACEHOLDER: "Permission (par ex. 'site.login')" + USERS_PER_PAGE: Utilisateurs par page + DEFAULT_LIST_STYLE: Style de liste par défaut + GRID: Grille + LIST: Liste + GROUP: Groupe + GROUPS: Groupes + READABLE_NAME: Nom lisible + ICON: Icone + ADD_GROUP: Ajouter un groupe + USERS: Utilisateurs + HELP: Aide + BULK_DELETE_USER: Supprimer les utilisateurs sélectionnés + BULK_DELETE: Supprimer + BULK_USER_GROUP: Actions en bloc liées aux groupes + BULK_ADD_GROUP: Ajouter des utilisateurs sélectionnés aux groupes sélectionnés + BULK_REMOVE_GROUP: Supprimer les utilisateurs sélectionnés des groupes sélectionnés + BULK_DELETE_GROUP: Supprimer les groupes sélectionnés + BULK_ADD_PERMISSIONS: Ajouter les permissions sélectionnées aux utilisateurs sélectionnés + BULK_REMOVE_PERMISSIONS: Supprimer les permissions sélectionnées des utilisateurs sélectionnés + LOGIN_AS: Se connecter en tant que + USER_CONFIRM_DELETE: Êtes-vous sûr de vouloir supprimer cet utilisateur ? diff --git a/user/plugins/admin-addon-user-manager/languages/no.yaml b/user/plugins/admin-addon-user-manager/languages/no.yaml new file mode 100644 index 0000000..55d9caf --- /dev/null +++ b/user/plugins/admin-addon-user-manager/languages/no.yaml @@ -0,0 +1,34 @@ +PLUGIN_ADMIN_ADDON_USER_MANAGER: + USER_MANAGER: Brukere + GROUP_MANAGER: Grupper + FILTER_ERROR: Ingen treff + FILTER_USERS: Filtrer brukere + FILTER_GROUPS: Filtrer grupper + NO_RESULTS: Ingen treff + EXPERT: Ekspert + PAGINATION_SUMMARY: Viser %d - %d av %d + BULK_ACTION: Bulk Handlinger + ACTIONS: Handlinger + CUSTOM_PERMISSIONS: Tilganger + CUSTOM_PERMISSIONS_PLACEHOLDER: "Tilganger (f.eks. 'site.login')" + USERS_PER_PAGE: Brukere pr side + DEFAULT_LIST_STYLE: Standard listevisning + GRID: Rutenett + LIST: Liste + GROUP: Gruppe + GROUPS: Grupper + READABLE_NAME: Lesbart navn + ICON: Ikon + ADD_GROUP: Legg til gruppe + USERS: Brukere + HELP: Hjelp + BULK_DELETE_USER: Slett valgte brukere + BULK_DELETE: Slett + BULK_USER_GROUP: Bulk brukerhandlinger relatert til grupper + BULK_ADD_GROUP: Legg valgte brukere til valgte grupper + BULK_REMOVE_GROUP: Fjern valgte brukere fra valgte grupper + BULK_DELETE_GROUP: Slett valgte grupper + BULK_ADD_PERMISSIONS: Legg valgte rettigheter til valgte brukere + BULK_REMOVE_PERMISSIONS: Fjern valgte rettigheter fra valgte brukere + LOGIN_AS: Logg inn som + USER_CONFIRM_DELETE: Er du sikker på at du vil slette denne brukeren? diff --git a/user/plugins/admin-addon-user-manager/languages/pt.yaml b/user/plugins/admin-addon-user-manager/languages/pt.yaml new file mode 100644 index 0000000..98b7b78 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/languages/pt.yaml @@ -0,0 +1,34 @@ +PLUGIN_ADMIN_ADDON_USER_MANAGER: + USER_MANAGER: Gerenciar usuários + GROUP_MANAGER: Gerenciar grupos + FILTER_ERROR: Erro no filtro + FILTER_USERS: Filtrar usuários + FILTER_GROUPS: Filtrar grupos + NO_RESULTS: Nenhum resultado + EXPERT: Avançado + PAGINATION_SUMMARY: Mostrando %d - %d de %d + BULK_ACTION: Ações em massa + ACTIONS: Ações + CUSTOM_PERMISSIONS: Permissões personalizadas + CUSTOM_PERMISSIONS_PLACEHOLDER: "Permissão (ex. 'site.login')" + USERS_PER_PAGE: Usuários por página + DEFAULT_LIST_STYLE: Estilo padrão da listagem + GRID: Grade + LIST: Lista + GROUP: Grupo + GROUPS: Grupos + READABLE_NAME: Nome de leitura + ICON: Ícone + ADD_GROUP: Adicionar grupo + USERS: Usuários + HELP: Ajuda + BULK_DELETE_USER: Excluir usuários slecionados + BULK_DELETE: Excluir + BULK_USER_GROUP: Ações em massa do usuário relacionadas a grupos + BULK_ADD_GROUP: Adicionar usuários selecionados aos grupos selecionados + BULK_REMOVE_GROUP: Remover usuários selecionados dos grupos selecionados + BULK_DELETE_GROUP: Excluir grupos selecionados + BULK_ADD_PERMISSIONS: Adicionar permissões selecionadas aos usuários selecionados + BULK_REMOVE_PERMISSIONS: Remover permissões selecionadas dos usuários selecionados + LOGIN_AS: Entrar como + USER_CONFIRM_DELETE: Você tem certeza que deseja excluir este usuário? diff --git a/user/plugins/admin-addon-user-manager/languages/ru.yaml b/user/plugins/admin-addon-user-manager/languages/ru.yaml new file mode 100644 index 0000000..6935dcc --- /dev/null +++ b/user/plugins/admin-addon-user-manager/languages/ru.yaml @@ -0,0 +1,34 @@ +PLUGIN_ADMIN_ADDON_USER_MANAGER: + USER_MANAGER: Пользователи + GROUP_MANAGER: Группы пользователей + FILTER_ERROR: Ошибка фильтрации + FILTER_USERS: Фильтр пользователей + FILTER_GROUPS: Фильтр групп + NO_RESULTS: Нет результатов + EXPERT: Экспертный + PAGINATION_SUMMARY: Страница %d - %d из %d + BULK_ACTION: Групповые действия + ACTIONS: Действия + CUSTOM_PERMISSIONS: Пользовательские разрешения + CUSTOM_PERMISSIONS_PLACEHOLDER: "Разрешение (например, 'site.login')" + USERS_PER_PAGE: Пользователи на странице + DEFAULT_LIST_STYLE: Стиль списка по умолчанию + GRID: Сетка + LIST: Список + GROUP: Группа + GROUPS: Группы + READABLE_NAME: Читаемое имя + ICON: Иконка + ADD_GROUP: Добавить группу + USERS: Пользователи + HELP: Помощь + BULK_DELETE_USER: Удалить выбранных пользователей + BULK_DELETE: Удалить + BULK_USER_GROUP: Массовые пользовательские действия, связанные с группами + BULK_ADD_GROUP: Добавить выбранных пользователей в выбранные группы + BULK_REMOVE_GROUP: Удалить выбранных пользователей из выбранных групп + BULK_DELETE_GROUP: Удалить выбранные группы + BULK_ADD_PERMISSIONS: Добавить выбранные разрешения для выбранных пользователей + BULK_REMOVE_PERMISSIONS: Удалить выбранные разрешения у выбранных пользователей + LOGIN_AS: Войти как + USER_CONFIRM_DELETE: Вы уверены, что хотите удалить этого пользователя? diff --git a/user/plugins/admin-addon-user-manager/languages/sr.yaml b/user/plugins/admin-addon-user-manager/languages/sr.yaml new file mode 100644 index 0000000..be9fa64 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/languages/sr.yaml @@ -0,0 +1,34 @@ +PLUGIN_ADMIN_ADDON_USER_MANAGER: + USER_MANAGER: Менаџер Корисника + GROUP_MANAGER: Менаџер Група + FILTER_ERROR: Грешка у филтрирању + FILTER_USERS: Филтрирај кориснике + FILTER_GROUPS: Филтрирај групе + NO_RESULTS: Нема резултата + EXPERT: Напредно + PAGINATION_SUMMARY: Приказујем %d - %d од %d + BULK_ACTION: Групна Акција + ACTIONS: Акције + CUSTOM_PERMISSIONS: Прилагођена овлашћења + CUSTOM_PERMISSIONS_PLACEHOLDER: "Овлашћења (e.g. 'site.login')" + USERS_PER_PAGE: Корисника по страници + DEFAULT_LIST_STYLE: Подразумевани изглед листе + GRID: Мрежа + LIST: Листа + GROUP: Група + GROUPS: Групе + READABLE_NAME: Читљиво име + ICON: Икона + ADD_GROUP: Додај групу + USERS: Корисници + HELP: Помоћ + BULK_DELETE_USER: Обриши изабране кориснике + BULK_DELETE: Обриши + BULK_USER_GROUP: Групна корисничка акција везана за фрупе + BULK_ADD_GROUP: Додај изабране кориснике у изабране групе + BULK_REMOVE_GROUP: Уклони изабране кориснике из изабраних група + BULK_DELETE_GROUP: Обриши изабране кориснике + BULK_ADD_PERMISSIONS: Додај изабрана овлашћења изабраним корисницима + BULK_REMOVE_PERMISSIONS: Уклони изабрана овлашћења изабраним корисницима + LOGIN_AS: Пријављује се као + USER_CONFIRM_DELETE: Да ли си сигуран да желиш да обришеш овог корисника? diff --git a/user/plugins/admin-addon-user-manager/languages/uk.yaml b/user/plugins/admin-addon-user-manager/languages/uk.yaml new file mode 100644 index 0000000..a68b997 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/languages/uk.yaml @@ -0,0 +1,34 @@ +PLUGIN_ADMIN_ADDON_USER_MANAGER: + USER_MANAGER: Користувачі + GROUP_MANAGER: Групи користувачів + FILTER_ERROR: Помилка фільтрації + FILTER_USERS: Фільтр користувачів + FILTER_GROUPS: Фільтр груп + NO_RESULTS: Немає результатів + EXPERT: Експертний + PAGINATION_SUMMARY: Сторінка %d - %d з %d + BULK_ACTION: Групові дії + ACTIONS: Дії + CUSTOM_PERMISSIONS: Призначені для користувача дозволи + CUSTOM_PERMISSIONS_PLACEHOLDER: "Дозвіл (наприклад, 'site.login')" + USERS_PER_PAGE: Користувачі на сторінці + DEFAULT_LIST_STYLE: Стиль списку за замовчуванням + GRID: Сітка + LIST: Список + GROUP: Група + GROUPS: Групи + READABLE_NAME: Читаєме ім'я + ICON: Іконка + ADD_GROUP: Додати групу + USERS: Користувачі + HELP: Допомога + BULK_DELETE_USER: Видалити вибраних користувачів + BULK_DELETE: Видалити + BULK_USER_GROUP: Масові дії користувача, пов'язані з групами + BULK_ADD_GROUP: Додати обраних користувачів в обрані групи + BULK_REMOVE_GROUP: Видалити вибраних користувачів з обраних груп + BULK_DELETE_GROUP: Видалити вибрані групи + BULK_ADD_PERMISSIONS: Додати вибрані дозволи для обраних користувачів + BULK_REMOVE_PERMISSIONS: Видалити вибрані дозволи у вибраних користувачів + LOGIN_AS: Увійти як + USER_CONFIRM_DELETE: Ви впевнені, що хочете видалити цього користувача? diff --git a/user/plugins/admin-addon-user-manager/languages/zh.yaml b/user/plugins/admin-addon-user-manager/languages/zh.yaml new file mode 100644 index 0000000..38ee7f5 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/languages/zh.yaml @@ -0,0 +1,34 @@ +PLUGIN_ADMIN_ADDON_USER_MANAGER: + USER_MANAGER: 用户管理 + GROUP_MANAGER: 组管理 + FILTER_ERROR: 筛选错误 + FILTER_USERS: 筛选用户 + FILTER_GROUPS: 筛选组 + NO_RESULTS: 没有内容 + EXPERT: 专家 + PAGINATION_SUMMARY: 正在显示 %d - %d (共 %d) + BULK_ACTION: 批量操作 + ACTIONS: 操作 + CUSTOM_PERMISSIONS: 自定义权限 + CUSTOM_PERMISSIONS_PLACEHOLDER: "权限 (例如 'site.login')" + USERS_PER_PAGE: 每页用户数 + DEFAULT_LIST_STYLE: 默认列表风格 + GRID: 网格 + LIST: 列表 + GROUP: 组 + GROUPS: 组 + READABLE_NAME: 显示名称 + ICON: 图标 + ADD_GROUP: 添加组 + USERS: 用户 + HELP: 帮助 + BULK_DELETE_USER: 删除所选用户 + BULK_DELETE: 删除 + BULK_USER_GROUP: 批量用户与关联组操作 + BULK_ADD_GROUP: 添加所选用户到指定组 + BULK_REMOVE_GROUP: 从指定组中移除所选用户 + BULK_DELETE_GROUP: 删除所选组 + BULK_ADD_PERMISSIONS: 向所选用户添加指定权限 + BULK_REMOVE_PERMISSIONS: 从所选用户上移除指定权限 + LOGIN_AS: 模拟登录 + USER_CONFIRM_DELETE: 确认要删除该用户吗? diff --git a/user/plugins/admin-addon-user-manager/modals.yaml b/user/plugins/admin-addon-user-manager/modals.yaml new file mode 100644 index 0000000..141e756 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/modals.yaml @@ -0,0 +1,93 @@ +add_user: + fields: + - type: section + title: PLUGIN_ADMIN.ADD_ACCOUNT + + - type: text + label: PLUGIN_ADMIN.USERNAME + help: PLUGIN_ADMIN.USERNAME_HELP + name: username + placeholder: PLUGIN_ADMIN.USERNAME_PLACEHOLDER + validate: + required: true + message: PLUGIN_LOGIN.USERNAME_NOT_VALID + +add_group: + fields: + - type: section + title: PLUGIN_ADMIN_ADDON_USER_MANAGER.ADD_GROUP + + - type: text + label: PLUGIN_ADMIN.NAME + name: name + validate: + required: true + +bulk_user: + fields: + - type: section + title: PLUGIN_ADMIN_ADDON_USER_MANAGER.BULK_DELETE_USER + + - type: button + text: PLUGIN_ADMIN_ADDON_USER_MANAGER.BULK_DELETE + name: bulk_delete + style: vertical + classes: large warning + + - type: section + title: PLUGIN_ADMIN_ADDON_USER_MANAGER.BULK_USER_GROUP + + - type: select + name: groups + multiple: true + size: large + label: PLUGIN_ADMIN.GROUPS + classes: fancy + help: PLUGIN_ADMIN.GROUPS_HELP + validate: + type: commalist + + - type: button + text: PLUGIN_ADMIN_ADDON_USER_MANAGER.BULK_ADD_GROUP + name: bulk_add_to_group + style: vertical + classes: large + + - type: button + text: PLUGIN_ADMIN_ADDON_USER_MANAGER.BULK_REMOVE_GROUP + name: bulk_remove_from_group + style: vertical + classes: large warning + + - type: selectize + name: permissions + multiple: true + size: large + label: PLUGIN_ADMIN.PERMISSIONS + classes: fancy + help: PLUGIN_ADMIN.PERMISSIONS_HELP + validate: + type: commalist + + - type: button + text: PLUGIN_ADMIN_ADDON_USER_MANAGER.BULK_ADD_PERMISSIONS + name: bulk_add_acl + style: vertical + classes: large + + - type: button + text: PLUGIN_ADMIN_ADDON_USER_MANAGER.BULK_REMOVE_PERMISSIONS + name: bulk_remove_acl + style: vertical + classes: large warning + +bulk_group: + fields: + - type: section + title: PLUGIN_ADMIN_ADDON_USER_MANAGER.BULK_DELETE_GROUP + + - type: button + text: PLUGIN_ADMIN_ADDON_USER_MANAGER.BULK_DELETE + name: bulk_delete + style: vertical + classes: large warning \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/src/Dot.php b/user/plugins/admin-addon-user-manager/src/Dot.php new file mode 100644 index 0000000..30bd78a --- /dev/null +++ b/user/plugins/admin-addon-user-manager/src/Dot.php @@ -0,0 +1,108 @@ + 1) { + $key = array_shift($keys); + + if (!isset($arr[$key]) || !is_array($arr[$key])) { + $arr[$key] = []; + } + + $arr = &$arr[$key]; + } + + $arr[array_shift($keys)] = $value; + } else { + $arr[$key] = $value; + } + } + + /** + * Deletes a $key and its value from the $arr + * + * @param array &$arr + * @param string $key + */ + public static function delete(array &$arr, $key) + { + if (strpos($key, '.') !== false && ($keys = explode('.', $key)) && count($keys)) { + while (count($keys) > 1) { + $arr = &$arr[array_shift($keys)]; + } + + unset($arr[array_shift($keys)]); + } else { + unset($arr[$key]); + } + } +} \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/src/Group.php b/user/plugins/admin-addon-user-manager/src/Group.php new file mode 100644 index 0000000..b864f48 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/src/Group.php @@ -0,0 +1,164 @@ +get('groups', []); + + $blueprints = new Blueprints; + $blueprint = $blueprints->get('user/group'); + foreach ($groups as $groupname => &$content) { + if (!isset($content['groupname'])) { + $content['groupname'] = $groupname; + } + $content = new Group($content, $blueprint); + } + + return $groups; + } + + /** + * Get the groups list + * + * @return array + */ + public static function groupNames() { + $groups = []; + + foreach(Grav::instance()['config']->get('groups', []) as $groupname => $group) { + $groups[$groupname] = isset($group['readableName']) ? $group['readableName'] : $groupname; + } + + return $groups; + } + + /** + * Checks if a group exists + * + * @param string $groupname + * + * @return bool + */ + public static function groupExists($groupname) { + return isset(self::groups()[$groupname]); + } + + /** + * Get a group by name + * + * @param string $groupname + * + * @return object + */ + public static function load($groupname) { + if (self::groupExists($groupname)) { + $group = self::groups()[$groupname]; + } else { + $blueprints = new Blueprints; + $blueprint = $blueprints->get('user/group'); + $content = ['groupname' => $groupname]; + $group = new Group($content, $blueprint); + } + + return $group; + } + + /** + * Save a group + */ + public function save() { + $grav = Grav::instance(); + $config = $grav['config']; + + $blueprints = new Blueprints; + $blueprint = $blueprints->get('user/group'); + + $fields = $blueprint->fields(); + + $config->set("groups.$this->groupname", []); + + foreach ($fields as $field) { + if ($field['type'] == 'text') { + $value = $field['name']; + if (isset($this->items[$value])) { + $config->set("groups.$this->groupname.$value", $this->items[$value]); + } + } + + if ($field['type'] == 'array' || $field['type'] == 'permissions') { + $value = $field['name']; + $arrayValues = Utils::getDotNotation($this->items, $field['name']); + + if ($arrayValues) { + foreach ($arrayValues as $arrayIndex => $arrayValue) { + $config->set("groups.$this->groupname.$value.$arrayIndex", $arrayValue); + } + } + } + } + + $type = 'groups'; + $obj = new Data($config->get($type), $blueprint); + $file = CompiledYamlFile::instance($grav['locator']->findResource('config://') . DS . "{$type}.yaml"); + $obj->file($file); + $obj->save(); + } + + /** + * Remove a group + * + * @param string $groupname + * + * @return bool True if the action was performed + */ + public static function remove($groupname) { + $grav = Grav::instance(); + $config = $grav['config']; + $blueprints = new Blueprints; + $blueprint = $blueprints->get('user/group'); + + $groups = $config->get('groups', []); + if (!isset($groups[$groupname])) { + return false; + } + unset($groups[$groupname]); + $config->set('groups', $groups); + + $type = 'groups'; + $obj = new Data($config->get($type), $blueprint); + $file = CompiledYamlFile::instance($grav['locator']->findResource("config://{$type}.yaml")); + $obj->file($file); + $obj->save(); + + return true; + } + + public function authorize($access) { + if (empty($this->items)) { + return false; + } + + if (!isset($this->items['access'])) { + return false; + } + + $val = Utils::getDotNotation($this->items['access'], $access); + + return Utils::isPositive($val) === true; + } + +} \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/src/Groups/Manager.php b/user/plugins/admin-addon-user-manager/src/Groups/Manager.php new file mode 100644 index 0000000..219c869 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/src/Groups/Manager.php @@ -0,0 +1,242 @@ +grav = $grav; + $this->plugin = $plugin; + + $this->grav['events']->addSubscriber($this); + } + + public static function getSubscribedEvents() { + return [ + 'onAdminControllerInit' => ['onAdminControllerInit', 0], + 'onAdminData' => ['onAdminData', 0] + ]; + } + + public function onAdminControllerInit($e) { + $controller = $e['controller']; + $this->adminController = $controller; + } + + public function onAdminData($e) { + $type = $e['type']; + + if (preg_match('|group-manager|', $type) && ($group = $this->grav['uri']->param('name', false))) { + $obj = Group::load($group); + $post = $this->adminController->data; + if (isset($post['users'])) { + $usersInGroup = $post['users']; + unset($post['users']); + } else { + $usersInGroup = []; + } + $obj->merge($post); + $e['data_type'] = $obj; + + foreach (UsersManager::$instance->users() as $u) { + $groups = $u->get('groups', []); + if (in_array($u['username'], $usersInGroup)) { + if (!in_array($obj['groupname'], $groups)) { + $u['groups'] = array_merge($groups, [$obj['groupname']]); + $u->save(); + } + } else { + if (in_array($obj['groupname'], $groups)) { + $u['groups'] = array_diff($groups, [$obj['groupname']]); + if (empty($u['groups'])) { + unset($u['groups']); + } + $u->save(); + } + } + } + } + } + + /** + * Returns the required permission to access the manager + * + * @return string + */ + public function getRequiredPermission() { + return $this->plugin->name . '.groups'; + } + + /** + * Returns the location of the manager + * It will be accessible at this path + * + * @return string + */ + public function getLocation() { + return 'group-manager'; + } + + /** + * Returns the plugin hooked nav array + * + * @return array + */ + public function getNav() { + return [ + 'label' => 'PLUGIN_ADMIN_ADDON_USER_MANAGER.GROUP_MANAGER', + 'location' => $this->getLocation(), + 'icon' => 'fa-group', + 'authorize' => $this->getRequiredPermission(), + 'badge' => [ + 'count' => count($this->groups()) + ] + ]; + } + + /** + * Initialiaze required assets + * + * @param \Grav\Common\Assets $assets + * @return void + */ + public function initializeAssets(Assets $assets) { + $this->grav['assets']->addCss('plugin://' . $this->plugin->name . '/assets/groups/style.css'); + } + + /** + * Handle task requests + * + * @param \RocketTheme\Toolbox\Event\Event $event + * @return boolean + */ + public function handleTask(Event $event) { + $method = $event['method']; + + if ($method === 'taskGroupDelete' && ($group = $this->grav['uri']->param('name', false))) { + Group::remove($group); + $this->grav->redirect($this->grav['uri']->url($this->getLocation())); + return true; + } + + return false; + } + + /** + * Logic of the manager goes here + * + * @return array The array to be merged to Twig vars + */ + public function handleRequest() { + $vars = []; + + $twig = $this->grav['twig']; + $uri = $this->grav['uri']; + + // Bulk actions + if (isset($_POST['selected'])) { + $groupnames = $_POST['selected']; + + if (isset($_POST['bulk_delete'])) { + // Bulk delete groups + foreach ($groupnames as $groupname) { + Group::remove($groupname); + } + + $this->grav->redirect($this->plugin->getPreviousUrl()); + } + } + + $group = $this->grav['uri']->param('name', false); + + if ($group) { + $vars['exists'] = Group::groupExists($group); + $vars['group'] = $group = Group::load($group); + $users = []; + foreach (UsersManager::$instance->users() as $u) { + if (in_array($group['groupname'], $u->get('groups', []))) { + $users[] = $u->username; + } + } + $group['users'] = $users; + } else { + $vars['fields'] = $this->plugin->getModalsConfiguration()['add_group']['fields']; + $vars['bulkFields'] = $this->plugin->getModalsConfiguration()['bulk_group']['fields']; + + $groups = $this->groups(); + foreach ($groups as &$group) { + $group['users'] = 0; + + foreach (UsersManager::$instance->users() as $u) { + if (in_array($group['groupname'], $u->get('groups', []))) { + $group['users'] += 1; + } + } + } + + // Filtering + $filterException = false; + $filter = (empty($_GET['filter'])) ? '' : $_GET['filter']; + $vars['filter'] = $filter; + if ($filter) { + try { + $language = new ExpressionLanguage(); + $language->addFunction(ExpressionFunction::fromPhp('count')); + foreach ($groups as $k => $group) { + if (!$language->evaluate($_GET['filter'], ['group' => $group])) { + unset($groups[$k]); + } + } + } catch (\Exception $exception) { + $vars['filterException'] = $exception; + $filterException = true; + } + } + + if ($filterException) { + $groups = []; + } + + // Pagination + $perPage = $this->plugin->getPluginConfigValue('pagination.per_page', 10); + $pagination = new ArrayPagination($groups, $perPage); + $pagination->paginate($uri->param('page')); + + $vars['pagination'] = [ + 'current' => $pagination->getCurrentPage(), + 'count' => $pagination->getPagesCount(), + 'total' => $pagination->getRowsCount(), + 'perPage' => $pagination->getRowsPerPage(), + 'startOffset' => $pagination->getStartOffset(), + 'endOffset' => $pagination->getEndOffset() + ]; + $groups = $pagination->getPaginatedRows(); + + $vars['groups'] = $groups; + } + + return $vars; + } + + public function groups() { + return Group::groups(); + } + +} diff --git a/user/plugins/admin-addon-user-manager/src/Manager.php b/user/plugins/admin-addon-user-manager/src/Manager.php new file mode 100644 index 0000000..ea8f793 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/src/Manager.php @@ -0,0 +1,58 @@ +data = $data; + $this->rowsPerPage = $rowsPerPage; + $this->page = 1; + } + + public function paginate($page) { + if ($page > $this->getPagesCount()) { + $page = $page; + } + + if ($page < 1) { + $page = 1; + } + + $this->page = $page; + + $this->slicedData = null; + } + + public function getRowsPerPage() { + return $this->rowsPerPage; + } + + public function getRowsCount() { + if ($this->rowsCount !== null) { + return $this->rowsCount; + } + + return $this->rowsCount = count($this->data); + } + + public function getCurrentPage() { + return $this->page; + } + + public function getPagesCount() { + return ceil($this->getRowsCount() / $this->getRowsPerPage()); + } + + public function getStartOffset() { + return ($this->getCurrentPage() - 1) * $this->getRowsPerPage(); + } + + public function getEndOffset() { + $endOffset = $this->getStartOffset() + $this->getRowsPerPage(); + + if ($endOffset > $this->getRowsCount()) { + $endOffset = $this->getRowsCount(); + } + + return $endOffset; + } + + public function getPaginatedRowsCount() { + return $this->getEndOffset() - $this->getStartOffset(); + } + + public function getPaginatedRows() { + if ($this->slicedData !== null) { + return $this->slicedData; + } + + return $this->slicedData = array_slice($this->data, $this->getStartOffset(), $this->getRowsPerPage()); + } + +} \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/src/Pagination/Pagination.php b/user/plugins/admin-addon-user-manager/src/Pagination/Pagination.php new file mode 100644 index 0000000..0dffde3 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/src/Pagination/Pagination.php @@ -0,0 +1,17 @@ +grav = $grav; + $this->plugin = $plugin; + + self::$instance = $this; + } + + /** + * Returns the required permission to access the manager + * + * @return string + */ + public function getRequiredPermission() { + return $this->plugin->name . '.users_expert'; + } + + /** + * Returns the location of the manager + * It will be accessible at this path + * + * @return string + */ + public function getLocation() { + return 'user-expert'; + } + + /** + * Returns the plugin hooked nav array + * + * @return array + */ + public function getNav() { + return false; + } + + /** + * Initialiaze required assets + * + * @param \Grav\Common\Assets $assets + * @return void + */ + public function initializeAssets(Assets $assets) { + $assets->addCss('plugin://admin/themes/grav/css/codemirror/codemirror.css'); + } + + /** + * Handle task requests + * + * @param \RocketTheme\Toolbox\Event\Event $event + * @return boolean + */ + public function handleTask(Event $event) {} + + /** + * Logic of the manager goes here + * + * @return array The array to be merged to Twig vars + */ + public function handleRequest() { + $vars = []; + + $twig = $this->grav['twig']; + $uri = $this->grav['uri']; + + $username = $uri->paths()[2]; + $user = $this->grav['accounts']->load($username); + + if (isset($_POST['raw'])) { + $user->file()->raw($_POST['raw']); + $user->file()->save(); + } + + $vars['raw'] = $user->file()->raw(); + $vars['user'] = $user; + + $blueprints = new Blueprints; + $vars['blueprint'] = $blueprints->get('user/account-raw'); + + return $vars; + } + +} \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/src/Users/Manager.php b/user/plugins/admin-addon-user-manager/src/Users/Manager.php new file mode 100644 index 0000000..b5579c7 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/src/Users/Manager.php @@ -0,0 +1,432 @@ + + */ + private $usersCached = null; + + /** + * In-memory cache of the account directory + * + * @var String + */ + private $accountDirCached = null; + + public static $instance; + + public function __construct(Grav $grav, AdminAddonUserManagerPlugin $plugin) { + $this->grav = $grav; + $this->plugin = $plugin; + + self::$instance = $this; + + $this->grav['events']->addSubscriber($this); + } + + public static function getSubscribedEvents() { + return [ + 'onAdminControllerInit' => ['onAdminControllerInit', 0], + 'onAdminData' => ['onAdminData', 0] + ]; + } + + public function onAdminControllerInit($e) { + $controller = $e['controller']; + $this->adminController = $controller; + } + + public function onAdminData($e) { + $type = $e['type']; + + if (preg_match('|user-manager/|', $type)) { + $post = $this->adminController->data; + + $obj = $this->grav['accounts']->load(preg_replace('|user-manager/|', '', $type)); + $obj->merge($post); + + $e['data_type'] = $obj; + } + } + + /** + * Returns the required permission to access the manager + * + * @return string + */ + public function getRequiredPermission() { + return $this->plugin->name . '.users'; + } + + /** + * Returns the location of the manager + * It will be accessible at this path + * + * @return string + */ + public function getLocation() { + return 'user-manager'; + } + + /** + * Returns the plugin hooked nav array + * + * @return array + */ + public function getNav() { + return [ + 'label' => 'PLUGIN_ADMIN_ADDON_USER_MANAGER.USER_MANAGER', + 'location' => $this->getLocation(), + 'icon' => 'fa-user', + 'authorize' => $this->getRequiredPermission(), + 'badge' => [ + 'count' => count($this->users()) + ] + ]; + } + + /** + * Initialiaze required assets + * + * @param \Grav\Common\Assets $assets + * @return void + */ + public function initializeAssets(Assets $assets) { + $assets->addCss('plugin://' . $this->plugin->name . '/assets/users/style.css'); + } + + /** + * Handle task requests + * + * @param \RocketTheme\Toolbox\Event\Event $event + * @return boolean + */ + public function handleTask(Event $event) { + $method = $event['method']; + + if ($method === 'taskUserDelete') { + $username = $this->grav['uri']->paths()[2]; + if ($this->removeUser($username)) { + $this->adminController->setRedirect($this->getLocation()); + } + } elseif ($method === 'taskUserLoginAs') { + $username = $this->grav['uri']->paths()[2]; + $user = $this->grav['accounts']->load($username); + $user->authenticated = true; + + $this->grav['session']->user = $user; + unset($this->grav['user']); + $this->grav['user'] = $user; + + if ($user->authorize('admin.login')) { + $this->adminController->setRedirect('/'); + } else { + $this->grav->redirect('/'); + } + } + + return false; + } + + /** + * Logic of the manager goes here + * + * @return array The array to be merged to Twig vars + */ + public function handleRequest() { + $vars = []; + + $twig = $this->grav['twig']; + $uri = $this->grav['uri']; + + $user = $this->grav['uri']->paths(); + if (count($user) == 3) { + $user = $user[2]; + } else { + $user = false; + } + + if ($user) { + if (isset($_POST['task']) && $_POST['task'] === 'admin-addon-user-manager-save') { + $user = $this->grav['accounts']->load($user); + $post = $_POST['data']; + + try { + $user->merge($post); + $method = new \ReflectionMethod('\Grav\Plugin\Admin\AdminController', 'storeFiles'); + $method->setAccessible(true); + $user = $method->invoke($this->adminController, $user); + $user->validate(); + $user->filter(); + $user->save(); + } catch (\Exception $e) { + $this->grav['admin']->setMessage($e->getMessage(), 'error'); + } + + $this->grav->redirect($this->plugin->getPreviousUrl()); + } else { + $blueprints = new Blueprints; + $blueprint = $blueprints->get('user/aaum-account'); + $vars['blueprints'] = $blueprint; + $vars['user'] = $user = $this->grav['accounts']->load($user); + $vars['exists'] = $user->exists(); + } + } else { + // Bulk actions + if (isset($_POST['selected'])) { + $usernames = $_POST['selected']; + + if (isset($_POST['bulk_delete'])) { + // Bulk delete + foreach ($usernames as $username) { + $this->removeUser($username); + } + + $this->grav->redirect($this->plugin->getPreviousUrl()); + } else if (isset($_POST['bulk_add_to_group']) && isset($_POST['groups'])) { + // Bulk add users to groups + $groups = $_POST['groups']; + + foreach ($usernames as $username) { + $user = $this->grav['accounts']->load($username); + if ($user->file()->exists()) { + if (!isset($user['groups']) || !is_array($user['groups'])) { + $user['groups'] = []; + } + + $user['groups'] = array_unique(array_merge($user['groups'], $groups)); + $user->save(); + } + } + + $this->grav->redirect($this->plugin->getPreviousUrl()); + } else if (isset($_POST['bulk_remove_from_group']) && isset($_POST['groups'])) { + // Bulk remove users from groups + $groups = $_POST['groups']; + + foreach ($usernames as $username) { + $user = $this->grav['accounts']->load($username); + if ($user->file()->exists()) { + if (!isset($user['groups']) || !is_array($user['groups'])) { + $user['groups'] = []; + } + + $user['groups'] = array_unique(array_diff($user['groups'], $groups)); + $user->save(); + } + } + + $this->grav->redirect($this->plugin->getPreviousUrl()); + } else if (isset($_POST['bulk_add_acl']) && isset($_POST['permissions'])) { + // Bulk add permissions to users + $access = []; + foreach ($_POST['permissions'] as $p) { + Dot::set($access, $p, true); + } + + foreach ($usernames as $username) { + $user = $this->grav['accounts']->load($username); + if ($user->file()->exists()) { + if (!isset($user['access']) || !is_array($user['access'])) { + $user['access'] = []; + } + + $user['access'] = array_merge_recursive($user['access'], $access); + $user->save(); + } + } + + $this->grav->redirect($this->plugin->getPreviousUrl()); + } else if (isset($_POST['bulk_remove_acl']) && isset($_POST['permissions'])) { + // Bulk remove permissions from users + foreach ($usernames as $username) { + $user = $this->grav['accounts']->load($username); + if ($user->file()->exists()) { + if (!isset($user['access']) || !is_array($user['access'])) { + $user['access'] = []; + } + + $access = $user['access']; + foreach ($_POST['permissions'] as $p) { + Dot::delete($access, $p); + } + $user['access'] = $access; + $user->save(); + } + } + + $this->grav->redirect($this->plugin->getPreviousUrl()); + } + } + + $vars['fields'] = $this->plugin->getModalsConfiguration()['add_user']['fields']; + $vars['bulkFields'] = $this->plugin->getModalsConfiguration()['bulk_user']['fields']; + $vars['groupnames'] = Group::groupNames(); + $permissions = array_keys($this->grav['admin']->getPermissions()); + foreach ($permissions as $k=>&$v) $v = ['text' => $v, 'value' => $v]; + $vars['permissions'] = $permissions; + + // List style (grid or list) + $listStyle = $uri->param('listStyle'); + if ($listStyle !== 'grid' && $listStyle !== 'list') { + $listStyle = $this->plugin->getPluginConfigValue('default_list_style', 'grid'); + } + $vars['listStyle'] = $listStyle; + + $users = $this->users(); + + // Filtering + $filterException = false; + $filter = (empty($_GET['filter'])) ? '' : $_GET['filter']; + $vars['filter'] = $filter; + if ($filter) { + try { + $language = new ExpressionLanguage(); + $language->addFunction(ExpressionFunction::fromPhp('count')); + foreach ($users as $k => $user) { + if (!is_array($user->groups)) { + $user->groups = []; + } + + if (!$language->evaluate($_GET['filter'], ['user' => $user])) { + unset($users[$k]); + } + } + } catch (\Exception $exception) { + $vars['filterException'] = $exception; + $filterException = true; + } + } + + if ($filterException) { + $users = []; + } + + // Pagination + $perPage = $this->plugin->getPluginConfigValue('pagination.per_page', 10); + $pagination = new ArrayPagination($users, $perPage); + $pagination->paginate($uri->param('page')); + + $vars['pagination'] = [ + 'current' => $pagination->getCurrentPage(), + 'count' => $pagination->getPagesCount(), + 'total' => $pagination->getRowsCount(), + 'perPage' => $pagination->getRowsPerPage(), + 'startOffset' => $pagination->getStartOffset(), + 'endOffset' => $pagination->getEndOffset() + ]; + $vars['users'] = $pagination->getPaginatedRows(); + $vars['user'] = false; + } + + return $vars; + } + + public function users() { + if ($this->usersCached) { + return $this->usersCached; + } + + $users = []; + $dir = $this->getAccountDir(); + + // Try cache + $cache = $this->grav['cache']; + $cacheKey = $this->plugin->name . '.users'; + + $modifyTime = filemtime($dir); + $usersCache = $cache->fetch($cacheKey); + if (!$usersCache || $modifyTime > $usersCache['modifyTime']) { + // Find accounts + $files = $dir ? array_diff(scandir($dir), ['.', '..']) : []; + foreach ($files as $file) { + if (Utils::endsWith($file, YAML_EXT)) { + $user = $this->grav['accounts']->load(trim(pathinfo($file, PATHINFO_FILENAME))); + $users[$user->username] = $user; + } + } + + // Populate and/or refresh cache + $this->saveUsersToCache($users); + } else { + $users = $usersCache['users']; + } + + $this->usersCached = $users; + + return $users; + } + + private function saveUsersToCache($users) { + $cache = $this->grav['cache']; + $cacheKey = $this->plugin->name . '.users'; + $dir = $this->getAccountDir(); + $modifyTime = filemtime($dir); + + $usersCache = [ + 'modifyTime' => $modifyTime, + 'users' => $users, + ]; + + $cache->save($cacheKey, $usersCache); + } + + private function getAccountDir() { + if ($this->accountDirCached) { + return $this->grav['locator']->findResource('account://'); + } + + return $this->accountDirCached = $this->grav['locator']->findResource('account://'); + } + + public function removeUser($username) { + $user = $this->grav['accounts']->load($username); + + if ($user->file()->exists()) { + $users = $this->users(); + $user->file()->delete(); + // Prevent users cache refresh + unset($users[$username]); + $this->saveUsersToCache($users); + return true; + } + + return false; + } + + public static function userNames() { + $instance = self::$instance; + $users = $instance->users(); + + $userNames = []; + foreach ($users as $u) { + $userNames[$u['username']] = $u['username']; + } + + return $userNames; + } + +} diff --git a/user/plugins/admin-addon-user-manager/templates/forms/fields/button/button.html.twig b/user/plugins/admin-addon-user-manager/templates/forms/fields/button/button.html.twig new file mode 100644 index 0000000..6b44ece --- /dev/null +++ b/user/plugins/admin-addon-user-manager/templates/forms/fields/button/button.html.twig @@ -0,0 +1,25 @@ +{% extends "forms/field.html.twig" %} + +{% block input %} +
+ +
+{% endblock %} \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/templates/group-manager.html.twig b/user/plugins/admin-addon-user-manager/templates/group-manager.html.twig new file mode 100644 index 0000000..c65fd01 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/templates/group-manager.html.twig @@ -0,0 +1,170 @@ +{% extends 'partials/base.html.twig' %} +{% import 'user-manager-macros.html.twig' as macros %} + +{% set title = "PLUGIN_ADMIN_ADDON_USER_MANAGER.GROUP_MANAGER"|tu %} + +{% if group %} +{% set group_name = group.readableName ?: group.groupname %} +{% set title = "PLUGIN_ADMIN_ADDON_USER_MANAGER.GROUP"|tu ~ ": " ~ group_name|e %} +{% endif %} + +{% set ps = config.system.param_sep %} + +{% block titlebar %} + {% if not group %} + + +

{{ "PLUGIN_ADMIN_ADDON_USER_MANAGER.GROUP_MANAGER"|tu }}

+ {% else %} +
+ {{ "PLUGIN_ADMIN.BACK"|tu }} + {% if exists %} + {{ "PLUGIN_ADMIN.DELETE"|tu }} + {% endif %} + +
+

{{ "PLUGIN_ADMIN_ADDON_USER_MANAGER.GROUP"|tu }}: {{ group.groupname|e }}

+ {% endif %} +{% endblock %} + +{% set appendUrl = '?filter=' ~ filter|url_encode %} + +{% block content %} + {% if not group %} +

{{ "PLUGIN_ADMIN_ADDON_USER_MANAGER.GROUPS"|tu }}

+ + {% if filterException %} +

{{ "PLUGIN_ADMIN_ADDON_USER_MANAGER.FILTER_ERROR"|tu }}

{{ filterException.message }}

+ {% endif %} + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + +
+ +
+
+ {% if groups is empty %} +

{{ "PLUGIN_ADMIN_ADDON_USER_MANAGER.NO_RESULTS"|tu }}

+ {% else %} +
+
+
+
{{ "PLUGIN_ADMIN.NAME"|tu }}
+
{{ "PLUGIN_ADMIN_ADDON_USER_MANAGER.ACTIONS"|tu }}
+
+
+ + {% for groupName, group in groups %} + + {% endfor %} + {% endif %} +
+ + +
+ + {{ macros.pagination(pagination, uri.route(true), ps, appendUrl) }} + +
+
+ {% for field in fields %} + {% if field.type %} + {% set value = data.value(field.name) %} +
+ {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} +
+ {% endif %} + {% endfor %} + +
+ +
+
+
+ +
+
+ {% for field in bulkFields %} + {% if field.type %} +
+ {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} +
+ {% endif %} + {% endfor %} + +
+ +
+
+
+ + + {% else %} +

{{ group.readableName ?: group.groupname }}

+ + {% include 'partials/blueprints.html.twig' with { data: group, blueprints: group.blueprints } %} + +
+
+

{{ "PLUGIN_ADMIN.MODAL_CHANGED_DETECTED_TITLE"|tu }}

+

+ {{ "PLUGIN_ADMIN.MODAL_CHANGED_DETECTED_DESC"|tu }} +

+
+ +
+
+ {% endif %} +{% endblock %} diff --git a/user/plugins/admin-addon-user-manager/templates/user-expert.html.twig b/user/plugins/admin-addon-user-manager/templates/user-expert.html.twig new file mode 100644 index 0000000..0427b12 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/templates/user-expert.html.twig @@ -0,0 +1,28 @@ +{% extends 'partials/base.html.twig' %} + +{% set title = "PLUGIN_ADMIN.USER"|tu ~ ": " ~ admin.route|e %} + +{% block titlebar %} +
+ {{ "PLUGIN_ADMIN.BACK"|tu }} + +
+ +

{{ "PLUGIN_ADMIN.USER"|tu }}: {{ user.username|e }}

+{% endblock %} + +{% block content %} +

{{ title }}

+ +
+ {% for field in blueprint.fields %} + {% if field.type %} + {% set value = attribute(_context, field.name) %} +
+ {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} +
+ {% endif %} + {% endfor %} + {{ nonce_field('form', 'form-nonce')|raw }} +
+{% endblock %} diff --git a/user/plugins/admin-addon-user-manager/templates/user-manager-macros.html.twig b/user/plugins/admin-addon-user-manager/templates/user-manager-macros.html.twig new file mode 100644 index 0000000..27959a3 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/templates/user-manager-macros.html.twig @@ -0,0 +1,31 @@ +{% macro pagination(pagination, url, ps, appendUrl) %} + {% if pagination.count > 1 %} +
+
    + {% if pagination.current > 1 %} +
  • <<
  • + {% endif %} + {% if pagination.current > 2 %} +
  • <
  • + {% endif %} + {% set fromPage = (pagination.current - 2 < 1) ? 1 : pagination.current - 2 %} + {% set toPage = (pagination.current + 2 > pagination.count) ? pagination.count : pagination.current + 2 %} + {% for i in fromPage..toPage %} + {% if pagination.current == i %} +
  • {{ i }}
  • + {% else %} +
  • {{ i }}
  • + {% endif %} + {% endfor %} + {% if pagination.current < pagination.count - 1 %} +
  • >
  • + {% endif %} + {% if pagination.current < pagination.count %} +
  • >>
  • + {% endif %} +
+ +
{{ "PLUGIN_ADMIN_ADDON_USER_MANAGER.PAGINATION_SUMMARY"|t(pagination.startOffset + 1, pagination.endOffset, pagination.total) }}
+
+ {% endif %} +{% endmacro %} \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/templates/user-manager.html.twig b/user/plugins/admin-addon-user-manager/templates/user-manager.html.twig new file mode 100644 index 0000000..3e60d79 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/templates/user-manager.html.twig @@ -0,0 +1,199 @@ +{% extends 'partials/base.html.twig' %} +{% import 'user-manager-macros.html.twig' as macros %} + +{% set title = "PLUGIN_ADMIN_ADDON_USER_MANAGER.USER_MANAGER"|tu %} + +{% block titlebar %} + {% if not user %} + + +

{{ "PLUGIN_ADMIN_ADDON_USER_MANAGER.USER_MANAGER"|tu }}

+ {% else %} + +

{{ "PLUGIN_ADMIN.USER"|tu }}: {{ user.username|e }}

+ {% endif %} +{% endblock %} + +{% set ps = config.system.param_sep %} +{% set appendUrl = '?filter=' ~ filter|url_encode %} + +{% block content %} + {% if not user %} + {% set style = listStyle|default('grid') %} + +

{{ "PLUGIN_ADMIN_ADDON_USER_MANAGER.USERS"|tu }}

+ + {% if filterException %} +

{{ "PLUGIN_ADMIN_ADDON_USER_MANAGER.FILTER_ERROR"|tu }}

{{ filterException.message }}

+ {% endif %} + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + +
+ +
+ {% if style != 'grid' %}{% else %}{% endif %} + {% if style != 'list' %}{% else %}{% endif %} +
+ +
+
+ {% if users is empty %} +

{{ "PLUGIN_ADMIN_ADDON_USER_MANAGER.NO_RESULTS"|tu }}

+ {% else %} + {% if style == 'list' %} +
+
+
+
{{ "PLUGIN_ADMIN.USERNAME"|tu }}
+ +
{{ "PLUGIN_ADMIN_ADDON_USER_MANAGER.ACTIONS"|tu }}
+
+
+ {% endif %} + {% for user in users %} +
+
+ {% if style == 'grid' %} +
+ {% else %} +
+ {% endif %} + + + +
+
+ {% endfor %} + {% endif %} +
+ + +
+ + {{ macros.pagination(pagination, uri.route(true) ~ '/listStyle' ~ ps ~ listStyle, ps, appendUrl) }} + +
+
+ {% for field in fields %} + {% if field.type %} + {% if field.name == 'username' %} + {% set field = field|merge({ validate: field.validate|merge({ pattern: grav.config.system.username_regex })}) %} + {% endif %} +
+ {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} +
+ {% endif %} + {% endfor %} + +
+ +
+
+
+ +
+
+ {% for field in bulkFields %} + {% if field.type %} + {% if field.name == 'groups' %} + {% set field = field|merge({options: groupnames}) %} + {% endif %} + {% if field.name == 'permissions' %} + {% set field = field|merge({selectize: { options: permissions }}) %} + {% endif %} +
+ {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} +
+ {% endif %} + {% endfor %} + +
+ +
+
+
+ + + {% else %} +

{{ user.username }}

+ + {% include 'partials/blueprints.html.twig' with { data: user, blueprints: blueprints } %} + +
+
+

{{ "PLUGIN_ADMIN.MODAL_CHANGED_DETECTED_TITLE"|tu }}

+

+ {{ "PLUGIN_ADMIN.MODAL_CHANGED_DETECTED_DESC"|tu }} +

+
+ +
+
+ + + + + + {% endif %} +{% endblock %} diff --git a/user/plugins/admin-addon-user-manager/vendor/autoload.php b/user/plugins/admin-addon-user-manager/vendor/autoload.php new file mode 100644 index 0000000..e967d8f --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/user/plugins/admin-addon-user-manager/vendor/composer/LICENSE b/user/plugins/admin-addon-user-manager/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/user/plugins/admin-addon-user-manager/vendor/composer/autoload_classmap.php b/user/plugins/admin-addon-user-manager/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..3a14165 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/composer/autoload_classmap.php @@ -0,0 +1,16 @@ + $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php', + 'AssertionError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php', + 'DivisionByZeroError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', + 'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php', + 'ParseError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ParseError.php', + 'SessionUpdateTimestampHandlerInterface' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php', + 'TypeError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/TypeError.php', +); diff --git a/user/plugins/admin-addon-user-manager/vendor/composer/autoload_files.php b/user/plugins/admin-addon-user-manager/vendor/composer/autoload_files.php new file mode 100644 index 0000000..268e870 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/composer/autoload_files.php @@ -0,0 +1,10 @@ + $vendorDir . '/symfony/polyfill-php70/bootstrap.php', +); diff --git a/user/plugins/admin-addon-user-manager/vendor/composer/autoload_namespaces.php b/user/plugins/admin-addon-user-manager/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/symfony/polyfill-php70'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), + 'Symfony\\Contracts\\Cache\\' => array($vendorDir . '/symfony/cache-contracts'), + 'Symfony\\Component\\VarExporter\\' => array($vendorDir . '/symfony/var-exporter'), + 'Symfony\\Component\\ExpressionLanguage\\' => array($vendorDir . '/symfony/expression-language'), + 'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'AdminAddonUserManager\\' => array($baseDir . '/src'), +); diff --git a/user/plugins/admin-addon-user-manager/vendor/composer/autoload_real.php b/user/plugins/admin-addon-user-manager/vendor/composer/autoload_real.php new file mode 100644 index 0000000..7f50636 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit089ad2d043aed688879945cd4bcae11c::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\ComposerStaticInit089ad2d043aed688879945cd4bcae11c::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire089ad2d043aed688879945cd4bcae11c($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire089ad2d043aed688879945cd4bcae11c($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/composer/autoload_static.php b/user/plugins/admin-addon-user-manager/vendor/composer/autoload_static.php new file mode 100644 index 0000000..ace6766 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/composer/autoload_static.php @@ -0,0 +1,97 @@ + __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Polyfill\\Php70\\' => 23, + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Contracts\\Cache\\' => 24, + 'Symfony\\Component\\VarExporter\\' => 30, + 'Symfony\\Component\\ExpressionLanguage\\' => 37, + 'Symfony\\Component\\Cache\\' => 24, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + 'Psr\\Container\\' => 14, + 'Psr\\Cache\\' => 10, + ), + 'A' => + array ( + 'AdminAddonUserManager\\' => 22, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Php70\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php70', + ), + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Symfony\\Contracts\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/cache-contracts', + ), + 'Symfony\\Component\\VarExporter\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/var-exporter', + ), + 'Symfony\\Component\\ExpressionLanguage\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/expression-language', + ), + 'Symfony\\Component\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/cache', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), + 'AdminAddonUserManager\\' => + array ( + 0 => __DIR__ . '/../..' . '/src', + ), + ); + + public static $classMap = array ( + 'ArithmeticError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php', + 'AssertionError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php', + 'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', + 'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php', + 'ParseError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ParseError.php', + 'SessionUpdateTimestampHandlerInterface' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php', + 'TypeError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/TypeError.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit089ad2d043aed688879945cd4bcae11c::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit089ad2d043aed688879945cd4bcae11c::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit089ad2d043aed688879945cd4bcae11c::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/composer/installed.json b/user/plugins/admin-addon-user-manager/vendor/composer/installed.json new file mode 100644 index 0000000..402ec27 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/composer/installed.json @@ -0,0 +1,574 @@ +[ + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "version_normalized": "9.99.99.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "time": "2018-07-02T15:55:56+00:00", + "type": "library", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ] + }, + { + "name": "psr/cache", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T20:24:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ] + }, + { + "name": "psr/container", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-02-14T16:28:37+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ] + }, + { + "name": "psr/log", + "version": "1.1.3", + "version_normalized": "1.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2020-03-23T09:12:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "name": "symfony/cache", + "version": "v4.4.7", + "version_normalized": "4.4.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "f777b570291aebe51081b9827e05f3a747665e87" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/f777b570291aebe51081b9827e05f3a747665e87", + "reference": "f777b570291aebe51081b9827e05f3a747665e87", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/cache": "~1.0", + "psr/log": "~1.0", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/service-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.2|^5.0" + }, + "conflict": { + "doctrine/dbal": "<2.5", + "symfony/dependency-injection": "<3.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0", + "symfony/cache-implementation": "1.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.5", + "predis/predis": "~1.1", + "psr/simple-cache": "^1.0", + "symfony/config": "^4.2|^5.0", + "symfony/dependency-injection": "^3.4|^4.1|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "time": "2020-03-27T16:54:36+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ] + }, + { + "name": "symfony/cache-contracts", + "version": "v2.0.1", + "version_normalized": "2.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16", + "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "psr/cache": "^1.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "time": "2019-11-18T17:27:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ] + }, + { + "name": "symfony/expression-language", + "version": "v3.4.39", + "version_normalized": "3.4.39.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "206165f46c660f3231df0afbdeec6a62f81afc59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/206165f46c660f3231df0afbdeec6a62f81afc59", + "reference": "206165f46c660f3231df0afbdeec6a62f81afc59", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/cache": "~3.1|~4.0", + "symfony/polyfill-php70": "~1.6" + }, + "time": "2020-03-16T08:31:04+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ExpressionLanguage Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.15.0", + "version_normalized": "1.15.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "2a18e37a489803559284416df58c71ccebe50bf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/2a18e37a489803559284416df58c71ccebe50bf0", + "reference": "2a18e37a489803559284416df58c71ccebe50bf0", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0|~9.99", + "php": ">=5.3.3" + }, + "time": "2020-02-27T09:26:54+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/service-contracts", + "version": "v2.0.1", + "version_normalized": "2.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "144c5e51266b281231e947b51223ba14acf1a749" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", + "reference": "144c5e51266b281231e947b51223ba14acf1a749", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "time": "2019-11-18T17:27:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ] + }, + { + "name": "symfony/var-exporter", + "version": "v5.0.7", + "version_normalized": "5.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "ffd29a70370e466343e33154b5df197a07a13afa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/ffd29a70370e466343e33154b5df197a07a13afa", + "reference": "ffd29a70370e466343e33154b5df197a07a13afa", + "shasum": "" + }, + "require": { + "php": "^7.2.5" + }, + "require-dev": { + "symfony/var-dumper": "^4.4|^5.0" + }, + "time": "2020-03-27T16:56:45+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ] + } +] diff --git a/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/LICENSE b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/LICENSE new file mode 100644 index 0000000..45c7017 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Paragon Initiative Enterprises + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/build-phar.sh b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/build-phar.sh new file mode 100644 index 0000000..b4a5ba3 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/build-phar.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) + +php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file diff --git a/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/composer.json b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/composer.json new file mode 100644 index 0000000..1fa8de9 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/composer.json @@ -0,0 +1,34 @@ +{ + "name": "paragonie/random_compat", + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "random", + "polyfill", + "pseudorandom" + ], + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "support": { + "issues": "https://github.com/paragonie/random_compat/issues", + "email": "info@paragonie.com", + "source": "https://github.com/paragonie/random_compat" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "vimeo/psalm": "^1", + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey new file mode 100644 index 0000000..eb50ebf --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm +pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p ++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc +-----END PUBLIC KEY----- diff --git a/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc new file mode 100644 index 0000000..6a1d7f3 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v2.0.22 (MingW32) + +iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip +QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg +1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW +NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA +NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV +JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= +=B6+8 +-----END PGP SIGNATURE----- diff --git a/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/lib/random.php b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/lib/random.php new file mode 100644 index 0000000..c7731a5 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/lib/random.php @@ -0,0 +1,32 @@ +buildFromDirectory(dirname(__DIR__).'/lib'); +rename( + dirname(__DIR__).'/lib/index.php', + dirname(__DIR__).'/lib/random.php' +); + +/** + * If we pass an (optional) path to a private key as a second argument, we will + * sign the Phar with OpenSSL. + * + * If you leave this out, it will produce an unsigned .phar! + */ +if ($argc > 1) { + if (!@is_readable($argv[1])) { + echo 'Could not read the private key file:', $argv[1], "\n"; + exit(255); + } + $pkeyFile = file_get_contents($argv[1]); + + $private = openssl_get_privatekey($pkeyFile); + if ($private !== false) { + $pkey = ''; + openssl_pkey_export($private, $pkey); + $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); + + /** + * Save the corresponding public key to the file + */ + if (!@is_readable($dist.'/random_compat.phar.pubkey')) { + $details = openssl_pkey_get_details($private); + file_put_contents( + $dist.'/random_compat.phar.pubkey', + $details['key'] + ); + } + } else { + echo 'An error occurred reading the private key from OpenSSL.', "\n"; + exit(255); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/psalm-autoload.php b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 0000000..d71d1b8 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + diff --git a/user/plugins/admin-addon-user-manager/vendor/psr/cache/CHANGELOG.md b/user/plugins/admin-addon-user-manager/vendor/psr/cache/CHANGELOG.md new file mode 100644 index 0000000..58ddab0 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/cache/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.0.1 - 2016-08-06 + +### Fixed + +- Make spacing consistent in phpdoc annotations php-fig/cache#9 - chalasr +- Fix grammar in phpdoc annotations php-fig/cache#10 - chalasr +- Be more specific in docblocks that `getItems()` and `deleteItems()` take an array of strings (`string[]`) compared to just `array` php-fig/cache#8 - GrahamCampbell +- For `expiresAt()` and `expiresAfter()` in CacheItemInterface fix docblock to specify null as a valid parameters as well as an implementation of DateTimeInterface php-fig/cache#7 - GrahamCampbell + +## 1.0.0 - 2015-12-11 + +Initial stable release; reflects accepted PSR-6 specification diff --git a/user/plugins/admin-addon-user-manager/vendor/psr/cache/LICENSE.txt b/user/plugins/admin-addon-user-manager/vendor/psr/cache/LICENSE.txt new file mode 100644 index 0000000..b1c2c97 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/cache/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2015 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/user/plugins/admin-addon-user-manager/vendor/psr/cache/README.md b/user/plugins/admin-addon-user-manager/vendor/psr/cache/README.md new file mode 100644 index 0000000..c8706ce --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/cache/README.md @@ -0,0 +1,9 @@ +PSR Cache +========= + +This repository holds all interfaces defined by +[PSR-6](http://www.php-fig.org/psr/psr-6/). + +Note that this is not a Cache implementation of its own. It is merely an +interface that describes a Cache implementation. See the specification for more +details. diff --git a/user/plugins/admin-addon-user-manager/vendor/psr/cache/composer.json b/user/plugins/admin-addon-user-manager/vendor/psr/cache/composer.json new file mode 100644 index 0000000..e828fec --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/cache/composer.json @@ -0,0 +1,25 @@ +{ + "name": "psr/cache", + "description": "Common interface for caching libraries", + "keywords": ["psr", "psr-6", "cache"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/psr/cache/src/CacheException.php b/user/plugins/admin-addon-user-manager/vendor/psr/cache/src/CacheException.php new file mode 100644 index 0000000..e27f22f --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/cache/src/CacheException.php @@ -0,0 +1,10 @@ +=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/psr/container/src/ContainerExceptionInterface.php b/user/plugins/admin-addon-user-manager/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 0000000..d35c6b4 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,13 @@ +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/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000..67f852d --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/LoggerInterface.php b/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000..2206cfd --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/NullLogger.php b/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000..c8f7293 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/Test/DummyTest.php b/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/Test/DummyTest.php new file mode 100644 index 0000000..9638c11 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/Test/DummyTest.php @@ -0,0 +1,18 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $closed = fopen('php://memory', 'r'); + fclose($closed); + + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + 'closed' => $closed, + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/Test/TestLogger.php b/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/Test/TestLogger.php new file mode 100644 index 0000000..1be3230 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/log/Psr/Log/Test/TestLogger.php @@ -0,0 +1,147 @@ + $level, + 'message' => $message, + 'context' => $context, + ]; + + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_string($record)) { + $record = ['message' => $record]; + } + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + return true; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses(callable $predicate, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + return false; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = strtolower($matches[2]); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + return call_user_func_array([$this, $genericMethod], $args); + } + } + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } + + public function reset() + { + $this->records = []; + $this->recordsByLevel = []; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/psr/log/README.md b/user/plugins/admin-addon-user-manager/vendor/psr/log/README.md new file mode 100644 index 0000000..a9f20c4 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/log/README.md @@ -0,0 +1,58 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Installation +------------ + +```bash +composer require psr/log +``` + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + try { + $this->doSomethingElse(); + } catch (Exception $exception) { + $this->logger->error('Oh no!', array('exception' => $exception)); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/user/plugins/admin-addon-user-manager/vendor/psr/log/composer.json b/user/plugins/admin-addon-user-manager/vendor/psr/log/composer.json new file mode 100644 index 0000000..3f6d4ee --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/.gitignore b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/CacheInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/CacheInterface.php new file mode 100644 index 0000000..4b1686b --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/CacheInterface.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * Covers most simple to advanced caching needs. + * + * @author Nicolas Grekas + */ +interface CacheInterface +{ + /** + * Fetches a value from the pool or computes it if not found. + * + * On cache misses, a callback is called that should return the missing value. + * This callback is given a PSR-6 CacheItemInterface instance corresponding to the + * requested key, that could be used e.g. for expiration control. It could also + * be an ItemInterface instance when its additional features are needed. + * + * @param string $key The key of the item to retrieve from the cache + * @param callable|CallbackInterface $callback Should return the computed value for the given key/item + * @param float|null $beta A float that, as it grows, controls the likeliness of triggering + * early expiration. 0 disables it, INF forces immediate expiration. + * The default (or providing null) is implementation dependent but should + * typically be 1.0, which should provide optimal stampede protection. + * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration + * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()} + * + * @return mixed The value corresponding to the provided key + * + * @throws InvalidArgumentException When $key is not valid or when $beta is negative + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null); + + /** + * Removes an item from the pool. + * + * @param string $key The key to delete + * + * @throws InvalidArgumentException When $key is not valid + * + * @return bool True if the item was successfully removed, false if there was any error + */ + public function delete(string $key): bool; +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/CacheTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/CacheTrait.php new file mode 100644 index 0000000..355ea29 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/CacheTrait.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemPoolInterface; +use Psr\Cache\InvalidArgumentException; +use Psr\Log\LoggerInterface; + +/** + * An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes. + * + * @author Nicolas Grekas + */ +trait CacheTrait +{ + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + return $this->doGet($this, $key, $callback, $beta, $metadata); + } + + /** + * {@inheritdoc} + */ + public function delete(string $key): bool + { + return $this->deleteItem($key); + } + + private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null) + { + if (0 > $beta = $beta ?? 1.0) { + throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', \get_class($this), $beta)) extends \InvalidArgumentException implements InvalidArgumentException { + }; + } + + $item = $pool->getItem($key); + $recompute = !$item->isHit() || INF === $beta; + $metadata = $item instanceof ItemInterface ? $item->getMetadata() : []; + + if (!$recompute && $metadata) { + $expiry = $metadata[ItemInterface::METADATA_EXPIRY] ?? false; + $ctime = $metadata[ItemInterface::METADATA_CTIME] ?? false; + + if ($recompute = $ctime && $expiry && $expiry <= ($now = microtime(true)) - $ctime / 1000 * $beta * log(random_int(1, PHP_INT_MAX) / PHP_INT_MAX)) { + // force applying defaultLifetime to expiry + $item->expiresAt(null); + $logger && $logger->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [ + 'key' => $key, + 'delta' => sprintf('%.1f', $expiry - $now), + ]); + } + } + + if ($recompute) { + $save = true; + $item->set($callback($item, $save)); + if ($save) { + $pool->save($item); + } + } + + return $item->get(); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/CallbackInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/CallbackInterface.php new file mode 100644 index 0000000..7dae2aa --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/CallbackInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemInterface; + +/** + * Computes and returns the cached value of an item. + * + * @author Nicolas Grekas + */ +interface CallbackInterface +{ + /** + * @param CacheItemInterface|ItemInterface $item The item to compute the value for + * @param bool &$save Should be set to false when the value should not be saved in the pool + * + * @return mixed The computed value for the passed item + */ + public function __invoke(CacheItemInterface $item, bool &$save); +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/ItemInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/ItemInterface.php new file mode 100644 index 0000000..cbd7226 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/ItemInterface.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheException; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * Augments PSR-6's CacheItemInterface with support for tags and metadata. + * + * @author Nicolas Grekas + */ +interface ItemInterface extends CacheItemInterface +{ + /** + * References the Unix timestamp stating when the item will expire. + */ + const METADATA_EXPIRY = 'expiry'; + + /** + * References the time the item took to be created, in milliseconds. + */ + const METADATA_CTIME = 'ctime'; + + /** + * References the list of tags that were assigned to the item, as string[]. + */ + const METADATA_TAGS = 'tags'; + + /** + * Reserved characters that cannot be used in a key or tag. + */ + const RESERVED_CHARACTERS = '{}()/\@:'; + + /** + * Adds a tag to a cache item. + * + * Tags are strings that follow the same validation rules as keys. + * + * @param string|string[] $tags A tag or array of tags + * + * @return $this + * + * @throws InvalidArgumentException When $tag is not valid + * @throws CacheException When the item comes from a pool that is not tag-aware + */ + public function tag($tags): self; + + /** + * Returns a list of metadata info that were saved alongside with the cached value. + * + * See ItemInterface::METADATA_* consts for keys potentially found in the returned array. + */ + public function getMetadata(): array; +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/LICENSE b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/LICENSE new file mode 100644 index 0000000..3f853aa --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/README.md b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/README.md new file mode 100644 index 0000000..58c589e --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Cache Contracts +======================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/master/README.md for more information. diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/TagAwareCacheInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/TagAwareCacheInterface.php new file mode 100644 index 0000000..7c4cf11 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/TagAwareCacheInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\InvalidArgumentException; + +/** + * Allows invalidating cached items using tags. + * + * @author Nicolas Grekas + */ +interface TagAwareCacheInterface extends CacheInterface +{ + /** + * Invalidates cached items using tags. + * + * When implemented on a PSR-6 pool, invalidation should not apply + * to deferred items. Instead, they should be committed as usual. + * This allows replacing old tagged values by new ones without + * race conditions. + * + * @param string[] $tags An array of tags to invalidate + * + * @return bool True on success + * + * @throws InvalidArgumentException When $tags is not valid + */ + public function invalidateTags(array $tags); +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/composer.json b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/composer.json new file mode 100644 index 0000000..c9cf8b5 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache-contracts/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/cache-contracts", + "type": "library", + "description": "Generic abstractions related to caching", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.2.5", + "psr/cache": "^1.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Cache\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/AbstractAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/AbstractAdapter.php new file mode 100644 index 0000000..79ecf3c --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/AbstractAdapter.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\AbstractAdapterTrait; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Nicolas Grekas + */ +abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface +{ + /** + * @internal + */ + protected const NS_SEPARATOR = ':'; + + use AbstractAdapterTrait; + use ContractsTrait; + + private static $apcuSupported; + private static $phpFilesSupported; + + protected function __construct(string $namespace = '', int $defaultLifetime = 0) + { + $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR; + if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { + throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); + } + $this->createCacheItem = \Closure::bind( + static function ($key, $value, $isHit) use ($defaultLifetime) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $v = $value; + $item->isHit = $isHit; + $item->defaultLifetime = $defaultLifetime; + // Detect wrapped values that encode for their expiry and creation duration + // For compactness, these values are packed in the key of an array using + // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { + $item->value = $v[$k]; + $v = unpack('Ve/Nc', substr($k, 1, -1)); + $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; + $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; + } + + return $item; + }, + null, + CacheItem::class + ); + $getId = \Closure::fromCallable([$this, 'getId']); + $this->mergeByLifetime = \Closure::bind( + static function ($deferred, $namespace, &$expiredIds) use ($getId) { + $byLifetime = []; + $now = microtime(true); + $expiredIds = []; + + foreach ($deferred as $key => $item) { + $key = (string) $key; + if (null === $item->expiry) { + $ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0; + } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) { + $expiredIds[] = $getId($key); + continue; + } + if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) { + unset($metadata[CacheItem::METADATA_TAGS]); + } + // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators + $byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item->value] : $item->value; + } + + return $byLifetime; + }, + null, + CacheItem::class + ); + } + + /** + * Returns the best possible adapter that your runtime supports. + * + * Using ApcuAdapter makes system caches compatible with read-only filesystems. + * + * @param string $namespace + * @param int $defaultLifetime + * @param string $version + * @param string $directory + * + * @return AdapterInterface + */ + public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null) + { + $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true); + if (null !== $logger) { + $opcache->setLogger($logger); + } + + if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) { + return $opcache; + } + + $apcu = new ApcuAdapter($namespace, (int) $defaultLifetime / 5, $version); + if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) { + $apcu->setLogger(new NullLogger()); + } elseif (null !== $logger) { + $apcu->setLogger($logger); + } + + return new ChainAdapter([$apcu, $opcache]); + } + + public static function createConnection($dsn, array $options = []) + { + if (!\is_string($dsn)) { + throw new InvalidArgumentException(sprintf('The "%s()" method expect argument #1 to be string, "%s" given.', __METHOD__, \gettype($dsn))); + } + if (0 === strpos($dsn, 'redis:') || 0 === strpos($dsn, 'rediss:')) { + return RedisAdapter::createConnection($dsn, $options); + } + if (0 === strpos($dsn, 'memcached:')) { + return MemcachedAdapter::createConnection($dsn, $options); + } + + throw new InvalidArgumentException(sprintf('Unsupported DSN: "%s".', $dsn)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + $ok = true; + $byLifetime = $this->mergeByLifetime; + $byLifetime = $byLifetime($this->deferred, $this->namespace, $expiredIds); + $retry = $this->deferred = []; + + if ($expiredIds) { + $this->doDelete($expiredIds); + } + foreach ($byLifetime as $lifetime => $values) { + try { + $e = $this->doSave($values, $lifetime); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + if (\is_array($e) || 1 === \count($values)) { + foreach (\is_array($e) ? $e : array_keys($values) as $id) { + $ok = false; + $v = $values[$id]; + $type = \is_object($v) ? \get_class($v) : \gettype($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]); + } + } else { + foreach ($values as $id => $v) { + $retry[$lifetime][] = $id; + } + } + } + + // When bulk-save failed, retry each item individually + foreach ($retry as $lifetime => $ids) { + foreach ($ids as $id) { + try { + $v = $byLifetime[$lifetime][$id]; + $e = $this->doSave([$id => $v], $lifetime); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + $ok = false; + $type = \is_object($v) ? \get_class($v) : \gettype($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]); + } + } + + return $ok; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php new file mode 100644 index 0000000..5a9f94b --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php @@ -0,0 +1,323 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Log\LoggerAwareInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\AbstractAdapterTrait; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * Abstract for native TagAware adapters. + * + * To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids + * to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate(). + * + * @author Nicolas Grekas + * @author André Rømcke + * + * @internal + */ +abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface +{ + use AbstractAdapterTrait; + use ContractsTrait; + + private const TAGS_PREFIX = "\0tags\0"; + + protected function __construct(string $namespace = '', int $defaultLifetime = 0) + { + $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':'; + if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { + throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); + } + $this->createCacheItem = \Closure::bind( + static function ($key, $value, $isHit) use ($defaultLifetime) { + $item = new CacheItem(); + $item->key = $key; + $item->defaultLifetime = $defaultLifetime; + $item->isTaggable = true; + // If structure does not match what we expect return item as is (no value and not a hit) + if (!\is_array($value) || !\array_key_exists('value', $value)) { + return $item; + } + $item->isHit = $isHit; + // Extract value, tags and meta data from the cache value + $item->value = $value['value']; + $item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? []; + if (isset($value['meta'])) { + // For compactness these values are packed, & expiry is offset to reduce size + $v = unpack('Ve/Nc', $value['meta']); + $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; + $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; + } + + return $item; + }, + null, + CacheItem::class + ); + $getId = \Closure::fromCallable([$this, 'getId']); + $tagPrefix = self::TAGS_PREFIX; + $this->mergeByLifetime = \Closure::bind( + static function ($deferred, &$expiredIds) use ($getId, $tagPrefix) { + $byLifetime = []; + $now = microtime(true); + $expiredIds = []; + + foreach ($deferred as $key => $item) { + $key = (string) $key; + if (null === $item->expiry) { + $ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0; + } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) { + $expiredIds[] = $getId($key); + continue; + } + // Store Value and Tags on the cache value + if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) { + $value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]]; + unset($metadata[CacheItem::METADATA_TAGS]); + } else { + $value = ['value' => $item->value, 'tags' => []]; + } + + if ($metadata) { + // For compactness, expiry and creation duration are packed, using magic numbers as separators + $value['meta'] = pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]); + } + + // Extract tag changes, these should be removed from values in doSave() + $value['tag-operations'] = ['add' => [], 'remove' => []]; + $oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? []; + foreach (array_diff($value['tags'], $oldTags) as $addedTag) { + $value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag); + } + foreach (array_diff($oldTags, $value['tags']) as $removedTag) { + $value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag); + } + + $byLifetime[$ttl][$getId($key)] = $value; + } + + return $byLifetime; + }, + null, + CacheItem::class + ); + } + + /** + * Persists several cache items immediately. + * + * @param array $values The values to cache, indexed by their cache identifier + * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning + * @param array[] $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag + * @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag + * + * @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not + */ + abstract protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array; + + /** + * Removes multiple items from the pool and their corresponding tags. + * + * @param array $ids An array of identifiers that should be removed from the pool + * + * @return bool True if the items were successfully removed, false otherwise + */ + abstract protected function doDelete(array $ids); + + /** + * Removes relations between tags and deleted items. + * + * @param array $tagData Array of tag => key identifiers that should be removed from the pool + */ + abstract protected function doDeleteTagRelations(array $tagData): bool; + + /** + * Invalidates cached items using tags. + * + * @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id + * + * @return bool True on success + */ + abstract protected function doInvalidate(array $tagIds): bool; + + /** + * Delete items and yields the tags they were bound to. + */ + protected function doDeleteYieldTags(array $ids): iterable + { + foreach ($this->doFetch($ids) as $id => $value) { + yield $id => \is_array($value) && \is_array($value['tags'] ?? null) ? $value['tags'] : []; + } + + $this->doDelete($ids); + } + + /** + * {@inheritdoc} + */ + public function commit(): bool + { + $ok = true; + $byLifetime = $this->mergeByLifetime; + $byLifetime = $byLifetime($this->deferred, $expiredIds); + $retry = $this->deferred = []; + + if ($expiredIds) { + // Tags are not cleaned up in this case, however that is done on invalidateTags(). + $this->doDelete($expiredIds); + } + foreach ($byLifetime as $lifetime => $values) { + try { + $values = $this->extractTagData($values, $addTagData, $removeTagData); + $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + if (\is_array($e) || 1 === \count($values)) { + foreach (\is_array($e) ? $e : array_keys($values) as $id) { + $ok = false; + $v = $values[$id]; + $type = \is_object($v) ? \get_class($v) : \gettype($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]); + } + } else { + foreach ($values as $id => $v) { + $retry[$lifetime][] = $id; + } + } + } + + // When bulk-save failed, retry each item individually + foreach ($retry as $lifetime => $ids) { + foreach ($ids as $id) { + try { + $v = $byLifetime[$lifetime][$id]; + $values = $this->extractTagData([$id => $v], $addTagData, $removeTagData); + $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + $ok = false; + $type = \is_object($v) ? \get_class($v) : \gettype($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]); + } + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys): bool + { + if (!$keys) { + return true; + } + + $ok = true; + $ids = []; + $tagData = []; + + foreach ($keys as $key) { + $ids[$key] = $this->getId($key); + unset($this->deferred[$key]); + } + + try { + foreach ($this->doDeleteYieldTags(array_values($ids)) as $id => $tags) { + foreach ($tags as $tag) { + $tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id; + } + } + } catch (\Exception $e) { + $ok = false; + } + + try { + if ((!$tagData || $this->doDeleteTagRelations($tagData)) && $ok) { + return true; + } + } catch (\Exception $e) { + } + + // When bulk-delete failed, retry each item individually + foreach ($ids as $key => $id) { + try { + $e = null; + if ($this->doDelete([$id])) { + continue; + } + } catch (\Exception $e) { + } + $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]); + $ok = false; + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) + { + if (empty($tags)) { + return false; + } + + $tagIds = []; + foreach (array_unique($tags) as $tag) { + $tagIds[] = $this->getId(self::TAGS_PREFIX.$tag); + } + + if ($this->doInvalidate($tagIds)) { + return true; + } + + return false; + } + + /** + * Extracts tags operation data from $values set in mergeByLifetime, and returns values without it. + */ + private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData): array + { + $addTagData = $removeTagData = []; + foreach ($values as $id => $value) { + foreach ($value['tag-operations']['add'] as $tag => $tagId) { + $addTagData[$tagId][] = $id; + } + + foreach ($value['tag-operations']['remove'] as $tag => $tagId) { + $removeTagData[$tagId][] = $id; + } + + unset($values[$id]['tag-operations']); + } + + return $values; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/AdapterInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/AdapterInterface.php new file mode 100644 index 0000000..fbc74ca --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/AdapterInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; + +// Help opcache.preload discover always-needed symbols +class_exists(CacheItem::class); + +/** + * Interface for adapters managing instances of Symfony's CacheItem. + * + * @author Kévin Dunglas + */ +interface AdapterInterface extends CacheItemPoolInterface +{ + /** + * {@inheritdoc} + * + * @return CacheItem + */ + public function getItem($key); + + /** + * {@inheritdoc} + * + * @return \Traversable|CacheItem[] + */ + public function getItems(array $keys = []); + + /** + * {@inheritdoc} + * + * @param string $prefix + * + * @return bool + */ + public function clear(/*string $prefix = ''*/); +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ApcuAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ApcuAdapter.php new file mode 100644 index 0000000..7db3956 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ApcuAdapter.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Traits\ApcuTrait; + +class ApcuAdapter extends AbstractAdapter +{ + use ApcuTrait; + + /** + * @throws CacheException if APCu is not enabled + */ + public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null) + { + $this->init($namespace, $defaultLifetime, $version); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ArrayAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ArrayAdapter.php new file mode 100644 index 0000000..d93dcbd --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ArrayAdapter.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Log\LoggerAwareInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ArrayTrait; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Nicolas Grekas + */ +class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface +{ + use ArrayTrait; + + private $createCacheItem; + + /** + * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise + */ + public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true) + { + $this->storeSerialized = $storeSerialized; + $this->createCacheItem = \Closure::bind( + static function ($key, $value, $isHit) use ($defaultLifetime) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + $item->defaultLifetime = $defaultLifetime; + + return $item; + }, + null, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + $item = $this->getItem($key); + $metadata = $item->getMetadata(); + + // ArrayAdapter works in memory, we don't care about stampede protection + if (INF === $beta || !$item->isHit()) { + $save = true; + $this->save($item->set($callback($item, $save))); + } + + return $item->get(); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + if (!$isHit = $this->hasItem($key)) { + $this->values[$key] = $value = null; + } else { + $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key]; + } + $f = $this->createCacheItem; + + return $f($key, $value, $isHit); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + foreach ($keys as $key) { + if (!\is_string($key) || !isset($this->expiries[$key])) { + CacheItem::validateKey($key); + } + } + + return $this->generateItems($keys, microtime(true), $this->createCacheItem); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + foreach ($keys as $key) { + $this->deleteItem($key); + } + + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + $item = (array) $item; + $key = $item["\0*\0key"]; + $value = $item["\0*\0value"]; + $expiry = $item["\0*\0expiry"]; + + if (null !== $expiry && $expiry <= microtime(true)) { + $this->deleteItem($key); + + return true; + } + if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) { + return false; + } + if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) { + $expiry = microtime(true) + $item["\0*\0defaultLifetime"]; + } + + $this->values[$key] = $value; + $this->expiries[$key] = null !== $expiry ? $expiry : PHP_INT_MAX; + + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + return $this->save($item); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function delete(string $key): bool + { + return $this->deleteItem($key); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ChainAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ChainAdapter.php new file mode 100644 index 0000000..63e97a8 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ChainAdapter.php @@ -0,0 +1,332 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Chains several adapters together. + * + * Cached items are fetched from the first adapter having them in its data store. + * They are saved and deleted in all adapters at once. + * + * @author Kévin Dunglas + */ +class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + use ContractsTrait; + + private $adapters = []; + private $adapterCount; + private $syncItem; + + /** + * @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items + * @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones + */ + public function __construct(array $adapters, int $defaultLifetime = 0) + { + if (!$adapters) { + throw new InvalidArgumentException('At least one adapter must be specified.'); + } + + foreach ($adapters as $adapter) { + if (!$adapter instanceof CacheItemPoolInterface) { + throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($adapter), CacheItemPoolInterface::class)); + } + + if ($adapter instanceof AdapterInterface) { + $this->adapters[] = $adapter; + } else { + $this->adapters[] = new ProxyAdapter($adapter); + } + } + $this->adapterCount = \count($this->adapters); + + $this->syncItem = \Closure::bind( + static function ($sourceItem, $item, $sourceMetadata = null) use ($defaultLifetime) { + $sourceItem->isTaggable = false; + $sourceMetadata = $sourceMetadata ?? $sourceItem->metadata; + unset($sourceMetadata[CacheItem::METADATA_TAGS]); + + $item->value = $sourceItem->value; + $item->expiry = $sourceMetadata[CacheItem::METADATA_EXPIRY] ?? $sourceItem->expiry; + $item->isHit = $sourceItem->isHit; + $item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata; + + if (0 < $sourceItem->defaultLifetime && $sourceItem->defaultLifetime < $defaultLifetime) { + $defaultLifetime = $sourceItem->defaultLifetime; + } + if (0 < $defaultLifetime && ($item->defaultLifetime <= 0 || $defaultLifetime < $item->defaultLifetime)) { + $item->defaultLifetime = $defaultLifetime; + } + + return $item; + }, + null, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + $lastItem = null; + $i = 0; + $wrap = function (CacheItem $item = null) use ($key, $callback, $beta, &$wrap, &$i, &$lastItem, &$metadata) { + $adapter = $this->adapters[$i]; + if (isset($this->adapters[++$i])) { + $callback = $wrap; + $beta = INF === $beta ? INF : 0; + } + if ($adapter instanceof CacheInterface) { + $value = $adapter->get($key, $callback, $beta, $metadata); + } else { + $value = $this->doGet($adapter, $key, $callback, $beta, $metadata); + } + if (null !== $item) { + ($this->syncItem)($lastItem = $lastItem ?? $item, $item, $metadata); + } + + return $value; + }; + + return $wrap(); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $syncItem = $this->syncItem; + $misses = []; + + foreach ($this->adapters as $i => $adapter) { + $item = $adapter->getItem($key); + + if ($item->isHit()) { + while (0 <= --$i) { + $this->adapters[$i]->save($syncItem($item, $misses[$i])); + } + + return $item; + } + + $misses[$i] = $item; + } + + return $item; + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + return $this->generateItems($this->adapters[0]->getItems($keys), 0); + } + + private function generateItems(iterable $items, int $adapterIndex) + { + $missing = []; + $misses = []; + $nextAdapterIndex = $adapterIndex + 1; + $nextAdapter = isset($this->adapters[$nextAdapterIndex]) ? $this->adapters[$nextAdapterIndex] : null; + + foreach ($items as $k => $item) { + if (!$nextAdapter || $item->isHit()) { + yield $k => $item; + } else { + $missing[] = $k; + $misses[$k] = $item; + } + } + + if ($missing) { + $syncItem = $this->syncItem; + $adapter = $this->adapters[$adapterIndex]; + $items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex); + + foreach ($items as $k => $item) { + if ($item->isHit()) { + $adapter->save($syncItem($item, $misses[$k])); + } + + yield $k => $item; + } + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + foreach ($this->adapters as $adapter) { + if ($adapter->hasItem($key)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + * + * @param string $prefix + * + * @return bool + */ + public function clear(/*string $prefix = ''*/) + { + $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : ''; + $cleared = true; + $i = $this->adapterCount; + + while ($i--) { + if ($this->adapters[$i] instanceof AdapterInterface) { + $cleared = $this->adapters[$i]->clear($prefix) && $cleared; + } else { + $cleared = $this->adapters[$i]->clear() && $cleared; + } + } + + return $cleared; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + $deleted = true; + $i = $this->adapterCount; + + while ($i--) { + $deleted = $this->adapters[$i]->deleteItem($key) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + $deleted = true; + $i = $this->adapterCount; + + while ($i--) { + $deleted = $this->adapters[$i]->deleteItems($keys) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + $saved = true; + $i = $this->adapterCount; + + while ($i--) { + $saved = $this->adapters[$i]->save($item) && $saved; + } + + return $saved; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + $saved = true; + $i = $this->adapterCount; + + while ($i--) { + $saved = $this->adapters[$i]->saveDeferred($item) && $saved; + } + + return $saved; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + $committed = true; + $i = $this->adapterCount; + + while ($i--) { + $committed = $this->adapters[$i]->commit() && $committed; + } + + return $committed; + } + + /** + * {@inheritdoc} + */ + public function prune() + { + $pruned = true; + + foreach ($this->adapters as $adapter) { + if ($adapter instanceof PruneableInterface) { + $pruned = $adapter->prune() && $pruned; + } + } + + return $pruned; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + foreach ($this->adapters as $adapter) { + if ($adapter instanceof ResetInterface) { + $adapter->reset(); + } + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/DoctrineAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/DoctrineAdapter.php new file mode 100644 index 0000000..75ae4cb --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/DoctrineAdapter.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Doctrine\Common\Cache\CacheProvider; +use Symfony\Component\Cache\Traits\DoctrineTrait; + +class DoctrineAdapter extends AbstractAdapter +{ + use DoctrineTrait; + + public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0) + { + parent::__construct('', $defaultLifetime); + $this->provider = $provider; + $provider->setNamespace($namespace); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/FilesystemAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/FilesystemAdapter.php new file mode 100644 index 0000000..7185dd4 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/FilesystemAdapter.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\FilesystemTrait; + +class FilesystemAdapter extends AbstractAdapter implements PruneableInterface +{ + use FilesystemTrait; + + public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null) + { + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php new file mode 100644 index 0000000..d9a1ad3 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Marshaller\TagAwareMarshaller; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\FilesystemTrait; + +/** + * Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls. + * + * @author Nicolas Grekas + * @author André Rømcke + */ +class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface +{ + use FilesystemTrait { + doClear as private doClearCache; + doSave as private doSaveCache; + } + + /** + * Folder used for tag symlinks. + */ + private const TAG_FOLDER = 'tags'; + + public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null) + { + $this->marshaller = new TagAwareMarshaller($marshaller); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + } + + /** + * {@inheritdoc} + */ + protected function doClear($namespace) + { + $ok = $this->doClearCache($namespace); + + if ('' !== $namespace) { + return $ok; + } + + set_error_handler(static function () {}); + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + try { + foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) { + if (rename($dir, $renamed = substr_replace($dir, bin2hex(random_bytes(4)), -8))) { + $dir = $renamed.\DIRECTORY_SEPARATOR; + } else { + $dir .= \DIRECTORY_SEPARATOR; + $renamed = null; + } + + for ($i = 0; $i < 38; ++$i) { + if (!file_exists($dir.$chars[$i])) { + continue; + } + for ($j = 0; $j < 38; ++$j) { + if (!file_exists($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + foreach (scandir($d, SCANDIR_SORT_NONE) ?: [] as $link) { + if ('.' !== $link && '..' !== $link && (null !== $renamed || !realpath($d.\DIRECTORY_SEPARATOR.$link))) { + unlink($d.\DIRECTORY_SEPARATOR.$link); + } + } + null === $renamed ?: rmdir($d); + } + null === $renamed ?: rmdir($dir.$chars[$i]); + } + null === $renamed ?: rmdir($renamed); + } + } finally { + restore_error_handler(); + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array + { + $failed = $this->doSaveCache($values, $lifetime); + + // Add Tags as symlinks + foreach ($addTagData as $tagId => $ids) { + $tagFolder = $this->getTagFolder($tagId); + foreach ($ids as $id) { + if ($failed && \in_array($id, $failed, true)) { + continue; + } + + $file = $this->getFile($id); + + if (!@symlink($file, $this->getFile($id, true, $tagFolder))) { + @unlink($file); + $failed[] = $id; + } + } + } + + // Unlink removed Tags + foreach ($removeTagData as $tagId => $ids) { + $tagFolder = $this->getTagFolder($tagId); + foreach ($ids as $id) { + if ($failed && \in_array($id, $failed, true)) { + continue; + } + + @unlink($this->getFile($id, false, $tagFolder)); + } + } + + return $failed; + } + + /** + * {@inheritdoc} + */ + protected function doDeleteYieldTags(array $ids): iterable + { + foreach ($ids as $id) { + $file = $this->getFile($id); + if (!file_exists($file) || !$h = @fopen($file, 'rb')) { + continue; + } + + if ((\PHP_VERSION_ID >= 70300 || '\\' !== \DIRECTORY_SEPARATOR) && !@unlink($file)) { + fclose($h); + continue; + } + + $meta = explode("\n", fread($h, 4096), 3)[2] ?? ''; + + // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (13 < \strlen($meta) && "\x9D" === $meta[0] && "\0" === $meta[5] && "\x5F" === $meta[9]) { + $meta[9] = "\0"; + $tagLen = unpack('Nlen', $meta, 9)['len']; + $meta = substr($meta, 13, $tagLen); + + if (0 < $tagLen -= \strlen($meta)) { + $meta .= fread($h, $tagLen); + } + + try { + yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta); + } catch (\Exception $e) { + yield $id => []; + } + } + + fclose($h); + + if (\PHP_VERSION_ID < 70300 && '\\' === \DIRECTORY_SEPARATOR) { + @unlink($file); + } + } + } + + /** + * {@inheritdoc} + */ + protected function doDeleteTagRelations(array $tagData): bool + { + foreach ($tagData as $tagId => $idList) { + $tagFolder = $this->getTagFolder($tagId); + foreach ($idList as $id) { + @unlink($this->getFile($id, false, $tagFolder)); + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doInvalidate(array $tagIds): bool + { + foreach ($tagIds as $tagId) { + if (!file_exists($tagFolder = $this->getTagFolder($tagId))) { + continue; + } + + set_error_handler(static function () {}); + + try { + if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -9))) { + $tagFolder = $renamed.\DIRECTORY_SEPARATOR; + } else { + $renamed = null; + } + + foreach ($this->scanHashDir($tagFolder) as $itemLink) { + unlink(realpath($itemLink) ?: $itemLink); + unlink($itemLink); + } + + if (null === $renamed) { + continue; + } + + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + for ($i = 0; $i < 38; ++$i) { + for ($j = 0; $j < 38; ++$j) { + rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]); + } + rmdir($tagFolder.$chars[$i]); + } + rmdir($renamed); + } finally { + restore_error_handler(); + } + } + + return true; + } + + private function getTagFolder(string $tagId): string + { + return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/MemcachedAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/MemcachedAdapter.php new file mode 100644 index 0000000..b678bb5 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/MemcachedAdapter.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Traits\MemcachedTrait; + +class MemcachedAdapter extends AbstractAdapter +{ + use MemcachedTrait; + + protected $maxIdLength = 250; + + /** + * Using a MemcachedAdapter with a TagAwareAdapter for storing tags is discouraged. + * Using a RedisAdapter is recommended instead. If you cannot do otherwise, be aware that: + * - the Memcached::OPT_BINARY_PROTOCOL must be enabled + * (that's the default when using MemcachedAdapter::createConnection()); + * - tags eviction by Memcached's LRU algorithm will break by-tags invalidation; + * your Memcached memory should be large enough to never trigger LRU. + * + * Using a MemcachedAdapter as a pure items store is fine. + */ + public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + { + $this->init($client, $namespace, $defaultLifetime, $marshaller); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/NullAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/NullAdapter.php new file mode 100644 index 0000000..a2fdd36 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/NullAdapter.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Titouan Galopin + */ +class NullAdapter implements AdapterInterface, CacheInterface +{ + private $createCacheItem; + + public function __construct() + { + $this->createCacheItem = \Closure::bind( + function ($key) { + $item = new CacheItem(); + $item->key = $key; + $item->isHit = false; + + return $item; + }, + $this, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + $save = true; + + return $callback(($this->createCacheItem)($key), $save); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $f = $this->createCacheItem; + + return $f($key); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + return $this->generateItems($keys); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + return false; + } + + /** + * {@inheritdoc} + * + * @param string $prefix + * + * @return bool + */ + public function clear(/*string $prefix = ''*/) + { + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + return false; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + return false; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function delete(string $key): bool + { + return $this->deleteItem($key); + } + + private function generateItems(array $keys) + { + $f = $this->createCacheItem; + + foreach ($keys as $key) { + yield $key => $f($key); + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/PdoAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/PdoAdapter.php new file mode 100644 index 0000000..d118736 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/PdoAdapter.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Doctrine\DBAL\Connection; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\PdoTrait; + +class PdoAdapter extends AbstractAdapter implements PruneableInterface +{ + use PdoTrait; + + protected $maxIdLength = 255; + + /** + * You can either pass an existing database connection as PDO instance or + * a Doctrine DBAL Connection or a DSN string that will be used to + * lazy-connect to the database when the cache is actually used. + * + * When a Doctrine DBAL Connection is passed, the cache table is created + * automatically when possible. Otherwise, use the createTable() method. + * + * List of available options: + * * db_table: The name of the table [default: cache_items] + * * db_id_col: The column where to store the cache id [default: item_id] + * * db_data_col: The column where to store the cache data [default: item_data] + * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime] + * * db_time_col: The column where to store the timestamp [default: item_time] + * * db_username: The username when lazy-connect [default: ''] + * * db_password: The password when lazy-connect [default: ''] + * * db_connection_options: An array of driver-specific connection options [default: []] + * + * @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null + * + * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string + * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + * @throws InvalidArgumentException When namespace contains invalid characters + */ + public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null) + { + $this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/PhpArrayAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/PhpArrayAdapter.php new file mode 100644 index 0000000..b4f13d1 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/PhpArrayAdapter.php @@ -0,0 +1,332 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Component\Cache\Traits\PhpArrayTrait; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0. + * Warmed up items are read-only and run-time discovered items are cached using a fallback adapter. + * + * @author Titouan Galopin + * @author Nicolas Grekas + */ +class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + use PhpArrayTrait; + use ContractsTrait; + + private $createCacheItem; + + /** + * @param string $file The PHP file were values are cached + * @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit + */ + public function __construct(string $file, AdapterInterface $fallbackPool) + { + $this->file = $file; + $this->pool = $fallbackPool; + $this->createCacheItem = \Closure::bind( + static function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + + return $item; + }, + null, + CacheItem::class + ); + } + + /** + * This adapter takes advantage of how PHP stores arrays in its latest versions. + * + * @param string $file The PHP file were values are cached + * @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit + * + * @return CacheItemPoolInterface + */ + public static function create($file, CacheItemPoolInterface $fallbackPool) + { + if (!$fallbackPool instanceof AdapterInterface) { + $fallbackPool = new ProxyAdapter($fallbackPool); + } + + return new static($file, $fallbackPool); + } + + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + if (null === $this->values) { + $this->initialize(); + } + if (!isset($this->keys[$key])) { + get_from_pool: + if ($this->pool instanceof CacheInterface) { + return $this->pool->get($key, $callback, $beta, $metadata); + } + + return $this->doGet($this->pool, $key, $callback, $beta, $metadata); + } + $value = $this->values[$this->keys[$key]]; + + if ('N;' === $value) { + return null; + } + try { + if ($value instanceof \Closure) { + return $value(); + } + } catch (\Throwable $e) { + unset($this->keys[$key]); + goto get_from_pool; + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + if (null === $this->values) { + $this->initialize(); + } + if (!isset($this->keys[$key])) { + return $this->pool->getItem($key); + } + + $value = $this->values[$this->keys[$key]]; + $isHit = true; + + if ('N;' === $value) { + $value = null; + } elseif ($value instanceof \Closure) { + try { + $value = $value(); + } catch (\Throwable $e) { + $value = null; + $isHit = false; + } + } + + $f = $this->createCacheItem; + + return $f($key, $value, $isHit); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + foreach ($keys as $key) { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + } + if (null === $this->values) { + $this->initialize(); + } + + return $this->generateItems($keys); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + if (null === $this->values) { + $this->initialize(); + } + + return isset($this->keys[$key]) || $this->pool->hasItem($key); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + if (null === $this->values) { + $this->initialize(); + } + + return !isset($this->keys[$key]) && $this->pool->deleteItem($key); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + $deleted = true; + $fallbackKeys = []; + + foreach ($keys as $key) { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + + if (isset($this->keys[$key])) { + $deleted = false; + } else { + $fallbackKeys[] = $key; + } + } + if (null === $this->values) { + $this->initialize(); + } + + if ($fallbackKeys) { + $deleted = $this->pool->deleteItems($fallbackKeys) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + if (null === $this->values) { + $this->initialize(); + } + + return !isset($this->keys[$item->getKey()]) && $this->pool->save($item); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + if (null === $this->values) { + $this->initialize(); + } + + return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + return $this->pool->commit(); + } + + private function generateItems(array $keys): \Generator + { + $f = $this->createCacheItem; + $fallbackKeys = []; + + foreach ($keys as $key) { + if (isset($this->keys[$key])) { + $value = $this->values[$this->keys[$key]]; + + if ('N;' === $value) { + yield $key => $f($key, null, true); + } elseif ($value instanceof \Closure) { + try { + yield $key => $f($key, $value(), true); + } catch (\Throwable $e) { + yield $key => $f($key, null, false); + } + } else { + yield $key => $f($key, $value, true); + } + } else { + $fallbackKeys[] = $key; + } + } + + if ($fallbackKeys) { + yield from $this->pool->getItems($fallbackKeys); + } + } + + /** + * @throws \ReflectionException When $class is not found and is required + * + * @internal to be removed in Symfony 5.0 + */ + public static function throwOnRequiredClass($class) + { + $e = new \ReflectionException("Class $class does not exist"); + $trace = debug_backtrace(); + $autoloadFrame = [ + 'function' => 'spl_autoload_call', + 'args' => [$class], + ]; + $i = 1 + array_search($autoloadFrame, $trace, true); + + if (isset($trace[$i]['function']) && !isset($trace[$i]['class'])) { + switch ($trace[$i]['function']) { + case 'get_class_methods': + case 'get_class_vars': + case 'get_parent_class': + case 'is_a': + case 'is_subclass_of': + case 'class_exists': + case 'class_implements': + case 'class_parents': + case 'trait_exists': + case 'defined': + case 'interface_exists': + case 'method_exists': + case 'property_exists': + case 'is_callable': + return; + } + } + + throw $e; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/PhpFilesAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/PhpFilesAdapter.php new file mode 100644 index 0000000..10938a0 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/PhpFilesAdapter.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\PhpFilesTrait; + +class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface +{ + use PhpFilesTrait; + + /** + * @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire. + * Doing so is encouraged because it fits perfectly OPcache's memory model. + * + * @throws CacheException if OPcache is not enabled + */ + public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false) + { + $this->appendOnly = $appendOnly; + self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time(); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + $this->includeHandler = static function ($type, $msg, $file, $line) { + throw new \ErrorException($msg, 0, $type, $file, $line); + }; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ProxyAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ProxyAdapter.php new file mode 100644 index 0000000..bccafb3 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/ProxyAdapter.php @@ -0,0 +1,269 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Component\Cache\Traits\ProxyTrait; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Nicolas Grekas + */ +class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + use ProxyTrait; + use ContractsTrait; + + private $namespace; + private $namespaceLen; + private $createCacheItem; + private $setInnerItem; + private $poolHash; + + public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0) + { + $this->pool = $pool; + $this->poolHash = $poolHash = spl_object_hash($pool); + $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace); + $this->namespaceLen = \strlen($namespace); + $this->createCacheItem = \Closure::bind( + static function ($key, $innerItem) use ($defaultLifetime, $poolHash) { + $item = new CacheItem(); + $item->key = $key; + + if (null === $innerItem) { + return $item; + } + + $item->value = $v = $innerItem->get(); + $item->isHit = $innerItem->isHit(); + $item->innerItem = $innerItem; + $item->defaultLifetime = $defaultLifetime; + $item->poolHash = $poolHash; + + // Detect wrapped values that encode for their expiry and creation duration + // For compactness, these values are packed in the key of an array using + // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { + $item->value = $v[$k]; + $v = unpack('Ve/Nc', substr($k, 1, -1)); + $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; + $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; + } elseif ($innerItem instanceof CacheItem) { + $item->metadata = $innerItem->metadata; + } + $innerItem->set(null); + + return $item; + }, + null, + CacheItem::class + ); + $this->setInnerItem = \Closure::bind( + /** + * @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix + */ + static function (CacheItemInterface $innerItem, array $item) { + // Tags are stored separately, no need to account for them when considering this item's newly set metadata + if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) { + unset($metadata[CacheItem::METADATA_TAGS]); + } + if ($metadata) { + // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators + $item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]]; + } + $innerItem->set($item["\0*\0value"]); + $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6f', $item["\0*\0expiry"])) : null); + }, + null, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + if (!$this->pool instanceof CacheInterface) { + return $this->doGet($this, $key, $callback, $beta, $metadata); + } + + return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) { + $item = ($this->createCacheItem)($key, $innerItem); + $item->set($value = $callback($item, $save)); + ($this->setInnerItem)($innerItem, (array) $item); + + return $value; + }, $beta, $metadata); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $f = $this->createCacheItem; + $item = $this->pool->getItem($this->getId($key)); + + return $f($key, $item); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + if ($this->namespaceLen) { + foreach ($keys as $i => $key) { + $keys[$i] = $this->getId($key); + } + } + + return $this->generateItems($this->pool->getItems($keys)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + return $this->pool->hasItem($this->getId($key)); + } + + /** + * {@inheritdoc} + * + * @param string $prefix + * + * @return bool + */ + public function clear(/*string $prefix = ''*/) + { + $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : ''; + + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($this->namespace.$prefix); + } + + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + return $this->pool->deleteItem($this->getId($key)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + if ($this->namespaceLen) { + foreach ($keys as $i => $key) { + $keys[$i] = $this->getId($key); + } + } + + return $this->pool->deleteItems($keys); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + return $this->doSave($item, __FUNCTION__); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + return $this->doSave($item, __FUNCTION__); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + return $this->pool->commit(); + } + + private function doSave(CacheItemInterface $item, string $method) + { + if (!$item instanceof CacheItem) { + return false; + } + $item = (array) $item; + if (null === $item["\0*\0expiry"] && 0 < $item["\0*\0defaultLifetime"]) { + $item["\0*\0expiry"] = microtime(true) + $item["\0*\0defaultLifetime"]; + } + + if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) { + $innerItem = $item["\0*\0innerItem"]; + } elseif ($this->pool instanceof AdapterInterface) { + // this is an optimization specific for AdapterInterface implementations + // so we can save a round-trip to the backend by just creating a new item + $f = $this->createCacheItem; + $innerItem = $f($this->namespace.$item["\0*\0key"], null); + } else { + $innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]); + } + + ($this->setInnerItem)($innerItem, $item); + + return $this->pool->$method($innerItem); + } + + private function generateItems(iterable $items) + { + $f = $this->createCacheItem; + + foreach ($items as $key => $item) { + if ($this->namespaceLen) { + $key = substr($key, $this->namespaceLen); + } + + yield $key => $f($key, $item); + } + } + + private function getId($key): string + { + CacheItem::validateKey($key); + + return $this->namespace.$key; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/Psr16Adapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/Psr16Adapter.php new file mode 100644 index 0000000..bb38871 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/Psr16Adapter.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\SimpleCache\CacheInterface; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ProxyTrait; + +/** + * Turns a PSR-16 cache into a PSR-6 one. + * + * @author Nicolas Grekas + */ +class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface +{ + /** + * @internal + */ + protected const NS_SEPARATOR = '_'; + + use ProxyTrait; + + private $miss; + + public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0) + { + parent::__construct($namespace, $defaultLifetime); + + $this->pool = $pool; + $this->miss = new \stdClass(); + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) { + if ($this->miss !== $value) { + yield $key => $value; + } + } + } + + /** + * {@inheritdoc} + */ + protected function doHave($id) + { + return $this->pool->has($id); + } + + /** + * {@inheritdoc} + */ + protected function doClear($namespace) + { + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + return $this->pool->deleteMultiple($ids); + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/RedisAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/RedisAdapter.php new file mode 100644 index 0000000..5c49f7a --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/RedisAdapter.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Traits\RedisTrait; + +class RedisAdapter extends AbstractAdapter +{ + use RedisTrait; + + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client + * @param string $namespace The default namespace + * @param int $defaultLifetime The default lifetime + */ + public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + { + $this->init($redisClient, $namespace, $defaultLifetime, $marshaller); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php new file mode 100644 index 0000000..f936afd --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php @@ -0,0 +1,292 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Predis\Connection\Aggregate\ClusterInterface; +use Predis\Connection\Aggregate\PredisCluster; +use Predis\Response\Status; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DeflateMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Marshaller\TagAwareMarshaller; +use Symfony\Component\Cache\Traits\RedisTrait; + +/** + * Stores tag id <> cache id relationship as a Redis Set, lookup on invalidation using RENAME+SMEMBERS. + * + * Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even + * if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache + * relationship survives eviction (cache cleanup when Redis runs out of memory). + * + * Requirements: + * - Client: PHP Redis or Predis + * Note: Due to lack of RENAME support it is NOT recommended to use Cluster on Predis, instead use phpredis. + * - Server: Redis 2.8+ + * Configured with any `volatile-*` eviction policy, OR `noeviction` if it will NEVER fill up memory + * + * Design limitations: + * - Max 4 billion cache keys per cache tag as limited by Redis Set datatype. + * E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 4 billion cache items also. + * + * @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies. + * @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype. + * + * @author Nicolas Grekas + * @author André Rømcke + */ +class RedisTagAwareAdapter extends AbstractTagAwareAdapter +{ + use RedisTrait; + + /** + * Limits for how many keys are deleted in batch. + */ + private const BULK_DELETE_LIMIT = 10000; + + /** + * On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are + * preferred to be evicted over tag Sets, if eviction policy is configured according to requirements. + */ + private const DEFAULT_CACHE_TTL = 8640000; + + /** + * @var string|null detected eviction policy used on Redis server + */ + private $redisEvictionPolicy; + + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client + * @param string $namespace The default namespace + * @param int $defaultLifetime The default lifetime + */ + public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + { + if ($redisClient instanceof \Predis\ClientInterface && $redisClient->getConnection() instanceof ClusterInterface && !$redisClient->getConnection() instanceof PredisCluster) { + throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, \get_class($redisClient->getConnection()))); + } + + if (\defined('Redis::OPT_COMPRESSION') && ($redisClient instanceof \Redis || $redisClient instanceof \RedisArray || $redisClient instanceof \RedisCluster)) { + $compression = $redisClient->getOption(\Redis::OPT_COMPRESSION); + + foreach (\is_array($compression) ? $compression : [$compression] as $c) { + if (\Redis::COMPRESSION_NONE !== $c) { + throw new InvalidArgumentException(sprintf('phpredis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class)); + } + } + } + + $this->init($redisClient, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller)); + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $delTagData = []): array + { + $eviction = $this->getRedisEvictionPolicy(); + if ('noeviction' !== $eviction && 0 !== strpos($eviction, 'volatile-')) { + CacheItem::log($this->logger, sprintf('Redis maxmemory-policy setting "%s" is *not* supported by RedisTagAwareAdapter, use "noeviction" or "volatile-*" eviction policies', $eviction)); + + return false; + } + + // serialize values + if (!$serialized = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + // While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op + $results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData, $failed) { + // Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one + foreach ($serialized as $id => $value) { + yield 'setEx' => [ + $id, + 0 >= $lifetime ? self::DEFAULT_CACHE_TTL : $lifetime, + $value, + ]; + } + + // Add and Remove Tags + foreach ($addTagData as $tagId => $ids) { + if (!$failed || $ids = array_diff($ids, $failed)) { + yield 'sAdd' => array_merge([$tagId], $ids); + } + } + + foreach ($delTagData as $tagId => $ids) { + if (!$failed || $ids = array_diff($ids, $failed)) { + yield 'sRem' => array_merge([$tagId], $ids); + } + } + }); + + foreach ($results as $id => $result) { + // Skip results of SADD/SREM operations, they'll be 1 or 0 depending on if set value already existed or not + if (is_numeric($result)) { + continue; + } + // setEx results + if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) { + $failed[] = $id; + } + } + + return $failed; + } + + /** + * {@inheritdoc} + */ + protected function doDeleteYieldTags(array $ids): iterable + { + $lua = <<<'EOLUA' + local v = redis.call('GET', KEYS[1]) + redis.call('DEL', KEYS[1]) + + if not v or v:len() <= 13 or v:byte(1) ~= 0x9D or v:byte(6) ~= 0 or v:byte(10) ~= 0x5F then + return '' + end + + return v:sub(14, 13 + v:byte(13) + v:byte(12) * 256 + v:byte(11) * 65536) +EOLUA; + + if ($this->redis instanceof \Predis\ClientInterface) { + $evalArgs = [$lua, 1, &$id]; + } else { + $evalArgs = [$lua, [&$id], 1]; + } + + $results = $this->pipeline(function () use ($ids, &$id, $evalArgs) { + foreach ($ids as $id) { + yield 'eval' => $evalArgs; + } + }); + + foreach ($results as $id => $result) { + try { + yield $id => !\is_string($result) || '' === $result ? [] : $this->marshaller->unmarshall($result); + } catch (\Exception $e) { + yield $id => []; + } + } + } + + /** + * {@inheritdoc} + */ + protected function doDeleteTagRelations(array $tagData): bool + { + $this->pipeline(static function () use ($tagData) { + foreach ($tagData as $tagId => $idList) { + array_unshift($idList, $tagId); + yield 'sRem' => $idList; + } + })->rewind(); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doInvalidate(array $tagIds): bool + { + if (!$this->redis instanceof \Predis\ClientInterface || !$this->redis->getConnection() instanceof PredisCluster) { + $movedTagSetIds = $this->renameKeys($this->redis, $tagIds); + } else { + $clusterConnection = $this->redis->getConnection(); + $tagIdsByConnection = new \SplObjectStorage(); + $movedTagSetIds = []; + + foreach ($tagIds as $id) { + $connection = $clusterConnection->getConnectionByKey($id); + $slot = $tagIdsByConnection[$connection] ?? $tagIdsByConnection[$connection] = new \ArrayObject(); + $slot[] = $id; + } + + foreach ($tagIdsByConnection as $connection) { + $slot = $tagIdsByConnection[$connection]; + $movedTagSetIds = array_merge($movedTagSetIds, $this->renameKeys(new $this->redis($connection, $this->redis->getOptions()), $slot->getArrayCopy())); + } + } + + // No Sets found + if (!$movedTagSetIds) { + return false; + } + + // Now safely take the time to read the keys in each set and collect ids we need to delete + $tagIdSets = $this->pipeline(static function () use ($movedTagSetIds) { + foreach ($movedTagSetIds as $movedTagId) { + yield 'sMembers' => [$movedTagId]; + } + }); + + // Return combination of the temporary Tag Set ids and their values (cache ids) + $ids = array_merge($movedTagSetIds, ...iterator_to_array($tagIdSets, false)); + + // Delete cache in chunks to avoid overloading the connection + foreach (array_chunk(array_unique($ids), self::BULK_DELETE_LIMIT) as $chunkIds) { + $this->doDelete($chunkIds); + } + + return true; + } + + /** + * Renames several keys in order to be able to operate on them without risk of race conditions. + * + * Filters out keys that do not exist before returning new keys. + * + * @see https://redis.io/commands/rename + * @see https://redis.io/topics/cluster-spec#keys-hash-tags + * + * @return array Filtered list of the valid moved keys (only those that existed) + */ + private function renameKeys($redis, array $ids): array + { + $newIds = []; + $uniqueToken = bin2hex(random_bytes(10)); + + $results = $this->pipeline(static function () use ($ids, $uniqueToken) { + foreach ($ids as $id) { + yield 'rename' => [$id, '{'.$id.'}'.$uniqueToken]; + } + }, $redis); + + foreach ($results as $id => $result) { + if (true === $result || ($result instanceof Status && Status::get('OK') === $result)) { + // Only take into account if ok (key existed), will be false on phpredis if it did not exist + $newIds[] = '{'.$id.'}'.$uniqueToken; + } + } + + return $newIds; + } + + private function getRedisEvictionPolicy(): string + { + if (null !== $this->redisEvictionPolicy) { + return $this->redisEvictionPolicy; + } + + foreach ($this->getHosts() as $host) { + $info = $host->info('Memory'); + $info = isset($info['Memory']) ? $info['Memory'] : $info; + + return $this->redisEvictionPolicy = $info['maxmemory_policy']; + } + + return $this->redisEvictionPolicy = ''; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/SimpleCacheAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/SimpleCacheAdapter.php new file mode 100644 index 0000000..d0d42e5 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/SimpleCacheAdapter.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +@trigger_error(sprintf('The "%s" class is @deprecated since Symfony 4.3, use "Psr16Adapter" instead.', SimpleCacheAdapter::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use Psr16Adapter instead. + */ +class SimpleCacheAdapter extends Psr16Adapter +{ +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TagAwareAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TagAwareAdapter.php new file mode 100644 index 0000000..7c0ee30 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TagAwareAdapter.php @@ -0,0 +1,429 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Component\Cache\Traits\ProxyTrait; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * @author Nicolas Grekas + */ +class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface +{ + const TAGS_PREFIX = "\0tags\0"; + + use ProxyTrait; + use ContractsTrait; + + private $deferred = []; + private $createCacheItem; + private $setCacheItemTags; + private $getTagsByKey; + private $invalidateTags; + private $tags; + private $knownTagVersions = []; + private $knownTagVersionsTtl; + + public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, float $knownTagVersionsTtl = 0.15) + { + $this->pool = $itemsPool; + $this->tags = $tagsPool ?: $itemsPool; + $this->knownTagVersionsTtl = $knownTagVersionsTtl; + $this->createCacheItem = \Closure::bind( + static function ($key, $value, CacheItem $protoItem) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->defaultLifetime = $protoItem->defaultLifetime; + $item->expiry = $protoItem->expiry; + $item->poolHash = $protoItem->poolHash; + + return $item; + }, + null, + CacheItem::class + ); + $this->setCacheItemTags = \Closure::bind( + static function (CacheItem $item, $key, array &$itemTags) { + $item->isTaggable = true; + if (!$item->isHit) { + return $item; + } + if (isset($itemTags[$key])) { + foreach ($itemTags[$key] as $tag => $version) { + $item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag; + } + unset($itemTags[$key]); + } else { + $item->value = null; + $item->isHit = false; + } + + return $item; + }, + null, + CacheItem::class + ); + $this->getTagsByKey = \Closure::bind( + static function ($deferred) { + $tagsByKey = []; + foreach ($deferred as $key => $item) { + $tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? []; + } + + return $tagsByKey; + }, + null, + CacheItem::class + ); + $this->invalidateTags = \Closure::bind( + static function (AdapterInterface $tagsAdapter, array $tags) { + foreach ($tags as $v) { + $v->defaultLifetime = 0; + $v->expiry = null; + $tagsAdapter->saveDeferred($v); + } + + return $tagsAdapter->commit(); + }, + null, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) + { + $ok = true; + $tagsByKey = []; + $invalidatedTags = []; + foreach ($tags as $tag) { + CacheItem::validateKey($tag); + $invalidatedTags[$tag] = 0; + } + + if ($this->deferred) { + $items = $this->deferred; + foreach ($items as $key => $item) { + if (!$this->pool->saveDeferred($item)) { + unset($this->deferred[$key]); + $ok = false; + } + } + + $f = $this->getTagsByKey; + $tagsByKey = $f($items); + $this->deferred = []; + } + + $tagVersions = $this->getTagVersions($tagsByKey, $invalidatedTags); + $f = $this->createCacheItem; + + foreach ($tagsByKey as $key => $tags) { + $this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key])); + } + $ok = $this->pool->commit() && $ok; + + if ($invalidatedTags) { + $f = $this->invalidateTags; + $ok = $f($this->tags, $invalidatedTags) && $ok; + } + + return $ok; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + if ($this->deferred) { + $this->commit(); + } + if (!$this->pool->hasItem($key)) { + return false; + } + + $itemTags = $this->pool->getItem(static::TAGS_PREFIX.$key); + + if (!$itemTags->isHit()) { + return false; + } + + if (!$itemTags = $itemTags->get()) { + return true; + } + + foreach ($this->getTagVersions([$itemTags]) as $tag => $version) { + if ($itemTags[$tag] !== $version && 1 !== $itemTags[$tag] - $version) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + foreach ($this->getItems([$key]) as $item) { + return $item; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + if ($this->deferred) { + $this->commit(); + } + $tagKeys = []; + + foreach ($keys as $key) { + if ('' !== $key && \is_string($key)) { + $key = static::TAGS_PREFIX.$key; + $tagKeys[$key] = $key; + } + } + + try { + $items = $this->pool->getItems($tagKeys + $keys); + } catch (InvalidArgumentException $e) { + $this->pool->getItems($keys); // Should throw an exception + + throw $e; + } + + return $this->generateItems($items, $tagKeys); + } + + /** + * {@inheritdoc} + * + * @param string $prefix + * + * @return bool + */ + public function clear(/*string $prefix = ''*/) + { + $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : ''; + + if ('' !== $prefix) { + foreach ($this->deferred as $key => $item) { + if (0 === strpos($key, $prefix)) { + unset($this->deferred[$key]); + } + } + } else { + $this->deferred = []; + } + + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($prefix); + } + + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + return $this->deleteItems([$key]); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + foreach ($keys as $key) { + if ('' !== $key && \is_string($key)) { + $keys[] = static::TAGS_PREFIX.$key; + } + } + + return $this->pool->deleteItems($keys); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return $this->commit(); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + return $this->invalidateTags([]); + } + + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->commit(); + } + + private function generateItems(iterable $items, array $tagKeys) + { + $bufferedItems = $itemTags = []; + $f = $this->setCacheItemTags; + + foreach ($items as $key => $item) { + if (!$tagKeys) { + yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags); + continue; + } + if (!isset($tagKeys[$key])) { + $bufferedItems[$key] = $item; + continue; + } + + unset($tagKeys[$key]); + + if ($item->isHit()) { + $itemTags[$key] = $item->get() ?: []; + } + + if (!$tagKeys) { + $tagVersions = $this->getTagVersions($itemTags); + + foreach ($itemTags as $key => $tags) { + foreach ($tags as $tag => $version) { + if ($tagVersions[$tag] !== $version && 1 !== $version - $tagVersions[$tag]) { + unset($itemTags[$key]); + continue 2; + } + } + } + $tagVersions = $tagKeys = null; + + foreach ($bufferedItems as $key => $item) { + yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags); + } + $bufferedItems = null; + } + } + } + + private function getTagVersions(array $tagsByKey, array &$invalidatedTags = []) + { + $tagVersions = $invalidatedTags; + + foreach ($tagsByKey as $tags) { + $tagVersions += $tags; + } + + if (!$tagVersions) { + return []; + } + + if (!$fetchTagVersions = 1 !== \func_num_args()) { + foreach ($tagsByKey as $tags) { + foreach ($tags as $tag => $version) { + if ($tagVersions[$tag] > $version) { + $tagVersions[$tag] = $version; + } + } + } + } + + $now = microtime(true); + $tags = []; + foreach ($tagVersions as $tag => $version) { + $tags[$tag.static::TAGS_PREFIX] = $tag; + if ($fetchTagVersions || !isset($this->knownTagVersions[$tag])) { + $fetchTagVersions = true; + continue; + } + $version -= $this->knownTagVersions[$tag][1]; + if ((0 !== $version && 1 !== $version) || $now - $this->knownTagVersions[$tag][0] >= $this->knownTagVersionsTtl) { + // reuse previously fetched tag versions up to the ttl, unless we are storing items or a potential miss arises + $fetchTagVersions = true; + } else { + $this->knownTagVersions[$tag][1] += $version; + } + } + + if (!$fetchTagVersions) { + return $tagVersions; + } + + foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) { + $tagVersions[$tag = $tags[$tag]] = $version->get() ?: 0; + if (isset($invalidatedTags[$tag])) { + $invalidatedTags[$tag] = $version->set(++$tagVersions[$tag]); + } + $this->knownTagVersions[$tag] = [$now, $tagVersions[$tag]]; + } + + return $tagVersions; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php new file mode 100644 index 0000000..340048c --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\InvalidArgumentException; + +/** + * Interface for invalidating cached items using tags. + * + * @author Nicolas Grekas + */ +interface TagAwareAdapterInterface extends AdapterInterface +{ + /** + * Invalidates cached items using tags. + * + * @param string[] $tags An array of tags to invalidate + * + * @return bool True on success + * + * @throws InvalidArgumentException When $tags is not valid + */ + public function invalidateTags(array $tags); +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TraceableAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TraceableAdapter.php new file mode 100644 index 0000000..7e9dac8 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TraceableAdapter.php @@ -0,0 +1,302 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * An adapter that collects data about all cache calls. + * + * @author Aaron Scherer + * @author Tobias Nyholm + * @author Nicolas Grekas + */ +class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + protected $pool; + private $calls = []; + + public function __construct(AdapterInterface $pool) + { + $this->pool = $pool; + } + + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + if (!$this->pool instanceof CacheInterface) { + throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', \get_class($this->pool), CacheInterface::class)); + } + + $isHit = true; + $callback = function (CacheItem $item, bool &$save) use ($callback, &$isHit) { + $isHit = $item->isHit(); + + return $callback($item, $save); + }; + + $event = $this->start(__FUNCTION__); + try { + $value = $this->pool->get($key, $callback, $beta, $metadata); + $event->result[$key] = \is_object($value) ? \get_class($value) : \gettype($value); + } finally { + $event->end = microtime(true); + } + if ($isHit) { + ++$event->hits; + } else { + ++$event->misses; + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $event = $this->start(__FUNCTION__); + try { + $item = $this->pool->getItem($key); + } finally { + $event->end = microtime(true); + } + if ($event->result[$key] = $item->isHit()) { + ++$event->hits; + } else { + ++$event->misses; + } + + return $item; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->hasItem($key); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->deleteItem($key); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$item->getKey()] = $this->pool->save($item); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$item->getKey()] = $this->pool->saveDeferred($item); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + $event = $this->start(__FUNCTION__); + try { + $result = $this->pool->getItems($keys); + } finally { + $event->end = microtime(true); + } + $f = function () use ($result, $event) { + $event->result = []; + foreach ($result as $key => $item) { + if ($event->result[$key] = $item->isHit()) { + ++$event->hits; + } else { + ++$event->misses; + } + yield $key => $item; + } + }; + + return $f(); + } + + /** + * {@inheritdoc} + * + * @param string $prefix + * + * @return bool + */ + public function clear(/*string $prefix = ''*/) + { + $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : ''; + $event = $this->start(__FUNCTION__); + try { + if ($this->pool instanceof AdapterInterface) { + return $event->result = $this->pool->clear($prefix); + } + + return $event->result = $this->pool->clear(); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + $event = $this->start(__FUNCTION__); + $event->result['keys'] = $keys; + try { + return $event->result['result'] = $this->pool->deleteItems($keys); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->commit(); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + */ + public function prune() + { + if (!$this->pool instanceof PruneableInterface) { + return false; + } + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->prune(); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + */ + public function reset() + { + if (!$this->pool instanceof ResetInterface) { + return; + } + $event = $this->start(__FUNCTION__); + try { + $this->pool->reset(); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + */ + public function delete(string $key): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->deleteItem($key); + } finally { + $event->end = microtime(true); + } + } + + public function getCalls() + { + return $this->calls; + } + + public function clearCalls() + { + $this->calls = []; + } + + protected function start($name) + { + $this->calls[] = $event = new TraceableAdapterEvent(); + $event->name = $name; + $event->start = microtime(true); + + return $event; + } +} + +class TraceableAdapterEvent +{ + public $name; + public $start; + public $end; + public $result; + public $hits = 0; + public $misses = 0; +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php new file mode 100644 index 0000000..69461b8 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * @author Robin Chalas + */ +class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface +{ + public function __construct(TagAwareAdapterInterface $pool) + { + parent::__construct($pool); + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->invalidateTags($tags); + } finally { + $event->end = microtime(true); + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/CHANGELOG.md b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/CHANGELOG.md new file mode 100644 index 0000000..435eaf3 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/CHANGELOG.md @@ -0,0 +1,73 @@ +CHANGELOG +========= + +4.4.0 +----- + + * added support for connecting to Redis Sentinel clusters + * added argument `$prefix` to `AdapterInterface::clear()` + * improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag + * added `TagAwareMarshaller` for optimized data storage when using `AbstractTagAwareAdapter` + * added `DeflateMarshaller` to compress serialized values + * removed support for phpredis 4 `compression` + * [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead + * Marked the `CacheDataCollector` class as `@final`. + +4.3.0 +----- + + * removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it + * deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead + * deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead + +4.2.0 +----- + + * added support for connecting to Redis clusters via DSN + * added support for configuring multiple Memcached servers via DSN + * added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available + * implemented `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache + * added sub-second expiry accuracy for backends that support it + * added support for phpredis 4 `compression` and `tcp_keepalive` options + * added automatic table creation when using Doctrine DBAL with PDO-based backends + * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool + * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead + * deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods + * added `CacheCollectorPass` (originally in `FrameworkBundle`) + * added `CachePoolClearerPass` (originally in `FrameworkBundle`) + * added `CachePoolPass` (originally in `FrameworkBundle`) + * added `CachePoolPrunerPass` (originally in `FrameworkBundle`) + +3.4.0 +----- + + * added using options from Memcached DSN + * added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning + * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, TagAwareAdapter and ChainTrait + * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and + ChainCache implement PruneableInterface and support manual stale cache pruning + +3.3.0 +----- + + * added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any + * added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters + * added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16 + * added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16) + * added TraceableAdapter (PSR-6) and TraceableCache (PSR-16) + +3.2.0 +----- + + * added TagAwareAdapter for tags-based invalidation + * added PdoAdapter with PDO and Doctrine DBAL support + * added PhpArrayAdapter and PhpFilesAdapter for OPcache-backed shared memory storage (PHP 7+ only) + * added NullAdapter + +3.1.0 +----- + + * added the component with strict PSR-6 implementations + * added ApcuAdapter, ArrayAdapter, FilesystemAdapter and RedisAdapter + * added AbstractAdapter, ChainAdapter and ProxyAdapter + * added DoctrineAdapter and DoctrineProvider for bidirectional interoperability with Doctrine Cache diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/CacheItem.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/CacheItem.php new file mode 100644 index 0000000..00661e0 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/CacheItem.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Exception\LogicException; +use Symfony\Contracts\Cache\ItemInterface; + +/** + * @author Nicolas Grekas + */ +final class CacheItem implements ItemInterface +{ + private const METADATA_EXPIRY_OFFSET = 1527506807; + + protected $key; + protected $value; + protected $isHit = false; + protected $expiry; + protected $defaultLifetime; + protected $metadata = []; + protected $newMetadata = []; + protected $innerItem; + protected $poolHash; + protected $isTaggable = false; + + /** + * {@inheritdoc} + */ + public function getKey(): string + { + return $this->key; + } + + /** + * {@inheritdoc} + */ + public function get() + { + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function isHit(): bool + { + return $this->isHit; + } + + /** + * {@inheritdoc} + * + * @return $this + */ + public function set($value): self + { + $this->value = $value; + + return $this; + } + + /** + * {@inheritdoc} + * + * @return $this + */ + public function expiresAt($expiration): self + { + if (null === $expiration) { + $this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null; + } elseif ($expiration instanceof \DateTimeInterface) { + $this->expiry = (float) $expiration->format('U.u'); + } else { + throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', \is_object($expiration) ? \get_class($expiration) : \gettype($expiration))); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return $this + */ + public function expiresAfter($time): self + { + if (null === $time) { + $this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null; + } elseif ($time instanceof \DateInterval) { + $this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); + } elseif (\is_int($time)) { + $this->expiry = $time + microtime(true); + } else { + throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($time) ? \get_class($time) : \gettype($time))); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function tag($tags): ItemInterface + { + if (!$this->isTaggable) { + throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key)); + } + if (!is_iterable($tags)) { + $tags = [$tags]; + } + foreach ($tags as $tag) { + if (!\is_string($tag)) { + throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag))); + } + if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) { + continue; + } + if ('' === $tag) { + throw new InvalidArgumentException('Cache tag length must be greater than zero.'); + } + if (false !== strpbrk($tag, self::RESERVED_CHARACTERS)) { + throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters "%s".', $tag, self::RESERVED_CHARACTERS)); + } + $this->newMetadata[self::METADATA_TAGS][$tag] = $tag; + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * Returns the list of tags bound to the value coming from the pool storage if any. + * + * @deprecated since Symfony 4.2, use the "getMetadata()" method instead. + */ + public function getPreviousTags(): array + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "getMetadata()" method instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->metadata[self::METADATA_TAGS] ?? []; + } + + /** + * Validates a cache key according to PSR-6. + * + * @param string $key The key to validate + * + * @throws InvalidArgumentException When $key is not valid + */ + public static function validateKey($key): string + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + if ('' === $key) { + throw new InvalidArgumentException('Cache key length must be greater than zero.'); + } + if (false !== strpbrk($key, self::RESERVED_CHARACTERS)) { + throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS)); + } + + return $key; + } + + /** + * Internal logging helper. + * + * @internal + */ + public static function log(?LoggerInterface $logger, string $message, array $context = []) + { + if ($logger) { + $logger->warning($message, $context); + } else { + $replace = []; + foreach ($context as $k => $v) { + if (is_scalar($v)) { + $replace['{'.$k.'}'] = $v; + } + } + @trigger_error(strtr($message, $replace), E_USER_WARNING); + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DataCollector/CacheDataCollector.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DataCollector/CacheDataCollector.php new file mode 100644 index 0000000..ed86fca --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DataCollector/CacheDataCollector.php @@ -0,0 +1,194 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DataCollector; + +use Symfony\Component\Cache\Adapter\TraceableAdapter; +use Symfony\Component\Cache\Adapter\TraceableAdapterEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; + +/** + * @author Aaron Scherer + * @author Tobias Nyholm + * + * @final since Symfony 4.4 + */ +class CacheDataCollector extends DataCollector implements LateDataCollectorInterface +{ + /** + * @var TraceableAdapter[] + */ + private $instances = []; + + /** + * @param string $name + */ + public function addInstance($name, TraceableAdapter $instance) + { + $this->instances[$name] = $instance; + } + + /** + * {@inheritdoc} + * + * @param \Throwable|null $exception + */ + public function collect(Request $request, Response $response/*, \Throwable $exception = null*/) + { + $empty = ['calls' => [], 'config' => [], 'options' => [], 'statistics' => []]; + $this->data = ['instances' => $empty, 'total' => $empty]; + foreach ($this->instances as $name => $instance) { + $this->data['instances']['calls'][$name] = $instance->getCalls(); + } + + $this->data['instances']['statistics'] = $this->calculateStatistics(); + $this->data['total']['statistics'] = $this->calculateTotalStatistics(); + } + + public function reset() + { + $this->data = []; + foreach ($this->instances as $instance) { + $instance->clearCalls(); + } + } + + public function lateCollect() + { + $this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'cache'; + } + + /** + * Method returns amount of logged Cache reads: "get" calls. + * + * @return array + */ + public function getStatistics() + { + return $this->data['instances']['statistics']; + } + + /** + * Method returns the statistic totals. + * + * @return array + */ + public function getTotals() + { + return $this->data['total']['statistics']; + } + + /** + * Method returns all logged Cache call objects. + * + * @return mixed + */ + public function getCalls() + { + return $this->data['instances']['calls']; + } + + private function calculateStatistics(): array + { + $statistics = []; + foreach ($this->data['instances']['calls'] as $name => $calls) { + $statistics[$name] = [ + 'calls' => 0, + 'time' => 0, + 'reads' => 0, + 'writes' => 0, + 'deletes' => 0, + 'hits' => 0, + 'misses' => 0, + ]; + /** @var TraceableAdapterEvent $call */ + foreach ($calls as $call) { + ++$statistics[$name]['calls']; + $statistics[$name]['time'] += $call->end - $call->start; + if ('get' === $call->name) { + ++$statistics[$name]['reads']; + if ($call->hits) { + ++$statistics[$name]['hits']; + } else { + ++$statistics[$name]['misses']; + ++$statistics[$name]['writes']; + } + } elseif ('getItem' === $call->name) { + ++$statistics[$name]['reads']; + if ($call->hits) { + ++$statistics[$name]['hits']; + } else { + ++$statistics[$name]['misses']; + } + } elseif ('getItems' === $call->name) { + $statistics[$name]['reads'] += $call->hits + $call->misses; + $statistics[$name]['hits'] += $call->hits; + $statistics[$name]['misses'] += $call->misses; + } elseif ('hasItem' === $call->name) { + ++$statistics[$name]['reads']; + if (false === $call->result) { + ++$statistics[$name]['misses']; + } else { + ++$statistics[$name]['hits']; + } + } elseif ('save' === $call->name) { + ++$statistics[$name]['writes']; + } elseif ('deleteItem' === $call->name) { + ++$statistics[$name]['deletes']; + } + } + if ($statistics[$name]['reads']) { + $statistics[$name]['hit_read_ratio'] = round(100 * $statistics[$name]['hits'] / $statistics[$name]['reads'], 2); + } else { + $statistics[$name]['hit_read_ratio'] = null; + } + } + + return $statistics; + } + + private function calculateTotalStatistics(): array + { + $statistics = $this->getStatistics(); + $totals = [ + 'calls' => 0, + 'time' => 0, + 'reads' => 0, + 'writes' => 0, + 'deletes' => 0, + 'hits' => 0, + 'misses' => 0, + ]; + foreach ($statistics as $name => $values) { + foreach ($totals as $key => $value) { + $totals[$key] += $statistics[$name][$key]; + } + } + if ($totals['reads']) { + $totals['hit_read_ratio'] = round(100 * $totals['hits'] / $totals['reads'], 2); + } else { + $totals['hit_read_ratio'] = null; + } + + return $totals; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php new file mode 100644 index 0000000..6193d34 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; +use Symfony\Component\Cache\Adapter\TraceableAdapter; +use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Inject a data collector to all the cache services to be able to get detailed statistics. + * + * @author Tobias Nyholm + */ +class CacheCollectorPass implements CompilerPassInterface +{ + private $dataCollectorCacheId; + private $cachePoolTag; + private $cachePoolRecorderInnerSuffix; + + public function __construct(string $dataCollectorCacheId = 'data_collector.cache', string $cachePoolTag = 'cache.pool', string $cachePoolRecorderInnerSuffix = '.recorder_inner') + { + $this->dataCollectorCacheId = $dataCollectorCacheId; + $this->cachePoolTag = $cachePoolTag; + $this->cachePoolRecorderInnerSuffix = $cachePoolRecorderInnerSuffix; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->dataCollectorCacheId)) { + return; + } + + $collectorDefinition = $container->getDefinition($this->dataCollectorCacheId); + foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $attributes) { + $definition = $container->getDefinition($id); + if ($definition->isAbstract()) { + continue; + } + + $recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class); + $recorder->setTags($definition->getTags()); + $recorder->setPublic($definition->isPublic()); + $recorder->setArguments([new Reference($innerId = $id.$this->cachePoolRecorderInnerSuffix)]); + + $definition->setTags([]); + $definition->setPublic(false); + + $container->setDefinition($innerId, $definition); + $container->setDefinition($id, $recorder); + + // Tell the collector to add the new instance + $collectorDefinition->addMethodCall('addInstance', [$id, new Reference($id)]); + $collectorDefinition->setPublic(false); + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php new file mode 100644 index 0000000..3ca89a3 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class CachePoolClearerPass implements CompilerPassInterface +{ + private $cachePoolClearerTag; + + public function __construct(string $cachePoolClearerTag = 'cache.pool.clearer') + { + $this->cachePoolClearerTag = $cachePoolClearerTag; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $container->getParameterBag()->remove('cache.prefix.seed'); + + foreach ($container->findTaggedServiceIds($this->cachePoolClearerTag) as $id => $attr) { + $clearer = $container->getDefinition($id); + $pools = []; + foreach ($clearer->getArgument(0) as $name => $ref) { + if ($container->hasDefinition($ref)) { + $pools[$name] = new Reference($ref); + } + } + $clearer->replaceArgument(0, $pools); + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CachePoolPass.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CachePoolPass.php new file mode 100644 index 0000000..f52d027 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CachePoolPass.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class CachePoolPass implements CompilerPassInterface +{ + private $cachePoolTag; + private $kernelResetTag; + private $cacheClearerId; + private $cachePoolClearerTag; + private $cacheSystemClearerId; + private $cacheSystemClearerTag; + + public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer') + { + $this->cachePoolTag = $cachePoolTag; + $this->kernelResetTag = $kernelResetTag; + $this->cacheClearerId = $cacheClearerId; + $this->cachePoolClearerTag = $cachePoolClearerTag; + $this->cacheSystemClearerId = $cacheSystemClearerId; + $this->cacheSystemClearerTag = $cacheSystemClearerTag; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if ($container->hasParameter('cache.prefix.seed')) { + $seed = '.'.$container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed')); + } else { + $seed = '_'.$container->getParameter('kernel.project_dir'); + } + $seed .= '.'.$container->getParameter('kernel.container_class'); + + $allPools = []; + $clearers = []; + $attributes = [ + 'provider', + 'name', + 'namespace', + 'default_lifetime', + 'reset', + ]; + foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) { + $adapter = $pool = $container->getDefinition($id); + if ($pool->isAbstract()) { + continue; + } + $class = $adapter->getClass(); + while ($adapter instanceof ChildDefinition) { + $adapter = $container->findDefinition($adapter->getParent()); + $class = $class ?: $adapter->getClass(); + if ($t = $adapter->getTag($this->cachePoolTag)) { + $tags[0] += $t[0]; + } + } + $name = $tags[0]['name'] ?? $id; + if (!isset($tags[0]['namespace'])) { + $namespaceSeed = $seed; + if (null !== $class) { + $namespaceSeed .= '.'.$class; + } + + $tags[0]['namespace'] = $this->getNamespace($namespaceSeed, $name); + } + if (isset($tags[0]['clearer'])) { + $clearer = $tags[0]['clearer']; + while ($container->hasAlias($clearer)) { + $clearer = (string) $container->getAlias($clearer); + } + } else { + $clearer = null; + } + unset($tags[0]['clearer'], $tags[0]['name']); + + if (isset($tags[0]['provider'])) { + $tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider'])); + } + + if (ChainAdapter::class === $class) { + $adapters = []; + foreach ($adapter->getArgument(0) as $provider => $adapter) { + if ($adapter instanceof ChildDefinition) { + $chainedPool = $adapter; + } else { + $chainedPool = $adapter = new ChildDefinition($adapter); + } + + $chainedTags = [\is_int($provider) ? [] : ['provider' => $provider]]; + $chainedClass = ''; + + while ($adapter instanceof ChildDefinition) { + $adapter = $container->findDefinition($adapter->getParent()); + $chainedClass = $chainedClass ?: $adapter->getClass(); + if ($t = $adapter->getTag($this->cachePoolTag)) { + $chainedTags[0] += $t[0]; + } + } + + if (ChainAdapter::class === $chainedClass) { + throw new InvalidArgumentException(sprintf('Invalid service "%s": chain of adapters cannot reference another chain, found "%s".', $id, $chainedPool->getParent())); + } + + $i = 0; + + if (isset($chainedTags[0]['provider'])) { + $chainedPool->replaceArgument($i++, new Reference(static::getServiceProvider($container, $chainedTags[0]['provider']))); + } + + if (isset($tags[0]['namespace']) && ArrayAdapter::class !== $adapter->getClass()) { + $chainedPool->replaceArgument($i++, $tags[0]['namespace']); + } + + if (isset($tags[0]['default_lifetime'])) { + $chainedPool->replaceArgument($i++, $tags[0]['default_lifetime']); + } + + $adapters[] = $chainedPool; + } + + $pool->replaceArgument(0, $adapters); + unset($tags[0]['provider'], $tags[0]['namespace']); + $i = 1; + } else { + $i = 0; + } + + foreach ($attributes as $attr) { + if (!isset($tags[0][$attr])) { + // no-op + } elseif ('reset' === $attr) { + if ($tags[0][$attr]) { + $pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]); + } + } elseif ('namespace' !== $attr || ArrayAdapter::class !== $class) { + $pool->replaceArgument($i++, $tags[0][$attr]); + } + unset($tags[0][$attr]); + } + if (!empty($tags[0])) { + throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime" and "reset", found "%s".', $this->cachePoolTag, $id, implode('", "', array_keys($tags[0])))); + } + + if (null !== $clearer) { + $clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + } + + $allPools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + } + + $notAliasedCacheClearerId = $this->cacheClearerId; + while ($container->hasAlias($this->cacheClearerId)) { + $this->cacheClearerId = (string) $container->getAlias($this->cacheClearerId); + } + if ($container->hasDefinition($this->cacheClearerId)) { + $clearers[$notAliasedCacheClearerId] = $allPools; + } + + foreach ($clearers as $id => $pools) { + $clearer = $container->getDefinition($id); + if ($clearer instanceof ChildDefinition) { + $clearer->replaceArgument(0, $pools); + } else { + $clearer->setArgument(0, $pools); + } + $clearer->addTag($this->cachePoolClearerTag); + + if ($this->cacheSystemClearerId === $id) { + $clearer->addTag($this->cacheSystemClearerTag); + } + } + + if ($container->hasDefinition('console.command.cache_pool_list')) { + $container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, array_keys($allPools)); + } + } + + private function getNamespace(string $seed, string $id) + { + return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$seed, true))), 0, 10); + } + + /** + * @internal + */ + public static function getServiceProvider(ContainerBuilder $container, $name) + { + $container->resolveEnvPlaceholders($name, null, $usedEnvs); + + if ($usedEnvs || preg_match('#^[a-z]++:#', $name)) { + $dsn = $name; + + if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) { + $definition = new Definition(AbstractAdapter::class); + $definition->setPublic(false); + $definition->setFactory([AbstractAdapter::class, 'createConnection']); + $definition->setArguments([$dsn, ['lazy' => true]]); + $container->setDefinition($name, $definition); + } + } + + return $name; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php new file mode 100644 index 0000000..e569962 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Rob Frawley 2nd + */ +class CachePoolPrunerPass implements CompilerPassInterface +{ + private $cacheCommandServiceId; + private $cachePoolTag; + + public function __construct(string $cacheCommandServiceId = 'console.command.cache_pool_prune', string $cachePoolTag = 'cache.pool') + { + $this->cacheCommandServiceId = $cacheCommandServiceId; + $this->cachePoolTag = $cachePoolTag; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->cacheCommandServiceId)) { + return; + } + + $services = []; + + foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) { + $class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass()); + + if (!$reflection = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + if ($reflection->implementsInterface(PruneableInterface::class)) { + $services[$id] = new Reference($id); + } + } + + $container->getDefinition($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services)); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DoctrineProvider.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DoctrineProvider.php new file mode 100644 index 0000000..d7e0bca --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/DoctrineProvider.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Doctrine\Common\Cache\CacheProvider; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Nicolas Grekas + */ +class DoctrineProvider extends CacheProvider implements PruneableInterface, ResettableInterface +{ + private $pool; + + public function __construct(CacheItemPoolInterface $pool) + { + $this->pool = $pool; + } + + /** + * {@inheritdoc} + */ + public function prune() + { + return $this->pool instanceof PruneableInterface && $this->pool->prune(); + } + + /** + * {@inheritdoc} + */ + public function reset() + { + if ($this->pool instanceof ResetInterface) { + $this->pool->reset(); + } + $this->setNamespace($this->getNamespace()); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $item = $this->pool->getItem(rawurlencode($id)); + + return $item->isHit() ? $item->get() : false; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + protected function doContains($id) + { + return $this->pool->hasItem(rawurlencode($id)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $item = $this->pool->getItem(rawurlencode($id)); + + if (0 < $lifeTime) { + $item->expiresAfter($lifeTime); + } + + return $this->pool->save($item->set($data)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + protected function doDelete($id) + { + return $this->pool->deleteItem(rawurlencode($id)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + protected function doFlush() + { + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + * + * @return array|null + */ + protected function doGetStats() + { + return null; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Exception/CacheException.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Exception/CacheException.php new file mode 100644 index 0000000..d2e975b --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Exception/CacheException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\CacheException as Psr6CacheInterface; +use Psr\SimpleCache\CacheException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class CacheException extends \Exception implements Psr6CacheInterface + { + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Exception/InvalidArgumentException.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..7f9584a --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Exception/InvalidArgumentException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\InvalidArgumentException as Psr6CacheInterface; +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface + { + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Exception/LogicException.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Exception/LogicException.php new file mode 100644 index 0000000..9ffa7ed --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Exception/LogicException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\CacheException as Psr6CacheInterface; +use Psr\SimpleCache\CacheException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class LogicException extends \LogicException implements Psr6CacheInterface + { + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/LICENSE b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/LICENSE new file mode 100644 index 0000000..a7ec708 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-2020 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/LockRegistry.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/LockRegistry.php new file mode 100644 index 0000000..80601be --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/LockRegistry.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Log\LoggerInterface; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\ItemInterface; + +/** + * LockRegistry is used internally by existing adapters to protect against cache stampede. + * + * It does so by wrapping the computation of items in a pool of locks. + * Foreach each apps, there can be at most 20 concurrent processes that + * compute items at the same time and only one per cache-key. + * + * @author Nicolas Grekas + */ +final class LockRegistry +{ + private static $openedFiles = []; + private static $lockedFiles = []; + + /** + * The number of items in this list controls the max number of concurrent processes. + */ + private static $files = [ + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractTagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AdapterInterface.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ApcuAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ArrayAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ChainAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemTagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'MemcachedAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'NullAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PdoAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpArrayAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpFilesAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ProxyAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'Psr16Adapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisTagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'SimpleCacheAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapterInterface.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableTagAwareAdapter.php', + ]; + + /** + * Defines a set of existing files that will be used as keys to acquire locks. + * + * @return array The previously defined set of files + */ + public static function setFiles(array $files): array + { + $previousFiles = self::$files; + self::$files = $files; + + foreach (self::$openedFiles as $file) { + if ($file) { + flock($file, LOCK_UN); + fclose($file); + } + } + self::$openedFiles = self::$lockedFiles = []; + + return $previousFiles; + } + + public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null) + { + $key = self::$files ? crc32($item->getKey()) % \count(self::$files) : -1; + + if ($key < 0 || (self::$lockedFiles[$key] ?? false) || !$lock = self::open($key)) { + return $callback($item, $save); + } + + while (true) { + try { + // race to get the lock in non-blocking mode + $locked = flock($lock, LOCK_EX | LOCK_NB, $wouldBlock); + + if ($locked || !$wouldBlock) { + $logger && $logger->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]); + self::$lockedFiles[$key] = true; + + $value = $callback($item, $save); + + if ($save) { + if ($setMetadata) { + $setMetadata($item); + } + + $pool->save($item->set($value)); + $save = false; + } + + return $value; + } + // if we failed the race, retry locking in blocking mode to wait for the winner + $logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]); + flock($lock, LOCK_SH); + } finally { + flock($lock, LOCK_UN); + unset(self::$lockedFiles[$key]); + } + static $signalingException, $signalingCallback; + $signalingException = $signalingException ?? unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}"); + $signalingCallback = $signalingCallback ?? function () use ($signalingException) { throw $signalingException; }; + + try { + $value = $pool->get($item->getKey(), $signalingCallback, 0); + $logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]); + $save = false; + + return $value; + } catch (\Exception $e) { + if ($signalingException !== $e) { + throw $e; + } + $logger && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]); + } + } + + return null; + } + + private static function open(int $key) + { + if (null !== $h = self::$openedFiles[$key] ?? null) { + return $h; + } + set_error_handler(function () {}); + try { + $h = fopen(self::$files[$key], 'r+'); + } finally { + restore_error_handler(); + } + + return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r'); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/DefaultMarshaller.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/DefaultMarshaller.php new file mode 100644 index 0000000..f4cad03 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/DefaultMarshaller.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +use Symfony\Component\Cache\Exception\CacheException; + +/** + * Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise. + * + * @author Nicolas Grekas + */ +class DefaultMarshaller implements MarshallerInterface +{ + private $useIgbinarySerialize = true; + + public function __construct(bool $useIgbinarySerialize = null) + { + if (null === $useIgbinarySerialize) { + $useIgbinarySerialize = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.0', phpversion('igbinary'), '<=')); + } elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || (\PHP_VERSION_ID >= 70400 && version_compare('3.1.0', phpversion('igbinary'), '>')))) { + throw new CacheException(\extension_loaded('igbinary') && \PHP_VERSION_ID >= 70400 ? 'Please upgrade the "igbinary" PHP extension to v3.1 or higher.' : 'The "igbinary" PHP extension is not loaded.'); + } + $this->useIgbinarySerialize = $useIgbinarySerialize; + } + + /** + * {@inheritdoc} + */ + public function marshall(array $values, ?array &$failed): array + { + $serialized = $failed = []; + + foreach ($values as $id => $value) { + try { + if ($this->useIgbinarySerialize) { + $serialized[$id] = igbinary_serialize($value); + } else { + $serialized[$id] = serialize($value); + } + } catch (\Exception $e) { + $failed[] = $id; + } + } + + return $serialized; + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value) + { + if ('b:0;' === $value) { + return false; + } + if ('N;' === $value) { + return null; + } + static $igbinaryNull; + if ($value === ($igbinaryNull ?? $igbinaryNull = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.0', phpversion('igbinary'), '<=')) ? igbinary_serialize(null) : false)) { + return null; + } + $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); + try { + if (':' === ($value[1] ?? ':')) { + if (false !== $value = unserialize($value)) { + return $value; + } + } elseif (false === $igbinaryNull) { + throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?'); + } elseif (null !== $value = igbinary_unserialize($value)) { + return $value; + } + + throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize values.'); + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine()); + } finally { + ini_set('unserialize_callback_func', $unserializeCallbackHandler); + } + } + + /** + * @internal + */ + public static function handleUnserializeCallback($class) + { + throw new \DomainException('Class not found: '.$class); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/DeflateMarshaller.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/DeflateMarshaller.php new file mode 100644 index 0000000..5544806 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/DeflateMarshaller.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +use Symfony\Component\Cache\Exception\CacheException; + +/** + * Compresses values using gzdeflate(). + * + * @author Nicolas Grekas + */ +class DeflateMarshaller implements MarshallerInterface +{ + private $marshaller; + + public function __construct(MarshallerInterface $marshaller) + { + if (!\function_exists('gzdeflate')) { + throw new CacheException('The "zlib" PHP extension is not loaded.'); + } + + $this->marshaller = $marshaller; + } + + /** + * {@inheritdoc} + */ + public function marshall(array $values, ?array &$failed): array + { + return array_map('gzdeflate', $this->marshaller->marshall($values, $failed)); + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value) + { + if (false !== $inflatedValue = @gzinflate($value)) { + $value = $inflatedValue; + } + + return $this->marshaller->unmarshall($value); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/MarshallerInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/MarshallerInterface.php new file mode 100644 index 0000000..cdd6c40 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/MarshallerInterface.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +/** + * Serializes/unserializes PHP values. + * + * Implementations of this interface MUST deal with errors carefully. They MUST + * also deal with forward and backward compatibility at the storage format level. + * + * @author Nicolas Grekas + */ +interface MarshallerInterface +{ + /** + * Serializes a list of values. + * + * When serialization fails for a specific value, no exception should be + * thrown. Instead, its key should be listed in $failed. + */ + public function marshall(array $values, ?array &$failed): array; + + /** + * Unserializes a single value and throws an exception if anything goes wrong. + * + * @return mixed + * + * @throws \Exception Whenever unserialization fails + */ + public function unmarshall(string $value); +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php new file mode 100644 index 0000000..5d1e303 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +/** + * A marshaller optimized for data structures generated by AbstractTagAwareAdapter. + * + * @author Nicolas Grekas + */ +class TagAwareMarshaller implements MarshallerInterface +{ + private $marshaller; + + public function __construct(MarshallerInterface $marshaller = null) + { + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + /** + * {@inheritdoc} + */ + public function marshall(array $values, ?array &$failed): array + { + $failed = $notSerialized = $serialized = []; + + foreach ($values as $id => $value) { + if (\is_array($value) && \is_array($value['tags'] ?? null) && \array_key_exists('value', $value) && \count($value) === 2 + (\is_string($value['meta'] ?? null) && 8 === \strlen($value['meta']))) { + // if the value is an array with keys "tags", "value" and "meta", use a compact serialization format + // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F allow detecting this format quickly in unmarshall() + + $v = $this->marshaller->marshall($value, $f); + + if ($f) { + $f = []; + $failed[] = $id; + } else { + if ([] === $value['tags']) { + $v['tags'] = ''; + } + + $serialized[$id] = "\x9D".($value['meta'] ?? "\0\0\0\0\0\0\0\0").pack('N', \strlen($v['tags'])).$v['tags'].$v['value']; + $serialized[$id][9] = "\x5F"; + } + } else { + // other arbitratry values are serialized using the decorated marshaller below + $notSerialized[$id] = $value; + } + } + + if ($notSerialized) { + $serialized += $this->marshaller->marshall($notSerialized, $f); + $failed = array_merge($failed, $f); + } + + return $serialized; + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value) + { + // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (13 >= \strlen($value) || "\x9D" !== $value[0] || "\0" !== $value[5] || "\x5F" !== $value[9]) { + return $this->marshaller->unmarshall($value); + } + + // data consists of value, tags and metadata which we need to unpack + $meta = substr($value, 1, 12); + $meta[8] = "\0"; + $tagLen = unpack('Nlen', $meta, 8)['len']; + $meta = substr($meta, 0, 8); + + return [ + 'value' => $this->marshaller->unmarshall(substr($value, 13 + $tagLen)), + 'tags' => $tagLen ? $this->marshaller->unmarshall(substr($value, 13, $tagLen)) : [], + 'meta' => "\0\0\0\0\0\0\0\0" === $meta ? null : $meta, + ]; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/PruneableInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/PruneableInterface.php new file mode 100644 index 0000000..4261525 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/PruneableInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +/** + * Interface extends psr-6 and psr-16 caches to allow for pruning (deletion) of all expired cache items. + */ +interface PruneableInterface +{ + /** + * @return bool + */ + public function prune(); +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Psr16Cache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Psr16Cache.php new file mode 100644 index 0000000..7358bf5 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Psr16Cache.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Cache\CacheException as Psr6CacheException; +use Psr\Cache\CacheItemPoolInterface; +use Psr\SimpleCache\CacheException as SimpleCacheException; +use Psr\SimpleCache\CacheInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Traits\ProxyTrait; + +/** + * Turns a PSR-6 cache into a PSR-16 one. + * + * @author Nicolas Grekas + */ +class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface +{ + use ProxyTrait; + + private const METADATA_EXPIRY_OFFSET = 1527506807; + + private $createCacheItem; + private $cacheItemPrototype; + + public function __construct(CacheItemPoolInterface $pool) + { + $this->pool = $pool; + + if (!$pool instanceof AdapterInterface) { + return; + } + $cacheItemPrototype = &$this->cacheItemPrototype; + $createCacheItem = \Closure::bind( + static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) { + $item = clone $cacheItemPrototype; + $item->key = $allowInt && \is_int($key) ? (string) $key : CacheItem::validateKey($key); + $item->value = $value; + $item->isHit = false; + + return $item; + }, + null, + CacheItem::class + ); + $this->createCacheItem = function ($key, $value, $allowInt = false) use ($createCacheItem) { + if (null === $this->cacheItemPrototype) { + $this->get($allowInt && \is_int($key) ? (string) $key : $key); + } + $this->createCacheItem = $createCacheItem; + + return $createCacheItem($key, null, $allowInt)->set($value); + }; + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + try { + $item = $this->pool->getItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + if (null === $this->cacheItemPrototype) { + $this->cacheItemPrototype = clone $item; + $this->cacheItemPrototype->set(null); + } + + return $item->isHit() ? $item->get() : $default; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function set($key, $value, $ttl = null) + { + try { + if (null !== $f = $this->createCacheItem) { + $item = $f($key, $value); + } else { + $item = $this->pool->getItem($key)->set($value); + } + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + if (null !== $ttl) { + $item->expiresAfter($ttl); + } + + return $this->pool->save($item); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function delete($key) + { + try { + return $this->pool->deleteItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear() + { + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + * + * @return iterable + */ + public function getMultiple($keys, $default = null) + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!\is_array($keys)) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys))); + } + + try { + $items = $this->pool->getItems($keys); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + $values = []; + + if (!$this->pool instanceof AdapterInterface) { + foreach ($items as $key => $item) { + $values[$key] = $item->isHit() ? $item->get() : $default; + } + + return $values; + } + + foreach ($items as $key => $item) { + if (!$item->isHit()) { + $values[$key] = $default; + continue; + } + $values[$key] = $item->get(); + + if (!$metadata = $item->getMetadata()) { + continue; + } + unset($metadata[CacheItem::METADATA_TAGS]); + + if ($metadata) { + $values[$key] = ["\x9D".pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME])."\x5F" => $values[$key]]; + } + } + + return $values; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function setMultiple($values, $ttl = null) + { + $valuesIsArray = \is_array($values); + if (!$valuesIsArray && !$values instanceof \Traversable) { + throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values))); + } + $items = []; + + try { + if (null !== $f = $this->createCacheItem) { + $valuesIsArray = false; + foreach ($values as $key => $value) { + $items[$key] = $f($key, $value, true); + } + } elseif ($valuesIsArray) { + $items = []; + foreach ($values as $key => $value) { + $items[] = (string) $key; + } + $items = $this->pool->getItems($items); + } else { + foreach ($values as $key => $value) { + if (\is_int($key)) { + $key = (string) $key; + } + $items[$key] = $this->pool->getItem($key)->set($value); + } + } + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + $ok = true; + + foreach ($items as $key => $item) { + if ($valuesIsArray) { + $item->set($values[$key]); + } + if (null !== $ttl) { + $item->expiresAfter($ttl); + } + $ok = $this->pool->saveDeferred($item) && $ok; + } + + return $this->pool->commit() && $ok; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteMultiple($keys) + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!\is_array($keys)) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys))); + } + + try { + return $this->pool->deleteItems($keys); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has($key) + { + try { + return $this->pool->hasItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/README.md b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/README.md new file mode 100644 index 0000000..c4ab752 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/README.md @@ -0,0 +1,18 @@ +Symfony PSR-6 implementation for caching +======================================== + +This component provides an extended [PSR-6](http://www.php-fig.org/psr/psr-6/) +implementation for adding cache to your applications. It is designed to have a +low overhead so that caching is fastest. It ships with a few caching adapters +for the most widespread and suited to caching backends. It also provides a +`doctrine/cache` proxy adapter to cover more advanced caching needs and a proxy +adapter for greater interoperability between PSR-6 implementations. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/cache.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/ResettableInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/ResettableInterface.php new file mode 100644 index 0000000..7b0a853 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/ResettableInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Symfony\Contracts\Service\ResetInterface; + +/** + * Resets a pool's local state. + */ +interface ResettableInterface extends ResetInterface +{ +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/AbstractCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/AbstractCache.php new file mode 100644 index 0000000..85bbeb6 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/AbstractCache.php @@ -0,0 +1,199 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Psr\Log\LoggerAwareInterface; +use Psr\SimpleCache\CacheInterface as Psr16CacheInterface; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\AbstractTrait; +use Symfony\Contracts\Cache\CacheInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', AbstractCache::class, AbstractAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use AbstractAdapter and type-hint for CacheInterface instead. + */ +abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface +{ + /** + * @internal + */ + protected const NS_SEPARATOR = ':'; + + use AbstractTrait { + deleteItems as private; + AbstractTrait::deleteItem as delete; + AbstractTrait::hasItem as has; + } + + private $defaultLifetime; + + protected function __construct(string $namespace = '', int $defaultLifetime = 0) + { + $this->defaultLifetime = max(0, $defaultLifetime); + $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':'; + if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { + throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); + } + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + $id = $this->getId($key); + + try { + foreach ($this->doFetch([$id]) as $value) { + return $value; + } + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]); + } + + return $default; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function set($key, $value, $ttl = null) + { + CacheItem::validateKey($key); + + return $this->setMultiple([$key => $value], $ttl); + } + + /** + * {@inheritdoc} + * + * @return iterable + */ + public function getMultiple($keys, $default = null) + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!\is_array($keys)) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys))); + } + $ids = []; + + foreach ($keys as $key) { + $ids[] = $this->getId($key); + } + try { + $values = $this->doFetch($ids); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]); + $values = []; + } + $ids = array_combine($ids, $keys); + + return $this->generateValues($values, $ids, $default); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function setMultiple($values, $ttl = null) + { + if (!\is_array($values) && !$values instanceof \Traversable) { + throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values))); + } + $valuesById = []; + + foreach ($values as $key => $value) { + if (\is_int($key)) { + $key = (string) $key; + } + $valuesById[$this->getId($key)] = $value; + } + if (false === $ttl = $this->normalizeTtl($ttl)) { + return $this->doDelete(array_keys($valuesById)); + } + + try { + $e = $this->doSave($valuesById, $ttl); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + return true; + } + $keys = []; + foreach (\is_array($e) ? $e : array_keys($valuesById) as $id) { + $keys[] = substr($id, \strlen($this->namespace)); + } + $message = 'Failed to save values'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]); + + return false; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteMultiple($keys) + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!\is_array($keys)) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys))); + } + + return $this->deleteItems($keys); + } + + private function normalizeTtl($ttl) + { + if (null === $ttl) { + return $this->defaultLifetime; + } + if ($ttl instanceof \DateInterval) { + $ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U'); + } + if (\is_int($ttl)) { + return 0 < $ttl ? $ttl : false; + } + + throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl))); + } + + private function generateValues(iterable $values, array &$keys, $default): iterable + { + try { + foreach ($values as $id => $value) { + if (!isset($keys[$id])) { + $id = key($keys); + } + $key = $keys[$id]; + unset($keys[$id]); + yield $key => $value; + } + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]); + } + + foreach ($keys as $key) { + yield $key => $default; + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/ApcuCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/ApcuCache.php new file mode 100644 index 0000000..f1eb946 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/ApcuCache.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Symfony\Component\Cache\Adapter\ApcuAdapter; +use Symfony\Component\Cache\Traits\ApcuTrait; +use Symfony\Contracts\Cache\CacheInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ApcuCache::class, ApcuAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use ApcuAdapter and type-hint for CacheInterface instead. + */ +class ApcuCache extends AbstractCache +{ + use ApcuTrait; + + public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null) + { + $this->init($namespace, $defaultLifetime, $version); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/ArrayCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/ArrayCache.php new file mode 100644 index 0000000..a3d4c67 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/ArrayCache.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Psr\Log\LoggerAwareInterface; +use Psr\SimpleCache\CacheInterface as Psr16CacheInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ArrayTrait; +use Symfony\Contracts\Cache\CacheInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ArrayCache::class, ArrayAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use ArrayAdapter and type-hint for CacheInterface instead. + */ +class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface +{ + use ArrayTrait { + ArrayTrait::deleteItem as delete; + ArrayTrait::hasItem as has; + } + + private $defaultLifetime; + + /** + * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise + */ + public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true) + { + $this->defaultLifetime = $defaultLifetime; + $this->storeSerialized = $storeSerialized; + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + if (!\is_string($key) || !isset($this->expiries[$key])) { + CacheItem::validateKey($key); + } + if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > microtime(true) || !$this->delete($key))) { + $this->values[$key] = null; + + return $default; + } + if (!$this->storeSerialized) { + return $this->values[$key]; + } + $value = $this->unfreeze($key, $isHit); + + return $isHit ? $value : $default; + } + + /** + * {@inheritdoc} + * + * @return iterable + */ + public function getMultiple($keys, $default = null) + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!\is_array($keys)) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys))); + } + foreach ($keys as $key) { + if (!\is_string($key) || !isset($this->expiries[$key])) { + CacheItem::validateKey($key); + } + } + + return $this->generateItems($keys, microtime(true), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; }); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteMultiple($keys) + { + if (!\is_array($keys) && !$keys instanceof \Traversable) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys))); + } + foreach ($keys as $key) { + $this->delete($key); + } + + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function set($key, $value, $ttl = null) + { + if (!\is_string($key)) { + CacheItem::validateKey($key); + } + + return $this->setMultiple([$key => $value], $ttl); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function setMultiple($values, $ttl = null) + { + if (!\is_array($values) && !$values instanceof \Traversable) { + throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values))); + } + $valuesArray = []; + + foreach ($values as $key => $value) { + if (!\is_int($key) && !(\is_string($key) && isset($this->expiries[$key]))) { + CacheItem::validateKey($key); + } + $valuesArray[$key] = $value; + } + if (false === $ttl = $this->normalizeTtl($ttl)) { + return $this->deleteMultiple(array_keys($valuesArray)); + } + $expiry = 0 < $ttl ? microtime(true) + $ttl : PHP_INT_MAX; + + foreach ($valuesArray as $key => $value) { + if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) { + return false; + } + $this->values[$key] = $value; + $this->expiries[$key] = $expiry; + } + + return true; + } + + private function normalizeTtl($ttl) + { + if (null === $ttl) { + return $this->defaultLifetime; + } + if ($ttl instanceof \DateInterval) { + $ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U'); + } + if (\is_int($ttl)) { + return 0 < $ttl ? $ttl : false; + } + + throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl))); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/ChainCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/ChainCache.php new file mode 100644 index 0000000..57a169f --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/ChainCache.php @@ -0,0 +1,271 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Psr\SimpleCache\CacheInterface as Psr16CacheInterface; +use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Service\ResetInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ChainCache::class, ChainAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * Chains several caches together. + * + * Cached items are fetched from the first cache having them in its data store. + * They are saved and deleted in all caches at once. + * + * @deprecated since Symfony 4.3, use ChainAdapter and type-hint for CacheInterface instead. + */ +class ChainCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface +{ + private $miss; + private $caches = []; + private $defaultLifetime; + private $cacheCount; + + /** + * @param Psr16CacheInterface[] $caches The ordered list of caches used to fetch cached items + * @param int $defaultLifetime The lifetime of items propagated from lower caches to upper ones + */ + public function __construct(array $caches, int $defaultLifetime = 0) + { + if (!$caches) { + throw new InvalidArgumentException('At least one cache must be specified.'); + } + + foreach ($caches as $cache) { + if (!$cache instanceof Psr16CacheInterface) { + throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($cache), Psr16CacheInterface::class)); + } + } + + $this->miss = new \stdClass(); + $this->caches = array_values($caches); + $this->cacheCount = \count($this->caches); + $this->defaultLifetime = 0 < $defaultLifetime ? $defaultLifetime : null; + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + $miss = null !== $default && \is_object($default) ? $default : $this->miss; + + foreach ($this->caches as $i => $cache) { + $value = $cache->get($key, $miss); + + if ($miss !== $value) { + while (0 <= --$i) { + $this->caches[$i]->set($key, $value, $this->defaultLifetime); + } + + return $value; + } + } + + return $default; + } + + /** + * {@inheritdoc} + * + * @return iterable + */ + public function getMultiple($keys, $default = null) + { + $miss = null !== $default && \is_object($default) ? $default : $this->miss; + + return $this->generateItems($this->caches[0]->getMultiple($keys, $miss), 0, $miss, $default); + } + + private function generateItems(iterable $values, int $cacheIndex, $miss, $default): iterable + { + $missing = []; + $nextCacheIndex = $cacheIndex + 1; + $nextCache = isset($this->caches[$nextCacheIndex]) ? $this->caches[$nextCacheIndex] : null; + + foreach ($values as $k => $value) { + if ($miss !== $value) { + yield $k => $value; + } elseif (!$nextCache) { + yield $k => $default; + } else { + $missing[] = $k; + } + } + + if ($missing) { + $cache = $this->caches[$cacheIndex]; + $values = $this->generateItems($nextCache->getMultiple($missing, $miss), $nextCacheIndex, $miss, $default); + + foreach ($values as $k => $value) { + if ($miss !== $value) { + $cache->set($k, $value, $this->defaultLifetime); + yield $k => $value; + } else { + yield $k => $default; + } + } + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has($key) + { + foreach ($this->caches as $cache) { + if ($cache->has($key)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear() + { + $cleared = true; + $i = $this->cacheCount; + + while ($i--) { + $cleared = $this->caches[$i]->clear() && $cleared; + } + + return $cleared; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function delete($key) + { + $deleted = true; + $i = $this->cacheCount; + + while ($i--) { + $deleted = $this->caches[$i]->delete($key) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteMultiple($keys) + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } + $deleted = true; + $i = $this->cacheCount; + + while ($i--) { + $deleted = $this->caches[$i]->deleteMultiple($keys) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function set($key, $value, $ttl = null) + { + $saved = true; + $i = $this->cacheCount; + + while ($i--) { + $saved = $this->caches[$i]->set($key, $value, $ttl) && $saved; + } + + return $saved; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function setMultiple($values, $ttl = null) + { + if ($values instanceof \Traversable) { + $valuesIterator = $values; + $values = function () use ($valuesIterator, &$values) { + $generatedValues = []; + + foreach ($valuesIterator as $key => $value) { + yield $key => $value; + $generatedValues[$key] = $value; + } + + $values = $generatedValues; + }; + $values = $values(); + } + $saved = true; + $i = $this->cacheCount; + + while ($i--) { + $saved = $this->caches[$i]->setMultiple($values, $ttl) && $saved; + } + + return $saved; + } + + /** + * {@inheritdoc} + */ + public function prune() + { + $pruned = true; + + foreach ($this->caches as $cache) { + if ($cache instanceof PruneableInterface) { + $pruned = $cache->prune() && $pruned; + } + } + + return $pruned; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + foreach ($this->caches as $cache) { + if ($cache instanceof ResetInterface) { + $cache->reset(); + } + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/DoctrineCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/DoctrineCache.php new file mode 100644 index 0000000..6a6d003 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/DoctrineCache.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Doctrine\Common\Cache\CacheProvider; +use Symfony\Component\Cache\Adapter\DoctrineAdapter; +use Symfony\Component\Cache\Traits\DoctrineTrait; +use Symfony\Contracts\Cache\CacheInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', DoctrineCache::class, DoctrineAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use DoctrineAdapter and type-hint for CacheInterface instead. + */ +class DoctrineCache extends AbstractCache +{ + use DoctrineTrait; + + public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0) + { + parent::__construct('', $defaultLifetime); + $this->provider = $provider; + $provider->setNamespace($namespace); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/FilesystemCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/FilesystemCache.php new file mode 100644 index 0000000..8891abd --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/FilesystemCache.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Symfony\Component\Cache\Adapter\FilesystemAdapter; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\FilesystemTrait; +use Symfony\Contracts\Cache\CacheInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', FilesystemCache::class, FilesystemAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use FilesystemAdapter and type-hint for CacheInterface instead. + */ +class FilesystemCache extends AbstractCache implements PruneableInterface +{ + use FilesystemTrait; + + public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null) + { + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/MemcachedCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/MemcachedCache.php new file mode 100644 index 0000000..e193411 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/MemcachedCache.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Symfony\Component\Cache\Adapter\MemcachedAdapter; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Traits\MemcachedTrait; +use Symfony\Contracts\Cache\CacheInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', MemcachedCache::class, MemcachedAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use MemcachedAdapter and type-hint for CacheInterface instead. + */ +class MemcachedCache extends AbstractCache +{ + use MemcachedTrait; + + protected $maxIdLength = 250; + + public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + { + $this->init($client, $namespace, $defaultLifetime, $marshaller); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/NullCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/NullCache.php new file mode 100644 index 0000000..35600fc --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/NullCache.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Psr\SimpleCache\CacheInterface as Psr16CacheInterface; +use Symfony\Component\Cache\Adapter\NullAdapter; +use Symfony\Contracts\Cache\CacheInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', NullCache::class, NullAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use NullAdapter and type-hint for CacheInterface instead. + */ +class NullCache implements Psr16CacheInterface +{ + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + return $default; + } + + /** + * {@inheritdoc} + * + * @return iterable + */ + public function getMultiple($keys, $default = null) + { + foreach ($keys as $key) { + yield $key => $default; + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has($key) + { + return false; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear() + { + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function delete($key) + { + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteMultiple($keys) + { + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function set($key, $value, $ttl = null) + { + return false; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function setMultiple($values, $ttl = null) + { + return false; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/PdoCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/PdoCache.php new file mode 100644 index 0000000..07849e9 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/PdoCache.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Symfony\Component\Cache\Adapter\PdoAdapter; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\PdoTrait; +use Symfony\Contracts\Cache\CacheInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PdoCache::class, PdoAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use PdoAdapter and type-hint for CacheInterface instead. + */ +class PdoCache extends AbstractCache implements PruneableInterface +{ + use PdoTrait; + + protected $maxIdLength = 255; + + /** + * You can either pass an existing database connection as PDO instance or + * a Doctrine DBAL Connection or a DSN string that will be used to + * lazy-connect to the database when the cache is actually used. + * + * When a Doctrine DBAL Connection is passed, the cache table is created + * automatically when possible. Otherwise, use the createTable() method. + * + * List of available options: + * * db_table: The name of the table [default: cache_items] + * * db_id_col: The column where to store the cache id [default: item_id] + * * db_data_col: The column where to store the cache data [default: item_data] + * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime] + * * db_time_col: The column where to store the timestamp [default: item_time] + * * db_username: The username when lazy-connect [default: ''] + * * db_password: The password when lazy-connect [default: ''] + * * db_connection_options: An array of driver-specific connection options [default: []] + * + * @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null + * + * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string + * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + * @throws InvalidArgumentException When namespace contains invalid characters + */ + public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null) + { + $this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/PhpArrayCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/PhpArrayCache.php new file mode 100644 index 0000000..92b79e3 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/PhpArrayCache.php @@ -0,0 +1,256 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Psr\SimpleCache\CacheInterface as Psr16CacheInterface; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\PhpArrayTrait; +use Symfony\Contracts\Cache\CacheInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PhpArrayCache::class, PhpArrayAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use PhpArrayAdapter and type-hint for CacheInterface instead. + */ +class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface +{ + use PhpArrayTrait; + + /** + * @param string $file The PHP file were values are cached + * @param Psr16CacheInterface $fallbackPool A pool to fallback on when an item is not hit + */ + public function __construct(string $file, Psr16CacheInterface $fallbackPool) + { + $this->file = $file; + $this->pool = $fallbackPool; + } + + /** + * This adapter takes advantage of how PHP stores arrays in its latest versions. + * + * @param string $file The PHP file were values are cached + * @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit + * + * @return Psr16CacheInterface + */ + public static function create($file, Psr16CacheInterface $fallbackPool) + { + return new static($file, $fallbackPool); + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + if (null === $this->values) { + $this->initialize(); + } + if (!isset($this->keys[$key])) { + return $this->pool->get($key, $default); + } + $value = $this->values[$this->keys[$key]]; + + if ('N;' === $value) { + return null; + } + if ($value instanceof \Closure) { + try { + return $value(); + } catch (\Throwable $e) { + return $default; + } + } + + return $value; + } + + /** + * {@inheritdoc} + * + * @return iterable + */ + public function getMultiple($keys, $default = null) + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!\is_array($keys)) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys))); + } + foreach ($keys as $key) { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + } + if (null === $this->values) { + $this->initialize(); + } + + return $this->generateItems($keys, $default); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has($key) + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + if (null === $this->values) { + $this->initialize(); + } + + return isset($this->keys[$key]) || $this->pool->has($key); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function delete($key) + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + if (null === $this->values) { + $this->initialize(); + } + + return !isset($this->keys[$key]) && $this->pool->delete($key); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteMultiple($keys) + { + if (!\is_array($keys) && !$keys instanceof \Traversable) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys))); + } + + $deleted = true; + $fallbackKeys = []; + + foreach ($keys as $key) { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + + if (isset($this->keys[$key])) { + $deleted = false; + } else { + $fallbackKeys[] = $key; + } + } + if (null === $this->values) { + $this->initialize(); + } + + if ($fallbackKeys) { + $deleted = $this->pool->deleteMultiple($fallbackKeys) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function set($key, $value, $ttl = null) + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + if (null === $this->values) { + $this->initialize(); + } + + return !isset($this->keys[$key]) && $this->pool->set($key, $value, $ttl); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function setMultiple($values, $ttl = null) + { + if (!\is_array($values) && !$values instanceof \Traversable) { + throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values))); + } + + $saved = true; + $fallbackValues = []; + + foreach ($values as $key => $value) { + if (!\is_string($key) && !\is_int($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); + } + + if (isset($this->keys[$key])) { + $saved = false; + } else { + $fallbackValues[$key] = $value; + } + } + + if ($fallbackValues) { + $saved = $this->pool->setMultiple($fallbackValues, $ttl) && $saved; + } + + return $saved; + } + + private function generateItems(array $keys, $default): iterable + { + $fallbackKeys = []; + + foreach ($keys as $key) { + if (isset($this->keys[$key])) { + $value = $this->values[$this->keys[$key]]; + + if ('N;' === $value) { + yield $key => null; + } elseif ($value instanceof \Closure) { + try { + yield $key => $value(); + } catch (\Throwable $e) { + yield $key => $default; + } + } else { + yield $key => $value; + } + } else { + $fallbackKeys[] = $key; + } + } + + if ($fallbackKeys) { + yield from $this->pool->getMultiple($fallbackKeys, $default); + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/PhpFilesCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/PhpFilesCache.php new file mode 100644 index 0000000..060244a --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/PhpFilesCache.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Symfony\Component\Cache\Adapter\PhpFilesAdapter; +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\PhpFilesTrait; +use Symfony\Contracts\Cache\CacheInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PhpFilesCache::class, PhpFilesAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use PhpFilesAdapter and type-hint for CacheInterface instead. + */ +class PhpFilesCache extends AbstractCache implements PruneableInterface +{ + use PhpFilesTrait; + + /** + * @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire. + * Doing so is encouraged because it fits perfectly OPcache's memory model. + * + * @throws CacheException if OPcache is not enabled + */ + public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false) + { + $this->appendOnly = $appendOnly; + self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time(); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + $this->includeHandler = static function ($type, $msg, $file, $line) { + throw new \ErrorException($msg, 0, $type, $file, $line); + }; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/Psr6Cache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/Psr6Cache.php new file mode 100644 index 0000000..090f48c --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/Psr6Cache.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Symfony\Component\Cache\Psr16Cache; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" instead.', Psr6Cache::class, Psr16Cache::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use Psr16Cache instead. + */ +class Psr6Cache extends Psr16Cache +{ +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/RedisCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/RedisCache.php new file mode 100644 index 0000000..9655a75 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/RedisCache.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Symfony\Component\Cache\Adapter\RedisAdapter; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Traits\RedisTrait; +use Symfony\Contracts\Cache\CacheInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', RedisCache::class, RedisAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use RedisAdapter and type-hint for CacheInterface instead. + */ +class RedisCache extends AbstractCache +{ + use RedisTrait; + + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient + */ + public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + { + $this->init($redisClient, $namespace, $defaultLifetime, $marshaller); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/TraceableCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/TraceableCache.php new file mode 100644 index 0000000..2214f1c --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Simple/TraceableCache.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Simple; + +use Psr\SimpleCache\CacheInterface as Psr16CacheInterface; +use Symfony\Component\Cache\Adapter\TraceableAdapter; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Service\ResetInterface; + +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', TraceableCache::class, TraceableAdapter::class, CacheInterface::class), E_USER_DEPRECATED); + +/** + * @deprecated since Symfony 4.3, use TraceableAdapter and type-hint for CacheInterface instead. + */ +class TraceableCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface +{ + private $pool; + private $miss; + private $calls = []; + + public function __construct(Psr16CacheInterface $pool) + { + $this->pool = $pool; + $this->miss = new \stdClass(); + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + $miss = null !== $default && \is_object($default) ? $default : $this->miss; + $event = $this->start(__FUNCTION__); + try { + $value = $this->pool->get($key, $miss); + } finally { + $event->end = microtime(true); + } + if ($event->result[$key] = $miss !== $value) { + ++$event->hits; + } else { + ++$event->misses; + $value = $default; + } + + return $value; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has($key) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->has($key); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function delete($key) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->delete($key); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function set($key, $value, $ttl = null) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->set($key, $value, $ttl); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function setMultiple($values, $ttl = null) + { + $event = $this->start(__FUNCTION__); + $event->result['keys'] = []; + + if ($values instanceof \Traversable) { + $values = function () use ($values, $event) { + foreach ($values as $k => $v) { + $event->result['keys'][] = $k; + yield $k => $v; + } + }; + $values = $values(); + } elseif (\is_array($values)) { + $event->result['keys'] = array_keys($values); + } + + try { + return $event->result['result'] = $this->pool->setMultiple($values, $ttl); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return iterable + */ + public function getMultiple($keys, $default = null) + { + $miss = null !== $default && \is_object($default) ? $default : $this->miss; + $event = $this->start(__FUNCTION__); + try { + $result = $this->pool->getMultiple($keys, $miss); + } finally { + $event->end = microtime(true); + } + $f = function () use ($result, $event, $miss, $default) { + $event->result = []; + foreach ($result as $key => $value) { + if ($event->result[$key] = $miss !== $value) { + ++$event->hits; + } else { + ++$event->misses; + $value = $default; + } + yield $key => $value; + } + }; + + return $f(); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear() + { + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->clear(); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteMultiple($keys) + { + $event = $this->start(__FUNCTION__); + if ($keys instanceof \Traversable) { + $keys = $event->result['keys'] = iterator_to_array($keys, false); + } else { + $event->result['keys'] = $keys; + } + try { + return $event->result['result'] = $this->pool->deleteMultiple($keys); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + */ + public function prune() + { + if (!$this->pool instanceof PruneableInterface) { + return false; + } + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->prune(); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + */ + public function reset() + { + if (!$this->pool instanceof ResetInterface) { + return; + } + $event = $this->start(__FUNCTION__); + try { + $this->pool->reset(); + } finally { + $event->end = microtime(true); + } + } + + public function getCalls() + { + try { + return $this->calls; + } finally { + $this->calls = []; + } + } + + private function start(string $name): TraceableCacheEvent + { + $this->calls[] = $event = new TraceableCacheEvent(); + $event->name = $name; + $event->start = microtime(true); + + return $event; + } +} + +class TraceableCacheEvent +{ + public $name; + public $start; + public $end; + public $result; + public $hits = 0; + public $misses = 0; +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/AbstractAdapterTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/AbstractAdapterTrait.php new file mode 100644 index 0000000..8bf9354 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/AbstractAdapterTrait.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheItem; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait AbstractAdapterTrait +{ + use AbstractTrait; + + /** + * @var \Closure needs to be set by class, signature is function(string , mixed , bool ) + */ + private $createCacheItem; + + /** + * @var \Closure needs to be set by class, signature is function(array , string , array <&expiredIds>) + */ + private $mergeByLifetime; + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + if ($this->deferred) { + $this->commit(); + } + $id = $this->getId($key); + + $f = $this->createCacheItem; + $isHit = false; + $value = null; + + try { + foreach ($this->doFetch([$id]) as $value) { + $isHit = true; + } + + return $f($key, $value, $isHit); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]); + } + + return $f($key, null, false); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + if ($this->deferred) { + $this->commit(); + } + $ids = []; + + foreach ($keys as $key) { + $ids[] = $this->getId($key); + } + try { + $items = $this->doFetch($ids); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]); + $items = []; + } + $ids = array_combine($ids, $keys); + + return $this->generateItems($items, $ids); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return $this->commit(); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return true; + } + + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if ($this->deferred) { + $this->commit(); + } + } + + private function generateItems(iterable $items, array &$keys): iterable + { + $f = $this->createCacheItem; + + try { + foreach ($items as $id => $value) { + if (!isset($keys[$id])) { + $id = key($keys); + } + $key = $keys[$id]; + unset($keys[$id]); + yield $key => $f($key, $value, true); + } + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]); + } + + foreach ($keys as $key) { + yield $key => $f($key, null, false); + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/AbstractTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/AbstractTrait.php new file mode 100644 index 0000000..5876c1c --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/AbstractTrait.php @@ -0,0 +1,303 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Cache\CacheItem; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait AbstractTrait +{ + use LoggerAwareTrait; + + private $namespace; + private $namespaceVersion = ''; + private $versioningIsEnabled = false; + private $deferred = []; + private $ids = []; + + /** + * @var int|null The maximum length to enforce for identifiers or null when no limit applies + */ + protected $maxIdLength; + + /** + * Fetches several cache items. + * + * @param array $ids The cache identifiers to fetch + * + * @return array|\Traversable The corresponding values found in the cache + */ + abstract protected function doFetch(array $ids); + + /** + * Confirms if the cache contains specified cache item. + * + * @param string $id The identifier for which to check existence + * + * @return bool True if item exists in the cache, false otherwise + */ + abstract protected function doHave($id); + + /** + * Deletes all items in the pool. + * + * @param string $namespace The prefix used for all identifiers managed by this pool + * + * @return bool True if the pool was successfully cleared, false otherwise + */ + abstract protected function doClear($namespace); + + /** + * Removes multiple items from the pool. + * + * @param array $ids An array of identifiers that should be removed from the pool + * + * @return bool True if the items were successfully removed, false otherwise + */ + abstract protected function doDelete(array $ids); + + /** + * Persists several cache items immediately. + * + * @param array $values The values to cache, indexed by their cache identifier + * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning + * + * @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not + */ + abstract protected function doSave(array $values, $lifetime); + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + $id = $this->getId($key); + + if (isset($this->deferred[$key])) { + $this->commit(); + } + + try { + return $this->doHave($id); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key, 'exception' => $e]); + + return false; + } + } + + /** + * {@inheritdoc} + * + * @param string $prefix + * + * @return bool + */ + public function clear(/*string $prefix = ''*/) + { + $this->deferred = []; + if ($cleared = $this->versioningIsEnabled) { + if ('' === $namespaceVersionToClear = $this->namespaceVersion) { + foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) { + $namespaceVersionToClear = $v; + } + } + $namespaceToClear = $this->namespace.$namespaceVersionToClear; + $namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), static::NS_SEPARATOR, 5); + try { + $cleared = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0); + } catch (\Exception $e) { + $cleared = false; + } + if ($cleared = true === $cleared || [] === $cleared) { + $this->namespaceVersion = $namespaceVersion; + $this->ids = []; + } + } else { + $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : ''; + $namespaceToClear = $this->namespace.$prefix; + } + + try { + return $this->doClear($namespaceToClear) || $cleared; + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e]); + + return false; + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + return $this->deleteItems([$key]); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + $ids = []; + + foreach ($keys as $key) { + $ids[$key] = $this->getId($key); + unset($this->deferred[$key]); + } + + try { + if ($this->doDelete($ids)) { + return true; + } + } catch (\Exception $e) { + } + + $ok = true; + + // When bulk-delete failed, retry each item individually + foreach ($ids as $key => $id) { + try { + $e = null; + if ($this->doDelete([$id])) { + continue; + } + } catch (\Exception $e) { + } + $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]); + $ok = false; + } + + return $ok; + } + + /** + * Enables/disables versioning of items. + * + * When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed, + * but old keys may need garbage collection and extra round-trips to the back-end are required. + * + * Calling this method also clears the memoized namespace version and thus forces a resynchonization of it. + * + * @param bool $enable + * + * @return bool the previous state of versioning + */ + public function enableVersioning($enable = true) + { + $wasEnabled = $this->versioningIsEnabled; + $this->versioningIsEnabled = (bool) $enable; + $this->namespaceVersion = ''; + $this->ids = []; + + return $wasEnabled; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + if ($this->deferred) { + $this->commit(); + } + $this->namespaceVersion = ''; + $this->ids = []; + } + + /** + * Like the native unserialize() function but throws an exception if anything goes wrong. + * + * @param string $value + * + * @return mixed + * + * @throws \Exception + * + * @deprecated since Symfony 4.2, use DefaultMarshaller instead. + */ + protected static function unserialize($value) + { + @trigger_error(sprintf('The "%s::unserialize()" method is deprecated since Symfony 4.2, use DefaultMarshaller instead.', __CLASS__), E_USER_DEPRECATED); + + if ('b:0;' === $value) { + return false; + } + $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); + try { + if (false !== $value = unserialize($value)) { + return $value; + } + throw new \DomainException('Failed to unserialize cached value.'); + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine()); + } finally { + ini_set('unserialize_callback_func', $unserializeCallbackHandler); + } + } + + private function getId($key): string + { + if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { + $this->ids = []; + $this->namespaceVersion = '1'.static::NS_SEPARATOR; + try { + foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) { + $this->namespaceVersion = $v; + } + if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) { + $this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), static::NS_SEPARATOR, 5); + $this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0); + } + } catch (\Exception $e) { + } + } + + if (\is_string($key) && isset($this->ids[$key])) { + return $this->namespace.$this->namespaceVersion.$this->ids[$key]; + } + CacheItem::validateKey($key); + $this->ids[$key] = $key; + + if (null === $this->maxIdLength) { + return $this->namespace.$this->namespaceVersion.$key; + } + if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { + // Use MD5 to favor speed over security, which is not an issue here + $this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2)); + $id = $this->namespace.$this->namespaceVersion.$id; + } + + return $id; + } + + /** + * @internal + */ + public static function handleUnserializeCallback($class) + { + throw new \DomainException('Class not found: '.$class); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ApcuTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ApcuTrait.php new file mode 100644 index 0000000..f681c05 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ApcuTrait.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\CacheException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait ApcuTrait +{ + public static function isSupported() + { + return \function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN); + } + + private function init(string $namespace, int $defaultLifetime, ?string $version) + { + if (!static::isSupported()) { + throw new CacheException('APCu is not enabled.'); + } + if ('cli' === \PHP_SAPI) { + ini_set('apc.use_request_time', 0); + } + parent::__construct($namespace, $defaultLifetime); + + if (null !== $version) { + CacheItem::validateKey($version); + + if (!apcu_exists($version.'@'.$namespace)) { + $this->doClear($namespace); + apcu_add($version.'@'.$namespace, null); + } + } + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); + try { + $values = []; + foreach (apcu_fetch($ids, $ok) ?: [] as $k => $v) { + if (null !== $v || $ok) { + $values[$k] = $v; + } + } + + return $values; + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine()); + } finally { + ini_set('unserialize_callback_func', $unserializeCallbackHandler); + } + } + + /** + * {@inheritdoc} + */ + protected function doHave($id) + { + return apcu_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doClear($namespace) + { + return isset($namespace[0]) && class_exists('APCuIterator', false) && ('cli' !== \PHP_SAPI || filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) + ? apcu_delete(new \APCuIterator(sprintf('/^%s/', preg_quote($namespace, '/')), APC_ITER_KEY)) + : apcu_clear_cache(); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + foreach ($ids as $id) { + apcu_delete($id); + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + try { + if (false === $failures = apcu_store($values, null, $lifetime)) { + $failures = $values; + } + + return array_keys($failures); + } catch (\Throwable $e) { + if (1 === \count($values)) { + // Workaround https://github.com/krakjoe/apcu/issues/170 + apcu_delete(key($values)); + } + + throw $e; + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ArrayTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ArrayTrait.php new file mode 100644 index 0000000..21872c5 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ArrayTrait.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Cache\CacheItem; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait ArrayTrait +{ + use LoggerAwareTrait; + + private $storeSerialized; + private $values = []; + private $expiries = []; + + /** + * Returns all cached values, with cache miss as null. + * + * @return array + */ + public function getValues() + { + if (!$this->storeSerialized) { + return $this->values; + } + + $values = $this->values; + foreach ($values as $k => $v) { + if (null === $v || 'N;' === $v) { + continue; + } + if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) { + $values[$k] = serialize($v); + } + } + + return $values; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) { + return true; + } + CacheItem::validateKey($key); + + return isset($this->expiries[$key]) && !$this->deleteItem($key); + } + + /** + * {@inheritdoc} + * + * @param string $prefix + * + * @return bool + */ + public function clear(/*string $prefix = ''*/) + { + $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : ''; + + if ('' !== $prefix) { + foreach ($this->values as $key => $value) { + if (0 === strpos($key, $prefix)) { + unset($this->values[$key], $this->expiries[$key]); + } + } + } else { + $this->values = $this->expiries = []; + } + + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + if (!\is_string($key) || !isset($this->expiries[$key])) { + CacheItem::validateKey($key); + } + unset($this->values[$key], $this->expiries[$key]); + + return true; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + $this->clear(); + } + + private function generateItems(array $keys, float $now, callable $f): iterable + { + foreach ($keys as $i => $key) { + if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) { + $this->values[$key] = $value = null; + } else { + $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key]; + } + unset($keys[$i]); + + yield $key => $f($key, $value, $isHit); + } + + foreach ($keys as $key) { + yield $key => $f($key, null, false); + } + } + + private function freeze($value, $key) + { + if (null === $value) { + return 'N;'; + } + if (\is_string($value)) { + // Serialize strings if they could be confused with serialized objects or arrays + if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { + return serialize($value); + } + } elseif (!is_scalar($value)) { + try { + $serialized = serialize($value); + } catch (\Exception $e) { + $type = \is_object($value) ? \get_class($value) : \gettype($value); + $message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage()); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]); + + return null; + } + // Keep value serialized if it contains any objects or any internal references + if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) { + return $serialized; + } + } + + return $value; + } + + private function unfreeze(string $key, bool &$isHit) + { + if ('N;' === $value = $this->values[$key]) { + return null; + } + if (\is_string($value) && isset($value[2]) && ':' === $value[1]) { + try { + $value = unserialize($value); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]); + $value = false; + } + if (false === $value) { + $this->values[$key] = $value = null; + $isHit = false; + } + } + + return $value; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ContractsTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ContractsTrait.php new file mode 100644 index 0000000..06070c9 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ContractsTrait.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\LockRegistry; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\CacheTrait; +use Symfony\Contracts\Cache\ItemInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait ContractsTrait +{ + use CacheTrait { + doGet as private contractsGet; + } + + private $callbackWrapper = [LockRegistry::class, 'compute']; + private $computing = []; + + /** + * Wraps the callback passed to ->get() in a callable. + * + * @return callable the previous callback wrapper + */ + public function setCallbackWrapper(?callable $callbackWrapper): callable + { + $previousWrapper = $this->callbackWrapper; + $this->callbackWrapper = $callbackWrapper ?? function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) { + return $callback($item, $save); + }; + + return $previousWrapper; + } + + private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null) + { + if (0 > $beta = $beta ?? 1.0) { + throw new InvalidArgumentException(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)); + } + + static $setMetadata; + + $setMetadata = $setMetadata ?? \Closure::bind( + static function (CacheItem $item, float $startTime, ?array &$metadata) { + if ($item->expiry > $endTime = microtime(true)) { + $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry; + $item->newMetadata[CacheItem::METADATA_CTIME] = $metadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime)); + } else { + unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME]); + } + }, + null, + CacheItem::class + ); + + return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use ($pool, $callback, $setMetadata, &$metadata, $key) { + // don't wrap nor save recursive calls + if (isset($this->computing[$key])) { + $value = $callback($item, $save); + $save = false; + + return $value; + } + + $this->computing[$key] = $key; + $startTime = microtime(true); + + try { + $value = ($this->callbackWrapper)($callback, $item, $save, $pool, function (CacheItem $item) use ($setMetadata, $startTime, &$metadata) { + $setMetadata($item, $startTime, $metadata); + }, $this->logger ?? null); + $setMetadata($item, $startTime, $metadata); + + return $value; + } finally { + unset($this->computing[$key]); + } + }, $beta, $metadata, $this->logger ?? null); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/DoctrineTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/DoctrineTrait.php new file mode 100644 index 0000000..c87ecab --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/DoctrineTrait.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait DoctrineTrait +{ + private $provider; + + /** + * {@inheritdoc} + */ + public function reset() + { + parent::reset(); + $this->provider->setNamespace($this->provider->getNamespace()); + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + $unserializeCallbackHandler = ini_set('unserialize_callback_func', parent::class.'::handleUnserializeCallback'); + try { + return $this->provider->fetchMultiple($ids); + } catch (\Error $e) { + $trace = $e->getTrace(); + + if (isset($trace[0]['function']) && !isset($trace[0]['class'])) { + switch ($trace[0]['function']) { + case 'unserialize': + case 'apcu_fetch': + case 'apc_fetch': + throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine()); + } + } + + throw $e; + } finally { + ini_set('unserialize_callback_func', $unserializeCallbackHandler); + } + } + + /** + * {@inheritdoc} + */ + protected function doHave($id) + { + return $this->provider->contains($id); + } + + /** + * {@inheritdoc} + */ + protected function doClear($namespace) + { + $namespace = $this->provider->getNamespace(); + + return isset($namespace[0]) + ? $this->provider->deleteAll() + : $this->provider->flushAll(); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + $ok = true; + foreach ($ids as $id) { + $ok = $this->provider->delete($id) && $ok; + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + return $this->provider->saveMultiple($values, $lifetime); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/FilesystemCommonTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/FilesystemCommonTrait.php new file mode 100644 index 0000000..67e2d61 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/FilesystemCommonTrait.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait FilesystemCommonTrait +{ + private $directory; + private $tmp; + + private function init(string $namespace, ?string $directory) + { + if (!isset($directory[0])) { + $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache'; + } else { + $directory = realpath($directory) ?: $directory; + } + if (isset($namespace[0])) { + if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); + } + $directory .= \DIRECTORY_SEPARATOR.$namespace; + } else { + $directory .= \DIRECTORY_SEPARATOR.'@'; + } + if (!file_exists($directory)) { + @mkdir($directory, 0777, true); + } + $directory .= \DIRECTORY_SEPARATOR; + // On Windows the whole path is limited to 258 chars + if ('\\' === \DIRECTORY_SEPARATOR && \strlen($directory) > 234) { + throw new InvalidArgumentException(sprintf('Cache directory too long (%s).', $directory)); + } + + $this->directory = $directory; + } + + /** + * {@inheritdoc} + */ + protected function doClear($namespace) + { + $ok = true; + + foreach ($this->scanHashDir($this->directory) as $file) { + if ('' !== $namespace && 0 !== strpos($this->getFileKey($file), $namespace)) { + continue; + } + + $ok = ($this->doUnlink($file) || !file_exists($file)) && $ok; + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + $ok = true; + + foreach ($ids as $id) { + $file = $this->getFile($id); + $ok = (!file_exists($file) || $this->doUnlink($file) || !file_exists($file)) && $ok; + } + + return $ok; + } + + protected function doUnlink($file) + { + return @unlink($file); + } + + private function write(string $file, string $data, int $expiresAt = null) + { + set_error_handler(__CLASS__.'::throwError'); + try { + if (null === $this->tmp) { + $this->tmp = $this->directory.uniqid('', true); + } + file_put_contents($this->tmp, $data); + + if (null !== $expiresAt) { + touch($this->tmp, $expiresAt); + } + + return rename($this->tmp, $file); + } finally { + restore_error_handler(); + } + } + + private function getFile(string $id, bool $mkdir = false, string $directory = null) + { + // Use MD5 to favor speed over security, which is not an issue here + $hash = str_replace('/', '-', base64_encode(hash('md5', static::class.$id, true))); + $dir = ($directory ?? $this->directory).strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR); + + if ($mkdir && !file_exists($dir)) { + @mkdir($dir, 0777, true); + } + + return $dir.substr($hash, 2, 20); + } + + private function getFileKey(string $file): string + { + return ''; + } + + private function scanHashDir(string $directory): \Generator + { + if (!file_exists($directory)) { + return; + } + + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + for ($i = 0; $i < 38; ++$i) { + if (!file_exists($directory.$chars[$i])) { + continue; + } + + for ($j = 0; $j < 38; ++$j) { + if (!file_exists($dir = $directory.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + + foreach (@scandir($dir, SCANDIR_SORT_NONE) ?: [] as $file) { + if ('.' !== $file && '..' !== $file) { + yield $dir.\DIRECTORY_SEPARATOR.$file; + } + } + } + } + } + + /** + * @internal + */ + public static function throwError($type, $message, $file, $line) + { + throw new \ErrorException($message, 0, $type, $file, $line); + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if (method_exists(parent::class, '__destruct')) { + parent::__destruct(); + } + if (null !== $this->tmp && file_exists($this->tmp)) { + unlink($this->tmp); + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/FilesystemTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/FilesystemTrait.php new file mode 100644 index 0000000..acf3ea0 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/FilesystemTrait.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\Exception\CacheException; + +/** + * @author Nicolas Grekas + * @author Rob Frawley 2nd + * + * @internal + */ +trait FilesystemTrait +{ + use FilesystemCommonTrait; + + private $marshaller; + + /** + * @return bool + */ + public function prune() + { + $time = time(); + $pruned = true; + + foreach ($this->scanHashDir($this->directory) as $file) { + if (!$h = @fopen($file, 'rb')) { + continue; + } + + if (($expiresAt = (int) fgets($h)) && $time >= $expiresAt) { + fclose($h); + $pruned = @unlink($file) && !file_exists($file) && $pruned; + } else { + fclose($h); + } + } + + return $pruned; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + $values = []; + $now = time(); + + foreach ($ids as $id) { + $file = $this->getFile($id); + if (!file_exists($file) || !$h = @fopen($file, 'rb')) { + continue; + } + if (($expiresAt = (int) fgets($h)) && $now >= $expiresAt) { + fclose($h); + @unlink($file); + } else { + $i = rawurldecode(rtrim(fgets($h))); + $value = stream_get_contents($h); + fclose($h); + if ($i === $id) { + $values[$id] = $this->marshaller->unmarshall($value); + } + } + } + + return $values; + } + + /** + * {@inheritdoc} + */ + protected function doHave($id) + { + $file = $this->getFile($id); + + return file_exists($file) && (@filemtime($file) > time() || $this->doFetch([$id])); + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + $expiresAt = $lifetime ? (time() + $lifetime) : 0; + $values = $this->marshaller->marshall($values, $failed); + + foreach ($values as $id => $value) { + if (!$this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".$value, $expiresAt)) { + $failed[] = $id; + } + } + + if ($failed && !is_writable($this->directory)) { + throw new CacheException(sprintf('Cache directory is not writable (%s).', $this->directory)); + } + + return $failed; + } + + private function getFileKey(string $file): string + { + if (!$h = @fopen($file, 'rb')) { + return ''; + } + + fgets($h); // expiry + $encodedKey = fgets($h); + fclose($h); + + return rawurldecode(rtrim($encodedKey)); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/MemcachedTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/MemcachedTrait.php new file mode 100644 index 0000000..507bc1f --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/MemcachedTrait.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Rob Frawley 2nd + * @author Nicolas Grekas + * + * @internal + */ +trait MemcachedTrait +{ + private static $defaultClientOptions = [ + 'persistent_id' => null, + 'username' => null, + 'password' => null, + \Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP, + ]; + + private $marshaller; + private $client; + private $lazyClient; + + public static function isSupported() + { + return \extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>='); + } + + private function init(\Memcached $client, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller) + { + if (!static::isSupported()) { + throw new CacheException('Memcached >= 2.2.0 is required.'); + } + if ('Memcached' === \get_class($client)) { + $opt = $client->getOption(\Memcached::OPT_SERIALIZER); + if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) { + throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".'); + } + $this->maxIdLength -= \strlen($client->getOption(\Memcached::OPT_PREFIX_KEY)); + $this->client = $client; + } else { + $this->lazyClient = $client; + } + + parent::__construct($namespace, $defaultLifetime); + $this->enableVersioning(); + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + /** + * Creates a Memcached instance. + * + * By default, the binary protocol, no block, and libketama compatible options are enabled. + * + * Examples for servers: + * - 'memcached://user:pass@localhost?weight=33' + * - [['localhost', 11211, 33]] + * + * @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs + * + * @return \Memcached + * + * @throws \ErrorException When invalid options or servers are provided + */ + public static function createConnection($servers, array $options = []) + { + if (\is_string($servers)) { + $servers = [$servers]; + } elseif (!\is_array($servers)) { + throw new InvalidArgumentException(sprintf('MemcachedAdapter::createClient() expects array or string as first argument, "%s" given.', \gettype($servers))); + } + if (!static::isSupported()) { + throw new CacheException('Memcached >= 2.2.0 is required.'); + } + set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); }); + try { + $options += static::$defaultClientOptions; + $client = new \Memcached($options['persistent_id']); + $username = $options['username']; + $password = $options['password']; + + // parse any DSN in $servers + foreach ($servers as $i => $dsn) { + if (\is_array($dsn)) { + continue; + } + if (0 !== strpos($dsn, 'memcached:')) { + throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s" does not start with "memcached:".', $dsn)); + } + $params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) { + if (!empty($m[2])) { + list($username, $password) = explode(':', $m[2], 2) + [1 => null]; + } + + return 'file:'.($m[1] ?? ''); + }, $dsn); + if (false === $params = parse_url($params)) { + throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn)); + } + $query = $hosts = []; + if (isset($params['query'])) { + parse_str($params['query'], $query); + + if (isset($query['host'])) { + if (!\is_array($hosts = $query['host'])) { + throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn)); + } + foreach ($hosts as $host => $weight) { + if (false === $port = strrpos($host, ':')) { + $hosts[$host] = [$host, 11211, (int) $weight]; + } else { + $hosts[$host] = [substr($host, 0, $port), (int) substr($host, 1 + $port), (int) $weight]; + } + } + $hosts = array_values($hosts); + unset($query['host']); + } + if ($hosts && !isset($params['host']) && !isset($params['path'])) { + unset($servers[$i]); + $servers = array_merge($servers, $hosts); + continue; + } + } + if (!isset($params['host']) && !isset($params['path'])) { + throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn)); + } + if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) { + $params['weight'] = $m[1]; + $params['path'] = substr($params['path'], 0, -\strlen($m[0])); + } + $params += [ + 'host' => isset($params['host']) ? $params['host'] : $params['path'], + 'port' => isset($params['host']) ? 11211 : null, + 'weight' => 0, + ]; + if ($query) { + $params += $query; + $options = $query + $options; + } + + $servers[$i] = [$params['host'], $params['port'], $params['weight']]; + + if ($hosts) { + $servers = array_merge($servers, $hosts); + } + } + + // set client's options + unset($options['persistent_id'], $options['username'], $options['password'], $options['weight'], $options['lazy']); + $options = array_change_key_case($options, CASE_UPPER); + $client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $client->setOption(\Memcached::OPT_NO_BLOCK, true); + $client->setOption(\Memcached::OPT_TCP_NODELAY, true); + if (!\array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !\array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) { + $client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true); + } + foreach ($options as $name => $value) { + if (\is_int($name)) { + continue; + } + if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) { + $value = \constant('Memcached::'.$name.'_'.strtoupper($value)); + } + $opt = \constant('Memcached::OPT_'.$name); + + unset($options[$name]); + $options[$opt] = $value; + } + $client->setOptions($options); + + // set client's servers, taking care of persistent connections + if (!$client->isPristine()) { + $oldServers = []; + foreach ($client->getServerList() as $server) { + $oldServers[] = [$server['host'], $server['port']]; + } + + $newServers = []; + foreach ($servers as $server) { + if (1 < \count($server)) { + $server = array_values($server); + unset($server[2]); + $server[1] = (int) $server[1]; + } + $newServers[] = $server; + } + + if ($oldServers !== $newServers) { + $client->resetServerList(); + $client->addServers($servers); + } + } else { + $client->addServers($servers); + } + + if (null !== $username || null !== $password) { + if (!method_exists($client, 'setSaslAuthData')) { + trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.'); + } + $client->setSaslAuthData($username, $password); + } + + return $client; + } finally { + restore_error_handler(); + } + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + if ($lifetime && $lifetime > 30 * 86400) { + $lifetime += time(); + } + + $encodedValues = []; + foreach ($values as $key => $value) { + $encodedValues[rawurlencode($key)] = $value; + } + + return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + try { + $encodedIds = array_map('rawurlencode', $ids); + + $encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds)); + + $result = []; + foreach ($encodedResult as $key => $value) { + $result[rawurldecode($key)] = $this->marshaller->unmarshall($value); + } + + return $result; + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine()); + } + } + + /** + * {@inheritdoc} + */ + protected function doHave($id) + { + return false !== $this->getClient()->get(rawurlencode($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode()); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + $ok = true; + $encodedIds = array_map('rawurlencode', $ids); + foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) { + if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) { + $ok = false; + break; + } + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doClear($namespace) + { + return '' === $namespace && $this->getClient()->flush(); + } + + private function checkResultCode($result) + { + $code = $this->client->getResultCode(); + + if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) { + return $result; + } + + throw new CacheException(sprintf('MemcachedAdapter client error: %s.', strtolower($this->client->getResultMessage()))); + } + + private function getClient(): \Memcached + { + if ($this->client) { + return $this->client; + } + + $opt = $this->lazyClient->getOption(\Memcached::OPT_SERIALIZER); + if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) { + throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".'); + } + if ('' !== $prefix = (string) $this->lazyClient->getOption(\Memcached::OPT_PREFIX_KEY)) { + throw new CacheException(sprintf('MemcachedAdapter: "prefix_key" option must be empty when using proxified connections, "%s" given.', $prefix)); + } + + return $this->client = $this->lazyClient; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/PdoTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/PdoTrait.php new file mode 100644 index 0000000..9b03f1c --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/PdoTrait.php @@ -0,0 +1,446 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\ServerInfoAwareConnection; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\Schema\Schema; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @internal + */ +trait PdoTrait +{ + private $marshaller; + private $conn; + private $dsn; + private $driver; + private $serverVersion; + private $table = 'cache_items'; + private $idCol = 'item_id'; + private $dataCol = 'item_data'; + private $lifetimeCol = 'item_lifetime'; + private $timeCol = 'item_time'; + private $username = ''; + private $password = ''; + private $connectionOptions = []; + private $namespace; + + private function init($connOrDsn, string $namespace, int $defaultLifetime, array $options, ?MarshallerInterface $marshaller) + { + if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0])); + } + + if ($connOrDsn instanceof \PDO) { + if (\PDO::ERRMODE_EXCEPTION !== $connOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { + throw new InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__)); + } + + $this->conn = $connOrDsn; + } elseif ($connOrDsn instanceof Connection) { + $this->conn = $connOrDsn; + } elseif (\is_string($connOrDsn)) { + $this->dsn = $connOrDsn; + } else { + throw new InvalidArgumentException(sprintf('"%s" requires PDO or Doctrine\DBAL\Connection instance or DSN string as first argument, "%s" given.', __CLASS__, \is_object($connOrDsn) ? \get_class($connOrDsn) : \gettype($connOrDsn))); + } + + $this->table = isset($options['db_table']) ? $options['db_table'] : $this->table; + $this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol; + $this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol; + $this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol; + $this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol; + $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username; + $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password; + $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions; + $this->namespace = $namespace; + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + + parent::__construct($namespace, $defaultLifetime); + } + + /** + * Creates the table to store cache items which can be called once for setup. + * + * Cache ID are saved in a column of maximum length 255. Cache data is + * saved in a BLOB. + * + * @throws \PDOException When the table already exists + * @throws DBALException When the table already exists + * @throws \DomainException When an unsupported PDO driver is used + */ + public function createTable() + { + // connect if we are not yet + $conn = $this->getConnection(); + + if ($conn instanceof Connection) { + $types = [ + 'mysql' => 'binary', + 'sqlite' => 'text', + 'pgsql' => 'string', + 'oci' => 'string', + 'sqlsrv' => 'string', + ]; + if (!isset($types[$this->driver])) { + throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver)); + } + + $schema = new Schema(); + $table = $schema->createTable($this->table); + $table->addColumn($this->idCol, $types[$this->driver], ['length' => 255]); + $table->addColumn($this->dataCol, 'blob', ['length' => 16777215]); + $table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]); + $table->addColumn($this->timeCol, 'integer', ['unsigned' => true]); + $table->setPrimaryKey([$this->idCol]); + + foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) { + $conn->exec($sql); + } + + return; + } + + switch ($this->driver) { + case 'mysql': + // We use varbinary for the ID column because it prevents unwanted conversions: + // - character set conversions between server and client + // - trailing space removal + // - case-insensitivity + // - language processing like é == e + $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(255) NOT NULL PRIMARY KEY, $this->dataCol MEDIUMBLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB"; + break; + case 'sqlite': + $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)"; + break; + case 'pgsql': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)"; + break; + case 'oci': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(255) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)"; + break; + case 'sqlsrv': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)"; + break; + default: + throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver)); + } + + $conn->exec($sql); + } + + /** + * {@inheritdoc} + */ + public function prune() + { + $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time"; + + if ('' !== $this->namespace) { + $deleteSql .= " AND $this->idCol LIKE :namespace"; + } + + try { + $delete = $this->getConnection()->prepare($deleteSql); + } catch (TableNotFoundException $e) { + return true; + } catch (\PDOException $e) { + return true; + } + $delete->bindValue(':time', time(), \PDO::PARAM_INT); + + if ('' !== $this->namespace) { + $delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), \PDO::PARAM_STR); + } + try { + return $delete->execute(); + } catch (TableNotFoundException $e) { + return true; + } catch (\PDOException $e) { + return true; + } + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + $now = time(); + $expired = []; + + $sql = str_pad('', (\count($ids) << 1) - 1, '?,'); + $sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN ($sql)"; + $stmt = $this->getConnection()->prepare($sql); + $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT); + foreach ($ids as $id) { + $stmt->bindValue(++$i, $id); + } + $stmt->execute(); + + while ($row = $stmt->fetch(\PDO::FETCH_NUM)) { + if (null === $row[1]) { + $expired[] = $row[0]; + } else { + yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]); + } + } + + if ($expired) { + $sql = str_pad('', (\count($expired) << 1) - 1, '?,'); + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN ($sql)"; + $stmt = $this->getConnection()->prepare($sql); + $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT); + foreach ($expired as $id) { + $stmt->bindValue(++$i, $id); + } + $stmt->execute(); + } + } + + /** + * {@inheritdoc} + */ + protected function doHave($id) + { + $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > :time)"; + $stmt = $this->getConnection()->prepare($sql); + + $stmt->bindValue(':id', $id); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->execute(); + + return (bool) $stmt->fetchColumn(); + } + + /** + * {@inheritdoc} + */ + protected function doClear($namespace) + { + $conn = $this->getConnection(); + + if ('' === $namespace) { + if ('sqlite' === $this->driver) { + $sql = "DELETE FROM $this->table"; + } else { + $sql = "TRUNCATE TABLE $this->table"; + } + } else { + $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'"; + } + + try { + $conn->exec($sql); + } catch (TableNotFoundException $e) { + } catch (\PDOException $e) { + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + $sql = str_pad('', (\count($ids) << 1) - 1, '?,'); + $sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)"; + try { + $stmt = $this->getConnection()->prepare($sql); + $stmt->execute(array_values($ids)); + } catch (TableNotFoundException $e) { + } catch (\PDOException $e) { + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $conn = $this->getConnection(); + $driver = $this->driver; + $insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + + switch (true) { + case 'mysql' === $driver: + $sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'oci' === $driver: + // DUAL is Oracle specific dummy table + $sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?"; + break; + case 'sqlsrv' === $driver && version_compare($this->getServerVersion(), '10', '>='): + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx + $sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite' === $driver: + $sql = 'INSERT OR REPLACE'.substr($insertSql, 6); + break; + case 'pgsql' === $driver && version_compare($this->getServerVersion(), '9.5', '>='): + $sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + default: + $driver = null; + $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"; + break; + } + + $now = time(); + $lifetime = $lifetime ?: null; + try { + $stmt = $conn->prepare($sql); + } catch (TableNotFoundException $e) { + if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + $this->createTable(); + } + $stmt = $conn->prepare($sql); + } catch (\PDOException $e) { + if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + $this->createTable(); + } + $stmt = $conn->prepare($sql); + } + + if ('sqlsrv' === $driver || 'oci' === $driver) { + $stmt->bindParam(1, $id); + $stmt->bindParam(2, $id); + $stmt->bindParam(3, $data, \PDO::PARAM_LOB); + $stmt->bindValue(4, $lifetime, \PDO::PARAM_INT); + $stmt->bindValue(5, $now, \PDO::PARAM_INT); + $stmt->bindParam(6, $data, \PDO::PARAM_LOB); + $stmt->bindValue(7, $lifetime, \PDO::PARAM_INT); + $stmt->bindValue(8, $now, \PDO::PARAM_INT); + } else { + $stmt->bindParam(':id', $id); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', $now, \PDO::PARAM_INT); + } + if (null === $driver) { + $insertStmt = $conn->prepare($insertSql); + + $insertStmt->bindParam(':id', $id); + $insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $insertStmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT); + $insertStmt->bindValue(':time', $now, \PDO::PARAM_INT); + } + + foreach ($values as $id => $data) { + try { + $stmt->execute(); + } catch (TableNotFoundException $e) { + if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + $this->createTable(); + } + $stmt->execute(); + } catch (\PDOException $e) { + if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + $this->createTable(); + } + $stmt->execute(); + } + if (null === $driver && !$stmt->rowCount()) { + try { + $insertStmt->execute(); + } catch (DBALException $e) { + } catch (\PDOException $e) { + // A concurrent write won, let it be + } + } + } + + return $failed; + } + + /** + * @return \PDO|Connection + */ + private function getConnection() + { + if (null === $this->conn) { + if (strpos($this->dsn, '://')) { + if (!class_exists(DriverManager::class)) { + throw new InvalidArgumentException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $this->dsn)); + } + $this->conn = DriverManager::getConnection(['url' => $this->dsn]); + } else { + $this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions); + $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + } + } + if (null === $this->driver) { + if ($this->conn instanceof \PDO) { + $this->driver = $this->conn->getAttribute(\PDO::ATTR_DRIVER_NAME); + } else { + switch ($this->driver = $this->conn->getDriver()->getName()) { + case 'mysqli': + throw new \LogicException(sprintf('The adapter "%s" does not support the mysqli driver, use pdo_mysql instead.', static::class)); + case 'pdo_mysql': + case 'drizzle_pdo_mysql': + $this->driver = 'mysql'; + break; + case 'pdo_sqlite': + $this->driver = 'sqlite'; + break; + case 'pdo_pgsql': + $this->driver = 'pgsql'; + break; + case 'oci8': + case 'pdo_oracle': + $this->driver = 'oci'; + break; + case 'pdo_sqlsrv': + $this->driver = 'sqlsrv'; + break; + } + } + } + + return $this->conn; + } + + private function getServerVersion(): string + { + if (null === $this->serverVersion) { + $conn = $this->conn instanceof \PDO ? $this->conn : $this->conn->getWrappedConnection(); + if ($conn instanceof \PDO) { + $this->serverVersion = $conn->getAttribute(\PDO::ATTR_SERVER_VERSION); + } elseif ($conn instanceof ServerInfoAwareConnection) { + $this->serverVersion = $conn->getServerVersion(); + } else { + $this->serverVersion = '0'; + } + } + + return $this->serverVersion; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/PhpArrayTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/PhpArrayTrait.php new file mode 100644 index 0000000..be352e0 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/PhpArrayTrait.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\VarExporter\VarExporter; + +/** + * @author Titouan Galopin + * @author Nicolas Grekas + * + * @internal + */ +trait PhpArrayTrait +{ + use ProxyTrait; + + private $file; + private $keys; + private $values; + + private static $valuesCache = []; + + /** + * Store an array of cached values. + * + * @param array $values The cached values + */ + public function warmUp(array $values) + { + if (file_exists($this->file)) { + if (!is_file($this->file)) { + throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: "%s".', $this->file)); + } + + if (!is_writable($this->file)) { + throw new InvalidArgumentException(sprintf('Cache file is not writable: "%s".', $this->file)); + } + } else { + $directory = \dirname($this->file); + + if (!is_dir($directory) && !@mkdir($directory, 0777, true)) { + throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: "%s".', $directory)); + } + + if (!is_writable($directory)) { + throw new InvalidArgumentException(sprintf('Cache directory is not writable: "%s".', $directory)); + } + } + + $dumpedValues = ''; + $dumpedMap = []; + $dump = <<<'EOF' + $value) { + CacheItem::validateKey(\is_int($key) ? (string) $key : $key); + $isStaticValue = true; + + if (null === $value) { + $value = "'N;'"; + } elseif (\is_object($value) || \is_array($value)) { + try { + $value = VarExporter::export($value, $isStaticValue); + } catch (\Exception $e) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e); + } + } elseif (\is_string($value)) { + // Wrap "N;" in a closure to not confuse it with an encoded `null` + if ('N;' === $value) { + $isStaticValue = false; + } + $value = var_export($value, true); + } elseif (!is_scalar($value)) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \gettype($value))); + } else { + $value = var_export($value, true); + } + + if (!$isStaticValue) { + $value = str_replace("\n", "\n ", $value); + $value = "static function () {\n return {$value};\n}"; + } + $hash = hash('md5', $value); + + if (null === $id = $dumpedMap[$hash] ?? null) { + $id = $dumpedMap[$hash] = \count($dumpedMap); + $dumpedValues .= "{$id} => {$value},\n"; + } + + $dump .= var_export($key, true)." => {$id},\n"; + } + + $dump .= "\n], [\n\n{$dumpedValues}\n]];\n"; + + $tmpFile = uniqid($this->file, true); + + file_put_contents($tmpFile, $dump); + @chmod($tmpFile, 0666 & ~umask()); + unset($serialized, $value, $dump); + + @rename($tmpFile, $this->file); + unset(self::$valuesCache[$this->file]); + + $this->initialize(); + } + + /** + * {@inheritdoc} + * + * @param string $prefix + * + * @return bool + */ + public function clear(/*string $prefix = ''*/) + { + $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : ''; + $this->keys = $this->values = []; + + $cleared = @unlink($this->file) || !file_exists($this->file); + unset(self::$valuesCache[$this->file]); + + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($prefix) && $cleared; + } + + return $this->pool->clear() && $cleared; + } + + /** + * Load the cache file. + */ + private function initialize() + { + if (isset(self::$valuesCache[$this->file])) { + $values = self::$valuesCache[$this->file]; + } elseif (!file_exists($this->file)) { + $this->keys = $this->values = []; + + return; + } else { + $values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []]; + } + + if (2 !== \count($values) || !isset($values[0], $values[1])) { + $this->keys = $this->values = []; + } else { + list($this->keys, $this->values) = $values; + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/PhpFilesTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/PhpFilesTrait.php new file mode 100644 index 0000000..6f8c45e --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/PhpFilesTrait.php @@ -0,0 +1,313 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\VarExporter\VarExporter; + +/** + * @author Piotr Stankowski + * @author Nicolas Grekas + * @author Rob Frawley 2nd + * + * @internal + */ +trait PhpFilesTrait +{ + use FilesystemCommonTrait { + doClear as private doCommonClear; + doDelete as private doCommonDelete; + } + + private $includeHandler; + private $appendOnly; + private $values = []; + private $files = []; + + private static $startTime; + private static $valuesCache = []; + + public static function isSupported() + { + self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time(); + + return \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN)); + } + + /** + * @return bool + */ + public function prune() + { + $time = time(); + $pruned = true; + $getExpiry = true; + + set_error_handler($this->includeHandler); + try { + foreach ($this->scanHashDir($this->directory) as $file) { + try { + if (\is_array($expiresAt = include $file)) { + $expiresAt = $expiresAt[0]; + } + } catch (\ErrorException $e) { + $expiresAt = $time; + } + + if ($time >= $expiresAt) { + $pruned = $this->doUnlink($file) && !file_exists($file) && $pruned; + } + } + } finally { + restore_error_handler(); + } + + return $pruned; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + if ($this->appendOnly) { + $now = 0; + $missingIds = []; + } else { + $now = time(); + $missingIds = $ids; + $ids = []; + } + $values = []; + + begin: + $getExpiry = false; + + foreach ($ids as $id) { + if (null === $value = $this->values[$id] ?? null) { + $missingIds[] = $id; + } elseif ('N;' === $value) { + $values[$id] = null; + } elseif (!\is_object($value)) { + $values[$id] = $value; + } elseif (!$value instanceof LazyValue) { + $values[$id] = $value(); + } elseif (false === $values[$id] = include $value->file) { + unset($values[$id], $this->values[$id]); + $missingIds[] = $id; + } + if (!$this->appendOnly) { + unset($this->values[$id]); + } + } + + if (!$missingIds) { + return $values; + } + + set_error_handler($this->includeHandler); + try { + $getExpiry = true; + + foreach ($missingIds as $k => $id) { + try { + $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id); + + if (isset(self::$valuesCache[$file])) { + [$expiresAt, $this->values[$id]] = self::$valuesCache[$file]; + } elseif (\is_array($expiresAt = include $file)) { + if ($this->appendOnly) { + self::$valuesCache[$file] = $expiresAt; + } + + [$expiresAt, $this->values[$id]] = $expiresAt; + } elseif ($now < $expiresAt) { + $this->values[$id] = new LazyValue($file); + } + + if ($now >= $expiresAt) { + unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]); + } + } catch (\ErrorException $e) { + unset($missingIds[$k]); + } + } + } finally { + restore_error_handler(); + } + + $ids = $missingIds; + $missingIds = []; + goto begin; + } + + /** + * {@inheritdoc} + */ + protected function doHave($id) + { + if ($this->appendOnly && isset($this->values[$id])) { + return true; + } + + set_error_handler($this->includeHandler); + try { + $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id); + $getExpiry = true; + + if (isset(self::$valuesCache[$file])) { + [$expiresAt, $value] = self::$valuesCache[$file]; + } elseif (\is_array($expiresAt = include $file)) { + if ($this->appendOnly) { + self::$valuesCache[$file] = $expiresAt; + } + + [$expiresAt, $value] = $expiresAt; + } elseif ($this->appendOnly) { + $value = new LazyValue($file); + } + } catch (\ErrorException $e) { + return false; + } finally { + restore_error_handler(); + } + if ($this->appendOnly) { + $now = 0; + $this->values[$id] = $value; + } else { + $now = time(); + } + + return $now < $expiresAt; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + $ok = true; + $expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX'; + $allowCompile = self::isSupported(); + + foreach ($values as $key => $value) { + unset($this->values[$key]); + $isStaticValue = true; + if (null === $value) { + $value = "'N;'"; + } elseif (\is_object($value) || \is_array($value)) { + try { + $value = VarExporter::export($value, $isStaticValue); + } catch (\Exception $e) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e); + } + } elseif (\is_string($value)) { + // Wrap "N;" in a closure to not confuse it with an encoded `null` + if ('N;' === $value) { + $isStaticValue = false; + } + $value = var_export($value, true); + } elseif (!is_scalar($value)) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \gettype($value))); + } else { + $value = var_export($value, true); + } + + $encodedKey = rawurlencode($key); + + if ($isStaticValue) { + $value = "return [{$expiry}, {$value}];"; + } elseif ($this->appendOnly) { + $value = "return [{$expiry}, static function () { return {$value}; }];"; + } else { + // We cannot use a closure here because of https://bugs.php.net/76982 + $value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value); + $value = "namespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};"; + } + + $file = $this->files[$key] = $this->getFile($key, true); + // Since OPcache only compiles files older than the script execution start, set the file's mtime in the past + $ok = $this->write($file, "directory)) { + throw new CacheException(sprintf('Cache directory is not writable (%s).', $this->directory)); + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doClear($namespace) + { + $this->values = []; + + return $this->doCommonClear($namespace); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + foreach ($ids as $id) { + unset($this->values[$id]); + } + + return $this->doCommonDelete($ids); + } + + protected function doUnlink($file) + { + unset(self::$valuesCache[$file]); + + if (self::isSupported()) { + @opcache_invalidate($file, true); + } + + return @unlink($file); + } + + private function getFileKey(string $file): string + { + if (!$h = @fopen($file, 'rb')) { + return ''; + } + + $encodedKey = substr(fgets($h), 8); + fclose($h); + + return rawurldecode(rtrim($encodedKey)); + } +} + +/** + * @internal + */ +class LazyValue +{ + public $file; + + public function __construct(string $file) + { + $this->file = $file; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ProxyTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ProxyTrait.php new file mode 100644 index 0000000..c86f360 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/ProxyTrait.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait ProxyTrait +{ + private $pool; + + /** + * {@inheritdoc} + */ + public function prune() + { + return $this->pool instanceof PruneableInterface && $this->pool->prune(); + } + + /** + * {@inheritdoc} + */ + public function reset() + { + if ($this->pool instanceof ResetInterface) { + $this->pool->reset(); + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/RedisClusterProxy.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/RedisClusterProxy.php new file mode 100644 index 0000000..b4cef59 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/RedisClusterProxy.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +/** + * @author Alessandro Chitolina + * + * @internal + */ +class RedisClusterProxy +{ + private $redis; + private $initializer; + + public function __construct(\Closure $initializer) + { + $this->initializer = $initializer; + } + + public function __call($method, array $args) + { + $this->redis ?: $this->redis = $this->initializer->__invoke(); + + return $this->redis->{$method}(...$args); + } + + public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null) + { + $this->redis ?: $this->redis = $this->initializer->__invoke(); + + return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount); + } + + public function scan(&$iIterator, $strPattern = null, $iCount = null) + { + $this->redis ?: $this->redis = $this->initializer->__invoke(); + + return $this->redis->scan($iIterator, $strPattern, $iCount); + } + + public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null) + { + $this->redis ?: $this->redis = $this->initializer->__invoke(); + + return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount); + } + + public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null) + { + $this->redis ?: $this->redis = $this->initializer->__invoke(); + + return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/RedisProxy.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/RedisProxy.php new file mode 100644 index 0000000..2b0b857 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/RedisProxy.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class RedisProxy +{ + private $redis; + private $initializer; + private $ready = false; + + public function __construct(\Redis $redis, \Closure $initializer) + { + $this->redis = $redis; + $this->initializer = $initializer; + } + + public function __call($method, array $args) + { + $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); + + return $this->redis->{$method}(...$args); + } + + public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null) + { + $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); + + return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount); + } + + public function scan(&$iIterator, $strPattern = null, $iCount = null) + { + $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); + + return $this->redis->scan($iIterator, $strPattern, $iCount); + } + + public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null) + { + $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); + + return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount); + } + + public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null) + { + $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); + + return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/RedisTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/RedisTrait.php new file mode 100644 index 0000000..d934a5f --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/Traits/RedisTrait.php @@ -0,0 +1,511 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Predis\Connection\Aggregate\ClusterInterface; +use Predis\Connection\Aggregate\RedisCluster; +use Predis\Response\Status; +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Aurimas Niekis + * @author Nicolas Grekas + * + * @internal + */ +trait RedisTrait +{ + private static $defaultConnectionOptions = [ + 'class' => null, + 'persistent' => 0, + 'persistent_id' => null, + 'timeout' => 30, + 'read_timeout' => 0, + 'retry_interval' => 0, + 'tcp_keepalive' => 0, + 'lazy' => null, + 'redis_cluster' => false, + 'redis_sentinel' => null, + 'dbindex' => 0, + 'failover' => 'none', + ]; + private $redis; + private $marshaller; + + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient + */ + private function init($redisClient, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller) + { + parent::__construct($namespace, $defaultLifetime); + + if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); + } + + if (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \RedisCluster && !$redisClient instanceof \Predis\ClientInterface && !$redisClient instanceof RedisProxy && !$redisClient instanceof RedisClusterProxy) { + throw new InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, \is_object($redisClient) ? \get_class($redisClient) : \gettype($redisClient))); + } + + if ($redisClient instanceof \Predis\ClientInterface && $redisClient->getOptions()->exceptions) { + $options = clone $redisClient->getOptions(); + \Closure::bind(function () { $this->options['exceptions'] = false; }, $options, $options)(); + $redisClient = new $redisClient($redisClient->getConnection(), $options); + } + + $this->redis = $redisClient; + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + /** + * Creates a Redis connection using a DSN configuration. + * + * Example DSN: + * - redis://localhost + * - redis://example.com:1234 + * - redis://secret@example.com/13 + * - redis:///var/run/redis.sock + * - redis://secret@/var/run/redis.sock/13 + * + * @param string $dsn + * @param array $options See self::$defaultConnectionOptions + * + * @throws InvalidArgumentException when the DSN is invalid + * + * @return \Redis|\RedisCluster|\Predis\ClientInterface According to the "class" option + */ + public static function createConnection($dsn, array $options = []) + { + if (0 === strpos($dsn, 'redis:')) { + $scheme = 'redis'; + } elseif (0 === strpos($dsn, 'rediss:')) { + $scheme = 'rediss'; + } else { + throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s" does not start with "redis:" or "rediss".', $dsn)); + } + + if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) { + throw new CacheException(sprintf('Cannot find the "redis" extension nor the "predis/predis" package: "%s".', $dsn)); + } + + $params = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) { + if (isset($m[2])) { + $auth = $m[2]; + } + + return 'file:'.($m[1] ?? ''); + }, $dsn); + + if (false === $params = parse_url($params)) { + throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn)); + } + + $query = $hosts = []; + + if (isset($params['query'])) { + parse_str($params['query'], $query); + + if (isset($query['host'])) { + if (!\is_array($hosts = $query['host'])) { + throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn)); + } + foreach ($hosts as $host => $parameters) { + if (\is_string($parameters)) { + parse_str($parameters, $parameters); + } + if (false === $i = strrpos($host, ':')) { + $hosts[$host] = ['scheme' => 'tcp', 'host' => $host, 'port' => 6379] + $parameters; + } elseif ($port = (int) substr($host, 1 + $i)) { + $hosts[$host] = ['scheme' => 'tcp', 'host' => substr($host, 0, $i), 'port' => $port] + $parameters; + } else { + $hosts[$host] = ['scheme' => 'unix', 'path' => substr($host, 0, $i)] + $parameters; + } + } + $hosts = array_values($hosts); + } + } + + if (isset($params['host']) || isset($params['path'])) { + if (!isset($params['dbindex']) && isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) { + $params['dbindex'] = $m[1]; + $params['path'] = substr($params['path'], 0, -\strlen($m[0])); + } + + if (isset($params['host'])) { + array_unshift($hosts, ['scheme' => 'tcp', 'host' => $params['host'], 'port' => $params['port'] ?? 6379]); + } else { + array_unshift($hosts, ['scheme' => 'unix', 'path' => $params['path']]); + } + } + + if (!$hosts) { + throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn)); + } + + if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class)) { + throw new CacheException(sprintf('Redis Sentinel support requires the "predis/predis" package: "%s".', $dsn)); + } + + $params += $query + $options + self::$defaultConnectionOptions; + + if (null === $params['class'] && !isset($params['redis_sentinel']) && \extension_loaded('redis')) { + $class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) ? \RedisArray::class : \Redis::class); + } else { + $class = null === $params['class'] ? \Predis\Client::class : $params['class']; + } + + if (is_a($class, \Redis::class, true)) { + $connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect'; + $redis = new $class(); + + $initializer = function ($redis) use ($connect, $params, $dsn, $auth, $hosts) { + try { + @$redis->{$connect}($hosts[0]['host'] ?? $hosts[0]['path'], $hosts[0]['port'] ?? null, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval']); + } catch (\RedisException $e) { + throw new InvalidArgumentException(sprintf('Redis connection failed (%s): "%s".', $e->getMessage(), $dsn)); + } + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $isConnected = $redis->isConnected(); + restore_error_handler(); + if (!$isConnected) { + $error = preg_match('/^Redis::p?connect\(\): (.*)/', $error, $error) ? sprintf(' (%s)', $error[1]) : ''; + throw new InvalidArgumentException(sprintf('Redis connection failed%s: "%s".', $error, $dsn)); + } + + if ((null !== $auth && !$redis->auth($auth)) + || ($params['dbindex'] && !$redis->select($params['dbindex'])) + || ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout'])) + ) { + $e = preg_replace('/^ERR /', '', $redis->getLastError()); + throw new InvalidArgumentException(sprintf('Redis connection failed (%s): "%s".', $e, $dsn)); + } + + if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { + $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + + return true; + }; + + if ($params['lazy']) { + $redis = new RedisProxy($redis, $initializer); + } else { + $initializer($redis); + } + } elseif (is_a($class, \RedisArray::class, true)) { + foreach ($hosts as $i => $host) { + $hosts[$i] = 'tcp' === $host['scheme'] ? $host['host'].':'.$host['port'] : $host['path']; + } + $params['lazy_connect'] = $params['lazy'] ?? true; + $params['connect_timeout'] = $params['timeout']; + + try { + $redis = new $class($hosts, $params); + } catch (\RedisClusterException $e) { + throw new InvalidArgumentException(sprintf('Redis connection failed (%s): "%s".', $e->getMessage(), $dsn)); + } + + if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { + $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + } elseif (is_a($class, \RedisCluster::class, true)) { + $initializer = function () use ($class, $params, $dsn, $hosts) { + foreach ($hosts as $i => $host) { + $hosts[$i] = 'tcp' === $host['scheme'] ? $host['host'].':'.$host['port'] : $host['path']; + } + + try { + $redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent']); + } catch (\RedisClusterException $e) { + throw new InvalidArgumentException(sprintf('Redis connection failed (%s): "%s".', $e->getMessage(), $dsn)); + } + + if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { + $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + switch ($params['failover']) { + case 'error': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_ERROR); break; + case 'distribute': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE); break; + case 'slaves': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES); break; + } + + return $redis; + }; + + $redis = $params['lazy'] ? new RedisClusterProxy($initializer) : $initializer(); + } elseif (is_a($class, \Predis\ClientInterface::class, true)) { + if ($params['redis_cluster']) { + $params['cluster'] = 'redis'; + if (isset($params['redis_sentinel'])) { + throw new InvalidArgumentException(sprintf('Cannot use both "redis_cluster" and "redis_sentinel" at the same time: "%s".', $dsn)); + } + } elseif (isset($params['redis_sentinel'])) { + $params['replication'] = 'sentinel'; + $params['service'] = $params['redis_sentinel']; + } + $params += ['parameters' => []]; + $params['parameters'] += [ + 'persistent' => $params['persistent'], + 'timeout' => $params['timeout'], + 'read_write_timeout' => $params['read_timeout'], + 'tcp_nodelay' => true, + ]; + if ($params['dbindex']) { + $params['parameters']['database'] = $params['dbindex']; + } + if (null !== $auth) { + $params['parameters']['password'] = $auth; + } + if (1 === \count($hosts) && !($params['redis_cluster'] || $params['redis_sentinel'])) { + $hosts = $hosts[0]; + } elseif (\in_array($params['failover'], ['slaves', 'distribute'], true) && !isset($params['replication'])) { + $params['replication'] = true; + $hosts[0] += ['alias' => 'master']; + } + $params['exceptions'] = false; + + $redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions)); + if (isset($params['redis_sentinel'])) { + $redis->getConnection()->setSentinelTimeout($params['timeout']); + } + } elseif (class_exists($class, false)) { + throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\ClientInterface".', $class)); + } else { + throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + return $redis; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + if (!$ids) { + return []; + } + + $result = []; + + if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) { + $values = $this->pipeline(function () use ($ids) { + foreach ($ids as $id) { + yield 'get' => [$id]; + } + }); + } else { + $values = $this->redis->mget($ids); + + if (!\is_array($values) || \count($values) !== \count($ids)) { + return []; + } + + $values = array_combine($ids, $values); + } + + foreach ($values as $id => $v) { + if ($v) { + $result[$id] = $this->marshaller->unmarshall($v); + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + protected function doHave($id) + { + return (bool) $this->redis->exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doClear($namespace) + { + $cleared = true; + if ($this->redis instanceof \Predis\ClientInterface) { + $evalArgs = [0, $namespace]; + } else { + $evalArgs = [[$namespace], 0]; + } + + foreach ($this->getHosts() as $host) { + if (!isset($namespace[0])) { + $cleared = $host->flushDb() && $cleared; + continue; + } + + $info = $host->info('Server'); + $info = isset($info['Server']) ? $info['Server'] : $info; + + if (!version_compare($info['redis_version'], '2.8', '>=')) { + // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS + // can hang your server when it is executed against large databases (millions of items). + // Whenever you hit this scale, you should really consider upgrading to Redis 2.8 or above. + $cleared = $host->eval("local keys=redis.call('KEYS',ARGV[1]..'*') for i=1,#keys,5000 do redis.call('DEL',unpack(keys,i,math.min(i+4999,#keys))) end return 1", $evalArgs[0], $evalArgs[1]) && $cleared; + continue; + } + + $cursor = null; + do { + $keys = $host instanceof \Predis\ClientInterface ? $host->scan($cursor, 'MATCH', $namespace.'*', 'COUNT', 1000) : $host->scan($cursor, $namespace.'*', 1000); + if (isset($keys[1]) && \is_array($keys[1])) { + $cursor = $keys[0]; + $keys = $keys[1]; + } + if ($keys) { + $this->doDelete($keys); + } + } while ($cursor = (int) $cursor); + } + + return $cleared; + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + if (!$ids) { + return true; + } + + if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) { + $this->pipeline(function () use ($ids) { + foreach ($ids as $id) { + yield 'del' => [$id]; + } + })->rewind(); + } else { + $this->redis->del($ids); + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $results = $this->pipeline(function () use ($values, $lifetime) { + foreach ($values as $id => $value) { + if (0 >= $lifetime) { + yield 'set' => [$id, $value]; + } else { + yield 'setEx' => [$id, $lifetime, $value]; + } + } + }); + + foreach ($results as $id => $result) { + if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) { + $failed[] = $id; + } + } + + return $failed; + } + + private function pipeline(\Closure $generator, $redis = null): \Generator + { + $ids = []; + $redis = $redis ?? $this->redis; + + if ($redis instanceof RedisClusterProxy || $redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof RedisCluster)) { + // phpredis & predis don't support pipelining with RedisCluster + // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining + // see https://github.com/nrk/predis/issues/267#issuecomment-123781423 + $results = []; + foreach ($generator() as $command => $args) { + $results[] = $redis->{$command}(...$args); + $ids[] = 'eval' === $command ? ($redis instanceof \Predis\ClientInterface ? $args[2] : $args[1][0]) : $args[0]; + } + } elseif ($redis instanceof \Predis\ClientInterface) { + $results = $redis->pipeline(static function ($redis) use ($generator, &$ids) { + foreach ($generator() as $command => $args) { + $redis->{$command}(...$args); + $ids[] = 'eval' === $command ? $args[2] : $args[0]; + } + }); + } elseif ($redis instanceof \RedisArray) { + $connections = $results = $ids = []; + foreach ($generator() as $command => $args) { + $id = 'eval' === $command ? $args[1][0] : $args[0]; + if (!isset($connections[$h = $redis->_target($id)])) { + $connections[$h] = [$redis->_instance($h), -1]; + $connections[$h][0]->multi(\Redis::PIPELINE); + } + $connections[$h][0]->{$command}(...$args); + $results[] = [$h, ++$connections[$h][1]]; + $ids[] = $id; + } + foreach ($connections as $h => $c) { + $connections[$h] = $c[0]->exec(); + } + foreach ($results as $k => list($h, $c)) { + $results[$k] = $connections[$h][$c]; + } + } else { + $redis->multi(\Redis::PIPELINE); + foreach ($generator() as $command => $args) { + $redis->{$command}(...$args); + $ids[] = 'eval' === $command ? $args[1][0] : $args[0]; + } + $results = $redis->exec(); + } + + foreach ($ids as $k => $id) { + yield $id => $results[$k]; + } + } + + private function getHosts(): array + { + $hosts = [$this->redis]; + if ($this->redis instanceof \Predis\ClientInterface) { + $connection = $this->redis->getConnection(); + if ($connection instanceof ClusterInterface && $connection instanceof \Traversable) { + $hosts = []; + foreach ($connection as $c) { + $hosts[] = new \Predis\Client($c); + } + } + } elseif ($this->redis instanceof \RedisArray) { + $hosts = []; + foreach ($this->redis->_hosts() as $host) { + $hosts[] = $this->redis->_instance($host); + } + } elseif ($this->redis instanceof RedisClusterProxy || $this->redis instanceof \RedisCluster) { + $hosts = []; + foreach ($this->redis->_masters() as $host) { + $hosts[] = $h = new \Redis(); + $h->connect($host[0], $host[1]); + } + } + + return $hosts; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/cache/composer.json b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/composer.json new file mode 100644 index 0000000..0f0033c --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/cache/composer.json @@ -0,0 +1,59 @@ +{ + "name": "symfony/cache", + "type": "library", + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "keywords": ["caching", "psr6"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0", + "symfony/cache-implementation": "1.0" + }, + "require": { + "php": "^7.1.3", + "psr/cache": "~1.0", + "psr/log": "~1.0", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/service-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.2|^5.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.5", + "predis/predis": "~1.1", + "psr/simple-cache": "^1.0", + "symfony/config": "^4.2|^5.0", + "symfony/dependency-injection": "^3.4|^4.1|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "conflict": { + "doctrine/dbal": "<2.5", + "symfony/dependency-injection": "<3.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Cache\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/.gitignore b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/CHANGELOG.md b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/CHANGELOG.md new file mode 100644 index 0000000..d00d17c --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/CHANGELOG.md @@ -0,0 +1,12 @@ +CHANGELOG +========= + +2.6.0 +----- + + * Added ExpressionFunction and ExpressionFunctionProviderInterface + +2.4.0 +----- + + * added the component diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Compiler.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Compiler.php new file mode 100644 index 0000000..282e82d --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Compiler.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Compiles a node to PHP code. + * + * @author Fabien Potencier + */ +class Compiler +{ + private $source; + private $functions; + + public function __construct(array $functions) + { + $this->functions = $functions; + } + + public function getFunction($name) + { + return $this->functions[$name]; + } + + /** + * Gets the current PHP code after compilation. + * + * @return string The PHP code + */ + public function getSource() + { + return $this->source; + } + + public function reset() + { + $this->source = ''; + + return $this; + } + + /** + * Compiles a node. + * + * @return $this + */ + public function compile(Node\Node $node) + { + $node->compile($this); + + return $this; + } + + public function subcompile(Node\Node $node) + { + $current = $this->source; + $this->source = ''; + + $node->compile($this); + + $source = $this->source; + $this->source = $current; + + return $source; + } + + /** + * Adds a raw string to the compiled code. + * + * @param string $string The string + * + * @return $this + */ + public function raw($string) + { + $this->source .= $string; + + 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('['); + $first = true; + foreach ($value as $key => $value) { + if (!$first) { + $this->raw(', '); + } + $first = false; + $this->repr($key); + $this->raw(' => '); + $this->repr($value); + } + $this->raw(']'); + } else { + $this->string($value); + } + + return $this; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Expression.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Expression.php new file mode 100644 index 0000000..ac656cc --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Expression.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents an expression. + * + * @author Fabien Potencier + */ +class Expression +{ + protected $expression; + + /** + * @param string $expression An expression + */ + public function __construct($expression) + { + $this->expression = (string) $expression; + } + + /** + * Gets the expression. + * + * @return string The expression + */ + public function __toString() + { + return $this->expression; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ExpressionFunction.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ExpressionFunction.php new file mode 100644 index 0000000..77c88f6 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ExpressionFunction.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents a function that can be used in an expression. + * + * A function is defined by two PHP callables. The callables are used + * by the language to compile and/or evaluate the function. + * + * The "compiler" function is used at compilation time and must return a + * PHP representation of the function call (it receives the function + * arguments as arguments). + * + * The "evaluator" function is used for expression evaluation and must return + * the value of the function call based on the values defined for the + * expression (it receives the values as a first argument and the function + * arguments as remaining arguments). + * + * @author Fabien Potencier + */ +class ExpressionFunction +{ + private $name; + private $compiler; + private $evaluator; + + /** + * @param string $name The function name + * @param callable $compiler A callable able to compile the function + * @param callable $evaluator A callable able to evaluate the function + */ + public function __construct($name, callable $compiler, callable $evaluator) + { + $this->name = $name; + $this->compiler = $compiler; + $this->evaluator = $evaluator; + } + + public function getName() + { + return $this->name; + } + + public function getCompiler() + { + return $this->compiler; + } + + public function getEvaluator() + { + return $this->evaluator; + } + + /** + * Creates an ExpressionFunction from a PHP function name. + * + * @param string $phpFunctionName The PHP function name + * @param string|null $expressionFunctionName The expression function name (default: same than the PHP function name) + * + * @return self + * + * @throws \InvalidArgumentException if given PHP function name does not exist + * @throws \InvalidArgumentException if given PHP function name is in namespace + * and expression function name is not defined + */ + public static function fromPhp($phpFunctionName, $expressionFunctionName = null) + { + $phpFunctionName = ltrim($phpFunctionName, '\\'); + if (!\function_exists($phpFunctionName)) { + throw new \InvalidArgumentException(sprintf('PHP function "%s" does not exist.', $phpFunctionName)); + } + + $parts = explode('\\', $phpFunctionName); + if (!$expressionFunctionName && \count($parts) > 1) { + throw new \InvalidArgumentException(sprintf('An expression function name must be defined when PHP function "%s" is namespaced.', $phpFunctionName)); + } + + $compiler = function () use ($phpFunctionName) { + return sprintf('\%s(%s)', $phpFunctionName, implode(', ', \func_get_args())); + }; + + $evaluator = function () use ($phpFunctionName) { + $args = \func_get_args(); + + return \call_user_func_array($phpFunctionName, array_splice($args, 1)); + }; + + return new self($expressionFunctionName ?: end($parts), $compiler, $evaluator); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ExpressionFunctionProviderInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ExpressionFunctionProviderInterface.php new file mode 100644 index 0000000..414b013 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ExpressionFunctionProviderInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * @author Fabien Potencier + */ +interface ExpressionFunctionProviderInterface +{ + /** + * @return ExpressionFunction[] An array of Function instances + */ + public function getFunctions(); +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ExpressionLanguage.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ExpressionLanguage.php new file mode 100644 index 0000000..ad06107 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ExpressionLanguage.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheAdapter; +use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface; + +/** + * Allows to compile and evaluate expressions written in your own DSL. + * + * @author Fabien Potencier + */ +class ExpressionLanguage +{ + private $cache; + private $lexer; + private $parser; + private $compiler; + + protected $functions = []; + + /** + * @param CacheItemPoolInterface $cache + * @param ExpressionFunctionProviderInterface[] $providers + */ + public function __construct($cache = null, array $providers = []) + { + if (null !== $cache) { + if ($cache instanceof ParserCacheInterface) { + @trigger_error(sprintf('Passing an instance of %s as constructor argument for %s is deprecated as of 3.2 and will be removed in 4.0. Pass an instance of %s instead.', ParserCacheInterface::class, self::class, CacheItemPoolInterface::class), E_USER_DEPRECATED); + $cache = new ParserCacheAdapter($cache); + } elseif (!$cache instanceof CacheItemPoolInterface) { + throw new \InvalidArgumentException(sprintf('Cache argument has to implement "%s".', CacheItemPoolInterface::class)); + } + } + + $this->cache = $cache ?: new ArrayAdapter(); + $this->registerFunctions(); + foreach ($providers as $provider) { + $this->registerProvider($provider); + } + } + + /** + * Compiles an expression source code. + * + * @param Expression|string $expression The expression to compile + * @param array $names An array of valid names + * + * @return string The compiled PHP source code + */ + public function compile($expression, $names = []) + { + return $this->getCompiler()->compile($this->parse($expression, $names)->getNodes())->getSource(); + } + + /** + * Evaluate an expression. + * + * @param Expression|string $expression The expression to compile + * @param array $values An array of values + * + * @return mixed The result of the evaluation of the expression + */ + public function evaluate($expression, $values = []) + { + return $this->parse($expression, array_keys($values))->getNodes()->evaluate($this->functions, $values); + } + + /** + * Parses an expression. + * + * @param Expression|string $expression The expression to parse + * @param array $names An array of valid names + * + * @return ParsedExpression A ParsedExpression instance + */ + public function parse($expression, $names) + { + if ($expression instanceof ParsedExpression) { + return $expression; + } + + asort($names); + $cacheKeyItems = []; + + foreach ($names as $nameKey => $name) { + $cacheKeyItems[] = \is_int($nameKey) ? $name : $nameKey.':'.$name; + } + + $cacheItem = $this->cache->getItem(rawurlencode($expression.'//'.implode('|', $cacheKeyItems))); + + if (null === $parsedExpression = $cacheItem->get()) { + $nodes = $this->getParser()->parse($this->getLexer()->tokenize((string) $expression), $names); + $parsedExpression = new ParsedExpression((string) $expression, $nodes); + + $cacheItem->set($parsedExpression); + $this->cache->save($cacheItem); + } + + return $parsedExpression; + } + + /** + * Registers a function. + * + * @param string $name The function name + * @param callable $compiler A callable able to compile the function + * @param callable $evaluator A callable able to evaluate the function + * + * @throws \LogicException when registering a function after calling evaluate(), compile() or parse() + * + * @see ExpressionFunction + */ + public function register($name, callable $compiler, callable $evaluator) + { + if (null !== $this->parser) { + throw new \LogicException('Registering functions after calling evaluate(), compile() or parse() is not supported.'); + } + + $this->functions[$name] = ['compiler' => $compiler, 'evaluator' => $evaluator]; + } + + public function addFunction(ExpressionFunction $function) + { + $this->register($function->getName(), $function->getCompiler(), $function->getEvaluator()); + } + + public function registerProvider(ExpressionFunctionProviderInterface $provider) + { + foreach ($provider->getFunctions() as $function) { + $this->addFunction($function); + } + } + + protected function registerFunctions() + { + $this->addFunction(ExpressionFunction::fromPhp('constant')); + } + + private function getLexer() + { + if (null === $this->lexer) { + $this->lexer = new Lexer(); + } + + return $this->lexer; + } + + private function getParser() + { + if (null === $this->parser) { + $this->parser = new Parser($this->functions); + } + + return $this->parser; + } + + private function getCompiler() + { + if (null === $this->compiler) { + $this->compiler = new Compiler($this->functions); + } + + return $this->compiler->reset(); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/LICENSE b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/LICENSE new file mode 100644 index 0000000..9e936ec --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2020 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Lexer.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Lexer.php new file mode 100644 index 0000000..e534a56 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Lexer.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Lexes an expression. + * + * @author Fabien Potencier + */ +class Lexer +{ + /** + * Tokenizes an expression. + * + * @param string $expression The expression to tokenize + * + * @return TokenStream A token stream instance + * + * @throws SyntaxError + */ + public function tokenize($expression) + { + $expression = str_replace(["\r", "\n", "\t", "\v", "\f"], ' ', $expression); + $cursor = 0; + $tokens = []; + $brackets = []; + $end = \strlen($expression); + + while ($cursor < $end) { + if (' ' == $expression[$cursor]) { + ++$cursor; + + continue; + } + + if (preg_match('/[0-9]+(?:\.[0-9]+)?/A', $expression, $match, 0, $cursor)) { + // numbers + $number = (float) $match[0]; // floats + if (preg_match('/^[0-9]+$/', $match[0]) && $number <= PHP_INT_MAX) { + $number = (int) $match[0]; // integers lower than the maximum + } + $tokens[] = new Token(Token::NUMBER_TYPE, $number, $cursor + 1); + $cursor += \strlen($match[0]); + } elseif (false !== strpos('([{', $expression[$cursor])) { + // opening bracket + $brackets[] = [$expression[$cursor], $cursor]; + + $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1); + ++$cursor; + } elseif (false !== strpos(')]}', $expression[$cursor])) { + // closing bracket + if (empty($brackets)) { + throw new SyntaxError(sprintf('Unexpected "%s".', $expression[$cursor]), $cursor, $expression); + } + + list($expect, $cur) = array_pop($brackets); + if ($expression[$cursor] != strtr($expect, '([{', ')]}')) { + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $cur, $expression); + } + + $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1); + ++$cursor; + } elseif (preg_match('/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As', $expression, $match, 0, $cursor)) { + // strings + $tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1); + $cursor += \strlen($match[0]); + } elseif (preg_match('/(?<=^|[\s(])not in(?=[\s(])|\!\=\=|(?<=^|[\s(])not(?=[\s(])|(?<=^|[\s(])and(?=[\s(])|\=\=\=|\>\=|(?<=^|[\s(])or(?=[\s(])|\<\=|\*\*|\.\.|(?<=^|[\s(])in(?=[\s(])|&&|\|\||(?<=^|[\s(])matches|\=\=|\!\=|\*|~|%|\/|\>|\||\!|\^|&|\+|\<|\-/A', $expression, $match, 0, $cursor)) { + // operators + $tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1); + $cursor += \strlen($match[0]); + } elseif (false !== strpos('.,?:', $expression[$cursor])) { + // punctuation + $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1); + ++$cursor; + } elseif (preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $expression, $match, 0, $cursor)) { + // names + $tokens[] = new Token(Token::NAME_TYPE, $match[0], $cursor + 1); + $cursor += \strlen($match[0]); + } else { + // unlexable + throw new SyntaxError(sprintf('Unexpected character "%s".', $expression[$cursor]), $cursor, $expression); + } + } + + $tokens[] = new Token(Token::EOF_TYPE, null, $cursor + 1); + + if (!empty($brackets)) { + list($expect, $cur) = array_pop($brackets); + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $cur, $expression); + } + + return new TokenStream($tokens, $expression); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ArgumentsNode.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ArgumentsNode.php new file mode 100644 index 0000000..e9849a4 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ArgumentsNode.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class ArgumentsNode extends ArrayNode +{ + public function compile(Compiler $compiler) + { + $this->compileArguments($compiler, false); + } + + public function toArray() + { + $array = []; + + foreach ($this->getKeyValuePairs() as $pair) { + $array[] = $pair['value']; + $array[] = ', '; + } + array_pop($array); + + return $array; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ArrayNode.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ArrayNode.php new file mode 100644 index 0000000..921319a --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ArrayNode.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class ArrayNode extends Node +{ + protected $index; + + public function __construct() + { + $this->index = -1; + } + + public function addElement(Node $value, Node $key = null) + { + if (null === $key) { + $key = new ConstantNode(++$this->index); + } + + array_push($this->nodes, $key, $value); + } + + /** + * Compiles the node to PHP. + */ + public function compile(Compiler $compiler) + { + $compiler->raw('['); + $this->compileArguments($compiler); + $compiler->raw(']'); + } + + public function evaluate($functions, $values) + { + $result = []; + foreach ($this->getKeyValuePairs() as $pair) { + $result[$pair['key']->evaluate($functions, $values)] = $pair['value']->evaluate($functions, $values); + } + + return $result; + } + + public function toArray() + { + $value = []; + foreach ($this->getKeyValuePairs() as $pair) { + $value[$pair['key']->attributes['value']] = $pair['value']; + } + + $array = []; + + if ($this->isHash($value)) { + foreach ($value as $k => $v) { + $array[] = ', '; + $array[] = new ConstantNode($k); + $array[] = ': '; + $array[] = $v; + } + $array[0] = '{'; + $array[] = '}'; + } else { + foreach ($value as $v) { + $array[] = ', '; + $array[] = $v; + } + $array[0] = '['; + $array[] = ']'; + } + + return $array; + } + + protected function getKeyValuePairs() + { + $pairs = []; + foreach (array_chunk($this->nodes, 2) as $pair) { + $pairs[] = ['key' => $pair[0], 'value' => $pair[1]]; + } + + return $pairs; + } + + protected function compileArguments(Compiler $compiler, $withKeys = true) + { + $first = true; + foreach ($this->getKeyValuePairs() as $pair) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + if ($withKeys) { + $compiler + ->compile($pair['key']) + ->raw(' => ') + ; + } + + $compiler->compile($pair['value']); + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/BinaryNode.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/BinaryNode.php new file mode 100644 index 0000000..549161b --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/BinaryNode.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class BinaryNode extends Node +{ + private static $operators = [ + '~' => '.', + 'and' => '&&', + 'or' => '||', + ]; + + private static $functions = [ + '**' => 'pow', + '..' => 'range', + 'in' => 'in_array', + 'not in' => '!in_array', + ]; + + public function __construct($operator, Node $left, Node $right) + { + parent::__construct( + ['left' => $left, 'right' => $right], + ['operator' => $operator] + ); + } + + public function compile(Compiler $compiler) + { + $operator = $this->attributes['operator']; + + if ('matches' == $operator) { + $compiler + ->raw('preg_match(') + ->compile($this->nodes['right']) + ->raw(', ') + ->compile($this->nodes['left']) + ->raw(')') + ; + + return; + } + + if (isset(self::$functions[$operator])) { + $compiler + ->raw(sprintf('%s(', self::$functions[$operator])) + ->compile($this->nodes['left']) + ->raw(', ') + ->compile($this->nodes['right']) + ->raw(')') + ; + + return; + } + + if (isset(self::$operators[$operator])) { + $operator = self::$operators[$operator]; + } + + $compiler + ->raw('(') + ->compile($this->nodes['left']) + ->raw(' ') + ->raw($operator) + ->raw(' ') + ->compile($this->nodes['right']) + ->raw(')') + ; + } + + public function evaluate($functions, $values) + { + $operator = $this->attributes['operator']; + $left = $this->nodes['left']->evaluate($functions, $values); + + if (isset(self::$functions[$operator])) { + $right = $this->nodes['right']->evaluate($functions, $values); + + if ('not in' === $operator) { + return !\in_array($left, $right); + } + $f = self::$functions[$operator]; + + return $f($left, $right); + } + + switch ($operator) { + case 'or': + case '||': + return $left || $this->nodes['right']->evaluate($functions, $values); + case 'and': + case '&&': + return $left && $this->nodes['right']->evaluate($functions, $values); + } + + $right = $this->nodes['right']->evaluate($functions, $values); + + switch ($operator) { + case '|': + return $left | $right; + case '^': + return $left ^ $right; + case '&': + return $left & $right; + case '==': + return $left == $right; + case '===': + return $left === $right; + case '!=': + return $left != $right; + case '!==': + return $left !== $right; + case '<': + return $left < $right; + case '>': + return $left > $right; + case '>=': + return $left >= $right; + case '<=': + return $left <= $right; + case 'not in': + return !\in_array($left, $right); + case 'in': + return \in_array($left, $right); + case '+': + return $left + $right; + case '-': + return $left - $right; + case '~': + return $left.$right; + case '*': + return $left * $right; + case '/': + if (0 == $right) { + throw new \DivisionByZeroError('Division by zero.'); + } + + return $left / $right; + case '%': + if (0 == $right) { + throw new \DivisionByZeroError('Modulo by zero.'); + } + + return $left % $right; + case 'matches': + return preg_match($right, $left); + } + } + + public function toArray() + { + return ['(', $this->nodes['left'], ' '.$this->attributes['operator'].' ', $this->nodes['right'], ')']; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ConditionalNode.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ConditionalNode.php new file mode 100644 index 0000000..ca1b484 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ConditionalNode.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class ConditionalNode extends Node +{ + public function __construct(Node $expr1, Node $expr2, Node $expr3) + { + parent::__construct( + ['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3] + ); + } + + public function compile(Compiler $compiler) + { + $compiler + ->raw('((') + ->compile($this->nodes['expr1']) + ->raw(') ? (') + ->compile($this->nodes['expr2']) + ->raw(') : (') + ->compile($this->nodes['expr3']) + ->raw('))') + ; + } + + public function evaluate($functions, $values) + { + if ($this->nodes['expr1']->evaluate($functions, $values)) { + return $this->nodes['expr2']->evaluate($functions, $values); + } + + return $this->nodes['expr3']->evaluate($functions, $values); + } + + public function toArray() + { + return ['(', $this->nodes['expr1'], ' ? ', $this->nodes['expr2'], ' : ', $this->nodes['expr3'], ')']; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ConstantNode.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ConstantNode.php new file mode 100644 index 0000000..670e52e --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/ConstantNode.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class ConstantNode extends Node +{ + private $isIdentifier; + + public function __construct($value, $isIdentifier = false) + { + $this->isIdentifier = $isIdentifier; + parent::__construct( + [], + ['value' => $value] + ); + } + + public function compile(Compiler $compiler) + { + $compiler->repr($this->attributes['value']); + } + + public function evaluate($functions, $values) + { + return $this->attributes['value']; + } + + public function toArray() + { + $array = []; + $value = $this->attributes['value']; + + if ($this->isIdentifier) { + $array[] = $value; + } elseif (true === $value) { + $array[] = 'true'; + } elseif (false === $value) { + $array[] = 'false'; + } elseif (null === $value) { + $array[] = 'null'; + } elseif (is_numeric($value)) { + $array[] = $value; + } elseif (!\is_array($value)) { + $array[] = $this->dumpString($value); + } elseif ($this->isHash($value)) { + foreach ($value as $k => $v) { + $array[] = ', '; + $array[] = new self($k); + $array[] = ': '; + $array[] = new self($v); + } + $array[0] = '{'; + $array[] = '}'; + } else { + foreach ($value as $v) { + $array[] = ', '; + $array[] = new self($v); + } + $array[0] = '['; + $array[] = ']'; + } + + return $array; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/FunctionNode.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/FunctionNode.php new file mode 100644 index 0000000..90008d5 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/FunctionNode.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class FunctionNode extends Node +{ + public function __construct($name, Node $arguments) + { + parent::__construct( + ['arguments' => $arguments], + ['name' => $name] + ); + } + + public function compile(Compiler $compiler) + { + $arguments = []; + foreach ($this->nodes['arguments']->nodes as $node) { + $arguments[] = $compiler->subcompile($node); + } + + $function = $compiler->getFunction($this->attributes['name']); + + $compiler->raw(\call_user_func_array($function['compiler'], $arguments)); + } + + public function evaluate($functions, $values) + { + $arguments = [$values]; + foreach ($this->nodes['arguments']->nodes as $node) { + $arguments[] = $node->evaluate($functions, $values); + } + + return \call_user_func_array($functions[$this->attributes['name']]['evaluator'], $arguments); + } + + public function toArray() + { + $array = []; + $array[] = $this->attributes['name']; + + foreach ($this->nodes['arguments']->nodes as $node) { + $array[] = ', '; + $array[] = $node; + } + $array[1] = '('; + $array[] = ')'; + + return $array; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/GetAttrNode.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/GetAttrNode.php new file mode 100644 index 0000000..1cf414a --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/GetAttrNode.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class GetAttrNode extends Node +{ + const PROPERTY_CALL = 1; + const METHOD_CALL = 2; + const ARRAY_CALL = 3; + + public function __construct(Node $node, Node $attribute, ArrayNode $arguments, $type) + { + parent::__construct( + ['node' => $node, 'attribute' => $attribute, 'arguments' => $arguments], + ['type' => $type] + ); + } + + public function compile(Compiler $compiler) + { + switch ($this->attributes['type']) { + case self::PROPERTY_CALL: + $compiler + ->compile($this->nodes['node']) + ->raw('->') + ->raw($this->nodes['attribute']->attributes['value']) + ; + break; + + case self::METHOD_CALL: + $compiler + ->compile($this->nodes['node']) + ->raw('->') + ->raw($this->nodes['attribute']->attributes['value']) + ->raw('(') + ->compile($this->nodes['arguments']) + ->raw(')') + ; + break; + + case self::ARRAY_CALL: + $compiler + ->compile($this->nodes['node']) + ->raw('[') + ->compile($this->nodes['attribute'])->raw(']') + ; + break; + } + } + + public function evaluate($functions, $values) + { + switch ($this->attributes['type']) { + case self::PROPERTY_CALL: + $obj = $this->nodes['node']->evaluate($functions, $values); + if (!\is_object($obj)) { + throw new \RuntimeException('Unable to get a property on a non-object.'); + } + + $property = $this->nodes['attribute']->attributes['value']; + + return $obj->$property; + + case self::METHOD_CALL: + $obj = $this->nodes['node']->evaluate($functions, $values); + if (!\is_object($obj)) { + throw new \RuntimeException('Unable to get a property on a non-object.'); + } + if (!\is_callable($toCall = [$obj, $this->nodes['attribute']->attributes['value']])) { + throw new \RuntimeException(sprintf('Unable to call method "%s" of object "%s".', $this->nodes['attribute']->attributes['value'], \get_class($obj))); + } + + return \call_user_func_array($toCall, $this->nodes['arguments']->evaluate($functions, $values)); + + case self::ARRAY_CALL: + $array = $this->nodes['node']->evaluate($functions, $values); + if (!\is_array($array) && !$array instanceof \ArrayAccess) { + throw new \RuntimeException('Unable to get an item on a non-array.'); + } + + return $array[$this->nodes['attribute']->evaluate($functions, $values)]; + } + } + + public function toArray() + { + switch ($this->attributes['type']) { + case self::PROPERTY_CALL: + return [$this->nodes['node'], '.', $this->nodes['attribute']]; + + case self::METHOD_CALL: + return [$this->nodes['node'], '.', $this->nodes['attribute'], '(', $this->nodes['arguments'], ')']; + + case self::ARRAY_CALL: + return [$this->nodes['node'], '[', $this->nodes['attribute'], ']']; + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/NameNode.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/NameNode.php new file mode 100644 index 0000000..1c9c277 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/NameNode.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class NameNode extends Node +{ + public function __construct($name) + { + parent::__construct( + [], + ['name' => $name] + ); + } + + public function compile(Compiler $compiler) + { + $compiler->raw('$'.$this->attributes['name']); + } + + public function evaluate($functions, $values) + { + return $values[$this->attributes['name']]; + } + + public function toArray() + { + return [$this->attributes['name']]; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/Node.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/Node.php new file mode 100644 index 0000000..9504590 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/Node.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * Represents a node in the AST. + * + * @author Fabien Potencier + */ +class Node +{ + public $nodes = []; + public $attributes = []; + + /** + * @param array $nodes An array of nodes + * @param array $attributes An array of attributes + */ + public function __construct(array $nodes = [], array $attributes = []) + { + $this->nodes = $nodes; + $this->attributes = $attributes; + } + + public function __toString() + { + $attributes = []; + foreach ($this->attributes as $name => $value) { + $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); + } + + $repr = [str_replace('Symfony\Component\ExpressionLanguage\Node\\', '', static::class).'('.implode(', ', $attributes)]; + + if (\count($this->nodes)) { + foreach ($this->nodes as $node) { + foreach (explode("\n", (string) $node) as $line) { + $repr[] = ' '.$line; + } + } + + $repr[] = ')'; + } else { + $repr[0] .= ')'; + } + + return implode("\n", $repr); + } + + public function compile(Compiler $compiler) + { + foreach ($this->nodes as $node) { + $node->compile($compiler); + } + } + + public function evaluate($functions, $values) + { + $results = []; + foreach ($this->nodes as $node) { + $results[] = $node->evaluate($functions, $values); + } + + return $results; + } + + public function toArray() + { + throw new \BadMethodCallException(sprintf('Dumping a "%s" instance is not supported yet.', static::class)); + } + + public function dump() + { + $dump = ''; + + foreach ($this->toArray() as $v) { + $dump .= is_scalar($v) ? $v : $v->dump(); + } + + return $dump; + } + + protected function dumpString($value) + { + return sprintf('"%s"', addcslashes($value, "\0\t\"\\")); + } + + protected function isHash(array $value) + { + $expectedKey = 0; + + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/UnaryNode.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/UnaryNode.php new file mode 100644 index 0000000..497b7fe --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Node/UnaryNode.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class UnaryNode extends Node +{ + private static $operators = [ + '!' => '!', + 'not' => '!', + '+' => '+', + '-' => '-', + ]; + + public function __construct($operator, Node $node) + { + parent::__construct( + ['node' => $node], + ['operator' => $operator] + ); + } + + public function compile(Compiler $compiler) + { + $compiler + ->raw('(') + ->raw(self::$operators[$this->attributes['operator']]) + ->compile($this->nodes['node']) + ->raw(')') + ; + } + + public function evaluate($functions, $values) + { + $value = $this->nodes['node']->evaluate($functions, $values); + switch ($this->attributes['operator']) { + case 'not': + case '!': + return !$value; + case '-': + return -$value; + } + + return $value; + } + + public function toArray() + { + return ['(', $this->attributes['operator'].' ', $this->nodes['node'], ')']; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParsedExpression.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParsedExpression.php new file mode 100644 index 0000000..a5603fc --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParsedExpression.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +use Symfony\Component\ExpressionLanguage\Node\Node; + +/** + * Represents an already parsed expression. + * + * @author Fabien Potencier + */ +class ParsedExpression extends Expression +{ + private $nodes; + + /** + * @param string $expression An expression + * @param Node $nodes A Node representing the expression + */ + public function __construct($expression, Node $nodes) + { + parent::__construct($expression); + + $this->nodes = $nodes; + } + + public function getNodes() + { + return $this->nodes; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Parser.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Parser.php new file mode 100644 index 0000000..bfc2439 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Parser.php @@ -0,0 +1,380 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Parsers a token stream. + * + * This parser implements a "Precedence climbing" algorithm. + * + * @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm + * @see http://en.wikipedia.org/wiki/Operator-precedence_parser + * + * @author Fabien Potencier + */ +class Parser +{ + const OPERATOR_LEFT = 1; + const OPERATOR_RIGHT = 2; + + private $stream; + private $unaryOperators; + private $binaryOperators; + private $functions; + private $names; + + public function __construct(array $functions) + { + $this->functions = $functions; + + $this->unaryOperators = [ + 'not' => ['precedence' => 50], + '!' => ['precedence' => 50], + '-' => ['precedence' => 500], + '+' => ['precedence' => 500], + ]; + $this->binaryOperators = [ + 'or' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT], + '||' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT], + 'and' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT], + '&&' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT], + '|' => ['precedence' => 16, 'associativity' => self::OPERATOR_LEFT], + '^' => ['precedence' => 17, 'associativity' => self::OPERATOR_LEFT], + '&' => ['precedence' => 18, 'associativity' => self::OPERATOR_LEFT], + '==' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '===' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '!=' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '!==' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '<' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '>' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '>=' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '<=' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + 'not in' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + 'in' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + 'matches' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '..' => ['precedence' => 25, 'associativity' => self::OPERATOR_LEFT], + '+' => ['precedence' => 30, 'associativity' => self::OPERATOR_LEFT], + '-' => ['precedence' => 30, 'associativity' => self::OPERATOR_LEFT], + '~' => ['precedence' => 40, 'associativity' => self::OPERATOR_LEFT], + '*' => ['precedence' => 60, 'associativity' => self::OPERATOR_LEFT], + '/' => ['precedence' => 60, 'associativity' => self::OPERATOR_LEFT], + '%' => ['precedence' => 60, 'associativity' => self::OPERATOR_LEFT], + '**' => ['precedence' => 200, 'associativity' => self::OPERATOR_RIGHT], + ]; + } + + /** + * Converts a token stream to a node tree. + * + * The valid names is an array where the values + * are the names that the user can use in an expression. + * + * If the variable name in the compiled PHP code must be + * different, define it as the key. + * + * For instance, ['this' => 'container'] means that the + * variable 'container' can be used in the expression + * but the compiled code will use 'this'. + * + * @param TokenStream $stream A token stream instance + * @param array $names An array of valid names + * + * @return Node\Node A node tree + * + * @throws SyntaxError + */ + public function parse(TokenStream $stream, $names = []) + { + $this->stream = $stream; + $this->names = $names; + + $node = $this->parseExpression(); + if (!$stream->isEOF()) { + throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', $stream->current->type, $stream->current->value), $stream->current->cursor, $stream->getExpression()); + } + + return $node; + } + + public function parseExpression($precedence = 0) + { + $expr = $this->getPrimary(); + $token = $this->stream->current; + while ($token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->value]) && $this->binaryOperators[$token->value]['precedence'] >= $precedence) { + $op = $this->binaryOperators[$token->value]; + $this->stream->next(); + + $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); + $expr = new Node\BinaryNode($token->value, $expr, $expr1); + + $token = $this->stream->current; + } + + if (0 === $precedence) { + return $this->parseConditionalExpression($expr); + } + + return $expr; + } + + protected function getPrimary() + { + $token = $this->stream->current; + + if ($token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->value])) { + $operator = $this->unaryOperators[$token->value]; + $this->stream->next(); + $expr = $this->parseExpression($operator['precedence']); + + return $this->parsePostfixExpression(new Node\UnaryNode($token->value, $expr)); + } + + if ($token->test(Token::PUNCTUATION_TYPE, '(')) { + $this->stream->next(); + $expr = $this->parseExpression(); + $this->stream->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); + + return $this->parsePostfixExpression($expr); + } + + return $this->parsePrimaryExpression(); + } + + protected function parseConditionalExpression($expr) + { + while ($this->stream->current->test(Token::PUNCTUATION_TYPE, '?')) { + $this->stream->next(); + if (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ':')) { + $expr2 = $this->parseExpression(); + if ($this->stream->current->test(Token::PUNCTUATION_TYPE, ':')) { + $this->stream->next(); + $expr3 = $this->parseExpression(); + } else { + $expr3 = new Node\ConstantNode(null); + } + } else { + $this->stream->next(); + $expr2 = $expr; + $expr3 = $this->parseExpression(); + } + + $expr = new Node\ConditionalNode($expr, $expr2, $expr3); + } + + return $expr; + } + + public function parsePrimaryExpression() + { + $token = $this->stream->current; + switch ($token->type) { + case Token::NAME_TYPE: + $this->stream->next(); + switch ($token->value) { + case 'true': + case 'TRUE': + return new Node\ConstantNode(true); + + case 'false': + case 'FALSE': + return new Node\ConstantNode(false); + + case 'null': + case 'NULL': + return new Node\ConstantNode(null); + + default: + if ('(' === $this->stream->current->value) { + if (false === isset($this->functions[$token->value])) { + throw new SyntaxError(sprintf('The function "%s" does not exist.', $token->value), $token->cursor, $this->stream->getExpression(), $token->value, array_keys($this->functions)); + } + + $node = new Node\FunctionNode($token->value, $this->parseArguments()); + } else { + if (!\in_array($token->value, $this->names, true)) { + throw new SyntaxError(sprintf('Variable "%s" is not valid.', $token->value), $token->cursor, $this->stream->getExpression(), $token->value, $this->names); + } + + // is the name used in the compiled code different + // from the name used in the expression? + if (\is_int($name = array_search($token->value, $this->names))) { + $name = $token->value; + } + + $node = new Node\NameNode($name); + } + } + break; + + case Token::NUMBER_TYPE: + case Token::STRING_TYPE: + $this->stream->next(); + + return new Node\ConstantNode($token->value); + + default: + if ($token->test(Token::PUNCTUATION_TYPE, '[')) { + $node = $this->parseArrayExpression(); + } elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) { + $node = $this->parseHashExpression(); + } else { + throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', $token->type, $token->value), $token->cursor, $this->stream->getExpression()); + } + } + + return $this->parsePostfixExpression($node); + } + + public function parseArrayExpression() + { + $this->stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); + + $node = new Node\ArrayNode(); + $first = true; + while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ']')) { + if (!$first) { + $this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); + + // trailing ,? + if ($this->stream->current->test(Token::PUNCTUATION_TYPE, ']')) { + break; + } + } + $first = false; + + $node->addElement($this->parseExpression()); + } + $this->stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); + + return $node; + } + + public function parseHashExpression() + { + $this->stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); + + $node = new Node\ArrayNode(); + $first = true; + while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, '}')) { + if (!$first) { + $this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); + + // trailing ,? + if ($this->stream->current->test(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 ($this->stream->current->test(Token::STRING_TYPE) || $this->stream->current->test(Token::NAME_TYPE) || $this->stream->current->test(Token::NUMBER_TYPE)) { + $key = new Node\ConstantNode($this->stream->current->value); + $this->stream->next(); + } elseif ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) { + $key = $this->parseExpression(); + } else { + $current = $this->stream->current; + + throw new SyntaxError(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".', $current->type, $current->value), $current->cursor, $this->stream->getExpression()); + } + + $this->stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); + $value = $this->parseExpression(); + + $node->addElement($value, $key); + } + $this->stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); + + return $node; + } + + public function parsePostfixExpression($node) + { + $token = $this->stream->current; + while (Token::PUNCTUATION_TYPE == $token->type) { + if ('.' === $token->value) { + $this->stream->next(); + $token = $this->stream->current; + $this->stream->next(); + + if ( + Token::NAME_TYPE !== $token->type + && + // Operators like "not" and "matches" are valid method or property names, + // + // In other words, besides NAME_TYPE, OPERATOR_TYPE could also be parsed as a property or method. + // This is because operators are processed by the lexer prior to names. So "not" in "foo.not()" or "matches" in "foo.matches" will be recognized as an operator first. + // But in fact, "not" and "matches" in such expressions shall be parsed as method or property names. + // + // And this ONLY works if the operator consists of valid characters for a property or method name. + // + // Other types, such as STRING_TYPE and NUMBER_TYPE, can't be parsed as property nor method names. + // + // As a result, if $token is NOT an operator OR $token->value is NOT a valid property or method name, an exception shall be thrown. + (Token::OPERATOR_TYPE !== $token->type || !preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $token->value)) + ) { + throw new SyntaxError('Expected name.', $token->cursor, $this->stream->getExpression()); + } + + $arg = new Node\ConstantNode($token->value, true); + + $arguments = new Node\ArgumentsNode(); + if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) { + $type = Node\GetAttrNode::METHOD_CALL; + foreach ($this->parseArguments()->nodes as $n) { + $arguments->addElement($n); + } + } else { + $type = Node\GetAttrNode::PROPERTY_CALL; + } + + $node = new Node\GetAttrNode($node, $arg, $arguments, $type); + } elseif ('[' === $token->value) { + $this->stream->next(); + $arg = $this->parseExpression(); + $this->stream->expect(Token::PUNCTUATION_TYPE, ']'); + + $node = new Node\GetAttrNode($node, $arg, new Node\ArgumentsNode(), Node\GetAttrNode::ARRAY_CALL); + } else { + break; + } + + $token = $this->stream->current; + } + + return $node; + } + + /** + * Parses arguments. + */ + public function parseArguments() + { + $args = []; + $this->stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); + while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ')')) { + if (!empty($args)) { + $this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); + } + + $args[] = $this->parseExpression(); + } + $this->stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); + + return new Node\Node($args); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParserCache/ArrayParserCache.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParserCache/ArrayParserCache.php new file mode 100644 index 0000000..8f9d9f4 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParserCache/ArrayParserCache.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\ParserCache; + +@trigger_error('The '.__NAMESPACE__.'\ArrayParserCache class is deprecated since Symfony 3.2 and will be removed in 4.0. Use the Symfony\Component\Cache\Adapter\ArrayAdapter class instead.', E_USER_DEPRECATED); + +use Symfony\Component\ExpressionLanguage\ParsedExpression; + +/** + * @author Adrien Brault + * + * @deprecated ArrayParserCache class is deprecated since version 3.2 and will be removed in 4.0. Use the Symfony\Component\Cache\Adapter\ArrayAdapter class instead. + */ +class ArrayParserCache implements ParserCacheInterface +{ + private $cache = []; + + /** + * {@inheritdoc} + */ + public function fetch($key) + { + return isset($this->cache[$key]) ? $this->cache[$key] : null; + } + + /** + * {@inheritdoc} + */ + public function save($key, ParsedExpression $expression) + { + $this->cache[$key] = $expression; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParserCache/ParserCacheAdapter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParserCache/ParserCacheAdapter.php new file mode 100644 index 0000000..b9b448d --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParserCache/ParserCacheAdapter.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\ParserCache; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; + +/** + * @author Alexandre GESLIN + * + * @internal and will be removed in Symfony 4.0. + */ +class ParserCacheAdapter implements CacheItemPoolInterface +{ + private $pool; + private $createCacheItem; + + public function __construct(ParserCacheInterface $pool) + { + $this->pool = $pool; + + $this->createCacheItem = \Closure::bind( + static function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + + return $item; + }, + null, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $value = $this->pool->fetch($key); + $f = $this->createCacheItem; + + return $f($key, $value, null !== $value); + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + $this->pool->save($item->getKey(), $item->get()); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + */ + public function commit() + { + throw new \BadMethodCallException('Not implemented.'); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParserCache/ParserCacheInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParserCache/ParserCacheInterface.php new file mode 100644 index 0000000..ed66b21 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/ParserCache/ParserCacheInterface.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\ParserCache; + +@trigger_error('The '.__NAMESPACE__.'\ParserCacheInterface interface is deprecated since Symfony 3.2 and will be removed in 4.0. Use Psr\Cache\CacheItemPoolInterface instead.', E_USER_DEPRECATED); + +use Symfony\Component\ExpressionLanguage\ParsedExpression; + +/** + * @author Adrien Brault + * + * @deprecated since version 3.2, to be removed in 4.0. Use Psr\Cache\CacheItemPoolInterface instead. + */ +interface ParserCacheInterface +{ + /** + * Saves an expression in the cache. + * + * @param string $key The cache key + * @param ParsedExpression $expression A ParsedExpression instance to store in the cache + */ + public function save($key, ParsedExpression $expression); + + /** + * Fetches an expression from the cache. + * + * @param string $key The cache key + * + * @return ParsedExpression|null + */ + public function fetch($key); +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/README.md b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/README.md new file mode 100644 index 0000000..08b310d --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/README.md @@ -0,0 +1,15 @@ +ExpressionLanguage Component +============================ + +The ExpressionLanguage component provides an engine that can compile and +evaluate expressions. An expression is a one-liner that returns a value +(mostly, but not limited to, Booleans). + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/expression_language/introduction.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Resources/bin/generate_operator_regex.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Resources/bin/generate_operator_regex.php new file mode 100644 index 0000000..c86e962 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Resources/bin/generate_operator_regex.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$operators = ['not', '!', 'or', '||', '&&', 'and', '|', '^', '&', '==', '===', '!=', '!==', '<', '>', '>=', '<=', 'not in', 'in', '..', '+', '-', '~', '*', '/', '%', 'matches', '**']; +$operators = array_combine($operators, array_map('strlen', $operators)); +arsort($operators); + +$regex = []; +foreach ($operators as $operator => $length) { + // Collisions of character operators: + // - an operator that begins with a character must have a space or a parenthesis before or starting at the beginning of a string + // - an operator that ends with a character must be followed by a whitespace or a parenthesis + $regex[] = + (ctype_alpha($operator[0]) ? '(?<=^|[\s(])' : '') + .preg_quote($operator, '/') + .(ctype_alpha($operator[$length - 1]) ? '(?=[\s(])' : ''); +} + +echo '/'.implode('|', $regex).'/A'; diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/SerializedParsedExpression.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/SerializedParsedExpression.php new file mode 100644 index 0000000..dd763f7 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/SerializedParsedExpression.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents an already parsed expression. + * + * @author Fabien Potencier + */ +class SerializedParsedExpression extends ParsedExpression +{ + private $nodes; + + /** + * @param string $expression An expression + * @param string $nodes The serialized nodes for the expression + */ + public function __construct($expression, $nodes) + { + $this->expression = (string) $expression; + $this->nodes = $nodes; + } + + public function getNodes() + { + return unserialize($this->nodes); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/SyntaxError.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/SyntaxError.php new file mode 100644 index 0000000..3340042 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/SyntaxError.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +class SyntaxError extends \LogicException +{ + public function __construct($message, $cursor = 0, $expression = '', $subject = null, array $proposals = null) + { + $message = sprintf('%s around position %d', rtrim($message, '.'), $cursor); + if ($expression) { + $message = sprintf('%s for expression `%s`', $message, $expression); + } + $message .= '.'; + + if (null !== $subject && null !== $proposals) { + $minScore = INF; + foreach ($proposals as $proposal) { + $distance = levenshtein($subject, $proposal); + if ($distance < $minScore) { + $guess = $proposal; + $minScore = $distance; + } + } + + if (isset($guess) && $minScore < 3) { + $message .= sprintf(' Did you mean "%s"?', $guess); + } + } + + parent::__construct($message); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ExpressionFunctionTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ExpressionFunctionTest.php new file mode 100644 index 0000000..d7e23cb --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ExpressionFunctionTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ExpressionLanguage\ExpressionFunction; + +/** + * Tests ExpressionFunction. + * + * @author Dany Maillard + */ +class ExpressionFunctionTest extends TestCase +{ + public function testFunctionDoesNotExist() + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('PHP function "fn_does_not_exist" does not exist.'); + ExpressionFunction::fromPhp('fn_does_not_exist'); + } + + public function testFunctionNamespaced() + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('An expression function name must be defined when PHP function "Symfony\Component\ExpressionLanguage\Tests\fn_namespaced" is namespaced.'); + ExpressionFunction::fromPhp('Symfony\Component\ExpressionLanguage\Tests\fn_namespaced'); + } +} + +function fn_namespaced() +{ +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ExpressionLanguageTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ExpressionLanguageTest.php new file mode 100644 index 0000000..6c7a802 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ExpressionLanguageTest.php @@ -0,0 +1,308 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ExpressionLanguage\ExpressionFunction; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\ParsedExpression; +use Symfony\Component\ExpressionLanguage\Tests\Fixtures\TestProvider; + +class ExpressionLanguageTest extends TestCase +{ + public function testCachedParse() + { + $cacheMock = $this->getMockBuilder('Psr\Cache\CacheItemPoolInterface')->getMock(); + $cacheItemMock = $this->getMockBuilder('Psr\Cache\CacheItemInterface')->getMock(); + $savedParsedExpression = null; + $expressionLanguage = new ExpressionLanguage($cacheMock); + + $cacheMock + ->expects($this->exactly(2)) + ->method('getItem') + ->with('1%20%2B%201%2F%2F') + ->willReturn($cacheItemMock) + ; + + $cacheItemMock + ->expects($this->exactly(2)) + ->method('get') + ->willReturnCallback(function () use (&$savedParsedExpression) { + return $savedParsedExpression; + }) + ; + + $cacheItemMock + ->expects($this->exactly(1)) + ->method('set') + ->with($this->isInstanceOf(ParsedExpression::class)) + ->willReturnCallback(function ($parsedExpression) use (&$savedParsedExpression) { + $savedParsedExpression = $parsedExpression; + }) + ; + + $cacheMock + ->expects($this->exactly(1)) + ->method('save') + ->with($cacheItemMock) + ; + + $parsedExpression = $expressionLanguage->parse('1 + 1', []); + $this->assertSame($savedParsedExpression, $parsedExpression); + + $parsedExpression = $expressionLanguage->parse('1 + 1', []); + $this->assertSame($savedParsedExpression, $parsedExpression); + } + + /** + * @group legacy + */ + public function testCachedParseWithDeprecatedParserCacheInterface() + { + $cacheMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock(); + + $savedParsedExpression = null; + $expressionLanguage = new ExpressionLanguage($cacheMock); + + $cacheMock + ->expects($this->exactly(1)) + ->method('fetch') + ->with('1%20%2B%201%2F%2F') + ->willReturn($savedParsedExpression) + ; + + $cacheMock + ->expects($this->exactly(1)) + ->method('save') + ->with('1%20%2B%201%2F%2F', $this->isInstanceOf(ParsedExpression::class)) + ->willReturnCallback(function ($key, $expression) use (&$savedParsedExpression) { + $savedParsedExpression = $expression; + }) + ; + + $parsedExpression = $expressionLanguage->parse('1 + 1', []); + $this->assertSame($savedParsedExpression, $parsedExpression); + } + + public function testWrongCacheImplementation() + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Cache argument has to implement "Psr\Cache\CacheItemPoolInterface".'); + $cacheMock = $this->getMockBuilder('Psr\Cache\CacheItemSpoolInterface')->getMock(); + new ExpressionLanguage($cacheMock); + } + + public function testConstantFunction() + { + $expressionLanguage = new ExpressionLanguage(); + $this->assertEquals(PHP_VERSION, $expressionLanguage->evaluate('constant("PHP_VERSION")')); + + $expressionLanguage = new ExpressionLanguage(); + $this->assertEquals('\constant("PHP_VERSION")', $expressionLanguage->compile('constant("PHP_VERSION")')); + } + + public function testProviders() + { + $expressionLanguage = new ExpressionLanguage(null, [new TestProvider()]); + $this->assertEquals('foo', $expressionLanguage->evaluate('identity("foo")')); + $this->assertEquals('"foo"', $expressionLanguage->compile('identity("foo")')); + $this->assertEquals('FOO', $expressionLanguage->evaluate('strtoupper("foo")')); + $this->assertEquals('\strtoupper("foo")', $expressionLanguage->compile('strtoupper("foo")')); + $this->assertEquals('foo', $expressionLanguage->evaluate('strtolower("FOO")')); + $this->assertEquals('\strtolower("FOO")', $expressionLanguage->compile('strtolower("FOO")')); + $this->assertTrue($expressionLanguage->evaluate('fn_namespaced()')); + $this->assertEquals('\Symfony\Component\ExpressionLanguage\Tests\Fixtures\fn_namespaced()', $expressionLanguage->compile('fn_namespaced()')); + } + + /** + * @dataProvider shortCircuitProviderEvaluate + */ + public function testShortCircuitOperatorsEvaluate($expression, array $values, $expected) + { + $expressionLanguage = new ExpressionLanguage(); + $this->assertEquals($expected, $expressionLanguage->evaluate($expression, $values)); + } + + /** + * @dataProvider shortCircuitProviderCompile + */ + public function testShortCircuitOperatorsCompile($expression, array $names, $expected) + { + $result = null; + $expressionLanguage = new ExpressionLanguage(); + eval(sprintf('$result = %s;', $expressionLanguage->compile($expression, $names))); + $this->assertSame($expected, $result); + } + + public function testParseThrowsInsteadOfNotice() + { + $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError'); + $this->expectExceptionMessage('Unexpected end of expression around position 6 for expression `node.`.'); + $expressionLanguage = new ExpressionLanguage(); + $expressionLanguage->parse('node.', ['node']); + } + + public function shortCircuitProviderEvaluate() + { + $object = $this->getMockBuilder('stdClass')->setMethods(['foo'])->getMock(); + $object->expects($this->never())->method('foo'); + + return [ + ['false and object.foo()', ['object' => $object], false], + ['false && object.foo()', ['object' => $object], false], + ['true || object.foo()', ['object' => $object], true], + ['true or object.foo()', ['object' => $object], true], + ]; + } + + public function shortCircuitProviderCompile() + { + return [ + ['false and foo', ['foo' => 'foo'], false], + ['false && foo', ['foo' => 'foo'], false], + ['true || foo', ['foo' => 'foo'], true], + ['true or foo', ['foo' => 'foo'], true], + ]; + } + + public function testCachingForOverriddenVariableNames() + { + $expressionLanguage = new ExpressionLanguage(); + $expression = 'a + b'; + $expressionLanguage->evaluate($expression, ['a' => 1, 'b' => 1]); + $result = $expressionLanguage->compile($expression, ['a', 'B' => 'b']); + $this->assertSame('($a + $B)', $result); + } + + public function testStrictEquality() + { + $expressionLanguage = new ExpressionLanguage(); + $expression = '123 === a'; + $result = $expressionLanguage->compile($expression, ['a']); + $this->assertSame('(123 === $a)', $result); + } + + public function testCachingWithDifferentNamesOrder() + { + $cacheMock = $this->getMockBuilder('Psr\Cache\CacheItemPoolInterface')->getMock(); + $cacheItemMock = $this->getMockBuilder('Psr\Cache\CacheItemInterface')->getMock(); + $expressionLanguage = new ExpressionLanguage($cacheMock); + $savedParsedExpression = null; + + $cacheMock + ->expects($this->exactly(2)) + ->method('getItem') + ->with('a%20%2B%20b%2F%2Fa%7CB%3Ab') + ->willReturn($cacheItemMock) + ; + + $cacheItemMock + ->expects($this->exactly(2)) + ->method('get') + ->willReturnCallback(function () use (&$savedParsedExpression) { + return $savedParsedExpression; + }) + ; + + $cacheItemMock + ->expects($this->exactly(1)) + ->method('set') + ->with($this->isInstanceOf(ParsedExpression::class)) + ->willReturnCallback(function ($parsedExpression) use (&$savedParsedExpression) { + $savedParsedExpression = $parsedExpression; + }) + ; + + $cacheMock + ->expects($this->exactly(1)) + ->method('save') + ->with($cacheItemMock) + ; + + $expression = 'a + b'; + $expressionLanguage->compile($expression, ['a', 'B' => 'b']); + $expressionLanguage->compile($expression, ['B' => 'b', 'a']); + } + + public function testOperatorCollisions() + { + $expressionLanguage = new ExpressionLanguage(); + $expression = 'foo.not in [bar]'; + $compiled = $expressionLanguage->compile($expression, ['foo', 'bar']); + $this->assertSame('in_array($foo->not, [0 => $bar])', $compiled); + + $result = $expressionLanguage->evaluate($expression, ['foo' => (object) ['not' => 'test'], 'bar' => 'test']); + $this->assertTrue($result); + } + + /** + * @dataProvider getRegisterCallbacks + */ + public function testRegisterAfterParse($registerCallback) + { + $this->expectException('LogicException'); + $el = new ExpressionLanguage(); + $el->parse('1 + 1', []); + $registerCallback($el); + } + + /** + * @dataProvider getRegisterCallbacks + */ + public function testRegisterAfterEval($registerCallback) + { + $this->expectException('LogicException'); + $el = new ExpressionLanguage(); + $el->evaluate('1 + 1'); + $registerCallback($el); + } + + public function testCallBadCallable() + { + $this->expectException('RuntimeException'); + $this->expectExceptionMessageRegExp('/Unable to call method "\w+" of object "\w+"./'); + $el = new ExpressionLanguage(); + $el->evaluate('foo.myfunction()', ['foo' => new \stdClass()]); + } + + /** + * @dataProvider getRegisterCallbacks + */ + public function testRegisterAfterCompile($registerCallback) + { + $this->expectException('LogicException'); + $el = new ExpressionLanguage(); + $el->compile('1 + 1'); + $registerCallback($el); + } + + public function getRegisterCallbacks() + { + return [ + [ + function (ExpressionLanguage $el) { + $el->register('fn', function () {}, function () {}); + }, + ], + [ + function (ExpressionLanguage $el) { + $el->addFunction(new ExpressionFunction('fn', function () {}, function () {})); + }, + ], + [ + function (ExpressionLanguage $el) { + $el->registerProvider(new TestProvider()); + }, + ], + ]; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ExpressionTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ExpressionTest.php new file mode 100644 index 0000000..052ef22 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ExpressionTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ExpressionLanguage\Expression; + +class ExpressionTest extends TestCase +{ + public function testSerialization() + { + $expression = new Expression('kernel.boot()'); + + $serializedExpression = serialize($expression); + $unserializedExpression = unserialize($serializedExpression); + + $this->assertEquals($expression, $unserializedExpression); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Fixtures/TestProvider.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Fixtures/TestProvider.php new file mode 100644 index 0000000..8405d4f --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Fixtures/TestProvider.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests\Fixtures; + +use Symfony\Component\ExpressionLanguage\ExpressionFunction; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Component\ExpressionLanguage\ExpressionPhpFunction; + +class TestProvider implements ExpressionFunctionProviderInterface +{ + public function getFunctions() + { + return [ + new ExpressionFunction('identity', function ($input) { + return $input; + }, function (array $values, $input) { + return $input; + }), + + ExpressionFunction::fromPhp('strtoupper'), + + ExpressionFunction::fromPhp('\strtolower'), + + ExpressionFunction::fromPhp('Symfony\Component\ExpressionLanguage\Tests\Fixtures\fn_namespaced', 'fn_namespaced'), + ]; + } +} + +function fn_namespaced() +{ + return true; +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/LexerTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/LexerTest.php new file mode 100644 index 0000000..6c3d1a7 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/LexerTest.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ExpressionLanguage\Lexer; +use Symfony\Component\ExpressionLanguage\Token; +use Symfony\Component\ExpressionLanguage\TokenStream; + +class LexerTest extends TestCase +{ + /** + * @var Lexer + */ + private $lexer; + + protected function setUp() + { + $this->lexer = new Lexer(); + } + + /** + * @dataProvider getTokenizeData + */ + public function testTokenize($tokens, $expression) + { + $tokens[] = new Token('end of expression', null, \strlen($expression) + 1); + $this->assertEquals(new TokenStream($tokens, $expression), $this->lexer->tokenize($expression)); + } + + public function testTokenizeThrowsErrorWithMessage() + { + $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError'); + $this->expectExceptionMessage('Unexpected character "\'" around position 33 for expression `service(faulty.expression.example\').dummyMethod()`.'); + $expression = "service(faulty.expression.example').dummyMethod()"; + $this->lexer->tokenize($expression); + } + + public function testTokenizeThrowsErrorOnUnclosedBrace() + { + $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError'); + $this->expectExceptionMessage('Unclosed "(" around position 7 for expression `service(unclosed.expression.dummyMethod()`.'); + $expression = 'service(unclosed.expression.dummyMethod()'; + $this->lexer->tokenize($expression); + } + + public function getTokenizeData() + { + return [ + [ + [new Token('name', 'a', 3)], + ' a ', + ], + [ + [new Token('name', 'a', 1)], + 'a', + ], + [ + [new Token('string', 'foo', 1)], + '"foo"', + ], + [ + [new Token('number', '3', 1)], + '3', + ], + [ + [new Token('operator', '+', 1)], + '+', + ], + [ + [new Token('punctuation', '.', 1)], + '.', + ], + [ + [ + new Token('punctuation', '(', 1), + new Token('number', '3', 2), + new Token('operator', '+', 4), + new Token('number', '5', 6), + new Token('punctuation', ')', 7), + new Token('operator', '~', 9), + new Token('name', 'foo', 11), + new Token('punctuation', '(', 14), + new Token('string', 'bar', 15), + new Token('punctuation', ')', 20), + new Token('punctuation', '.', 21), + new Token('name', 'baz', 22), + new Token('punctuation', '[', 25), + new Token('number', '4', 26), + new Token('punctuation', ']', 27), + ], + '(3 + 5) ~ foo("bar").baz[4]', + ], + [ + [new Token('operator', '..', 1)], + '..', + ], + [ + [new Token('string', '#foo', 1)], + "'#foo'", + ], + [ + [new Token('string', '#foo', 1)], + '"#foo"', + ], + [ + [ + new Token('name', 'foo', 1), + new Token('punctuation', '.', 4), + new Token('name', 'not', 5), + new Token('operator', 'in', 9), + new Token('punctuation', '[', 12), + new Token('name', 'bar', 13), + new Token('punctuation', ']', 16), + ], + 'foo.not in [bar]', + ], + ]; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/AbstractNodeTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/AbstractNodeTest.php new file mode 100644 index 0000000..2975037 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/AbstractNodeTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ExpressionLanguage\Compiler; + +abstract class AbstractNodeTest extends TestCase +{ + /** + * @dataProvider getEvaluateData + */ + public function testEvaluate($expected, $node, $variables = [], $functions = []) + { + $this->assertSame($expected, $node->evaluate($functions, $variables)); + } + + abstract public function getEvaluateData(); + + /** + * @dataProvider getCompileData + */ + public function testCompile($expected, $node, $functions = []) + { + $compiler = new Compiler($functions); + $node->compile($compiler); + $this->assertSame($expected, $compiler->getSource()); + } + + abstract public function getCompileData(); + + /** + * @dataProvider getDumpData + */ + public function testDump($expected, $node) + { + $this->assertSame($expected, $node->dump()); + } + + abstract public function getDumpData(); +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ArgumentsNodeTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ArgumentsNodeTest.php new file mode 100644 index 0000000..9d88b4a --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ArgumentsNodeTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests\Node; + +use Symfony\Component\ExpressionLanguage\Node\ArgumentsNode; + +class ArgumentsNodeTest extends ArrayNodeTest +{ + public function getCompileData() + { + return [ + ['"a", "b"', $this->getArrayNode()], + ]; + } + + public function getDumpData() + { + return [ + ['"a", "b"', $this->getArrayNode()], + ]; + } + + protected function createArrayNode() + { + return new ArgumentsNode(); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ArrayNodeTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ArrayNodeTest.php new file mode 100644 index 0000000..3d03d83 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ArrayNodeTest.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 Symfony\Component\ExpressionLanguage\Tests\Node; + +use Symfony\Component\ExpressionLanguage\Node\ArrayNode; +use Symfony\Component\ExpressionLanguage\Node\ConstantNode; + +class ArrayNodeTest extends AbstractNodeTest +{ + public function testSerialization() + { + $node = $this->createArrayNode(); + $node->addElement(new ConstantNode('foo')); + + $serializedNode = serialize($node); + $unserializedNode = unserialize($serializedNode); + + $this->assertEquals($node, $unserializedNode); + $this->assertNotEquals($this->createArrayNode(), $unserializedNode); + } + + public function getEvaluateData() + { + return [ + [['b' => 'a', 'b'], $this->getArrayNode()], + ]; + } + + public function getCompileData() + { + return [ + ['["b" => "a", 0 => "b"]', $this->getArrayNode()], + ]; + } + + public function getDumpData() + { + yield ['{"b": "a", 0: "b"}', $this->getArrayNode()]; + + $array = $this->createArrayNode(); + $array->addElement(new ConstantNode('c'), new ConstantNode('a"b')); + $array->addElement(new ConstantNode('d'), new ConstantNode('a\b')); + yield ['{"a\\"b": "c", "a\\\\b": "d"}', $array]; + + $array = $this->createArrayNode(); + $array->addElement(new ConstantNode('c')); + $array->addElement(new ConstantNode('d')); + yield ['["c", "d"]', $array]; + } + + protected function getArrayNode() + { + $array = $this->createArrayNode(); + $array->addElement(new ConstantNode('a'), new ConstantNode('b')); + $array->addElement(new ConstantNode('b')); + + return $array; + } + + protected function createArrayNode() + { + return new ArrayNode(); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/BinaryNodeTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/BinaryNodeTest.php new file mode 100644 index 0000000..b45a1e5 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/BinaryNodeTest.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests\Node; + +use Symfony\Component\ExpressionLanguage\Node\ArrayNode; +use Symfony\Component\ExpressionLanguage\Node\BinaryNode; +use Symfony\Component\ExpressionLanguage\Node\ConstantNode; + +class BinaryNodeTest extends AbstractNodeTest +{ + public function getEvaluateData() + { + $array = new ArrayNode(); + $array->addElement(new ConstantNode('a')); + $array->addElement(new ConstantNode('b')); + + return [ + [true, new BinaryNode('or', new ConstantNode(true), new ConstantNode(false))], + [true, new BinaryNode('||', new ConstantNode(true), new ConstantNode(false))], + [false, new BinaryNode('and', new ConstantNode(true), new ConstantNode(false))], + [false, new BinaryNode('&&', new ConstantNode(true), new ConstantNode(false))], + + [0, new BinaryNode('&', new ConstantNode(2), new ConstantNode(4))], + [6, new BinaryNode('|', new ConstantNode(2), new ConstantNode(4))], + [6, new BinaryNode('^', new ConstantNode(2), new ConstantNode(4))], + + [true, new BinaryNode('<', new ConstantNode(1), new ConstantNode(2))], + [true, new BinaryNode('<=', new ConstantNode(1), new ConstantNode(2))], + [true, new BinaryNode('<=', new ConstantNode(1), new ConstantNode(1))], + + [false, new BinaryNode('>', new ConstantNode(1), new ConstantNode(2))], + [false, new BinaryNode('>=', new ConstantNode(1), new ConstantNode(2))], + [true, new BinaryNode('>=', new ConstantNode(1), new ConstantNode(1))], + + [true, new BinaryNode('===', new ConstantNode(true), new ConstantNode(true))], + [false, new BinaryNode('!==', new ConstantNode(true), new ConstantNode(true))], + + [false, new BinaryNode('==', new ConstantNode(2), new ConstantNode(1))], + [true, new BinaryNode('!=', new ConstantNode(2), new ConstantNode(1))], + + [-1, new BinaryNode('-', new ConstantNode(1), new ConstantNode(2))], + [3, new BinaryNode('+', new ConstantNode(1), new ConstantNode(2))], + [4, new BinaryNode('*', new ConstantNode(2), new ConstantNode(2))], + [1, new BinaryNode('/', new ConstantNode(2), new ConstantNode(2))], + [1, new BinaryNode('%', new ConstantNode(5), new ConstantNode(2))], + [25, new BinaryNode('**', new ConstantNode(5), new ConstantNode(2))], + ['ab', new BinaryNode('~', new ConstantNode('a'), new ConstantNode('b'))], + + [true, new BinaryNode('in', new ConstantNode('a'), $array)], + [false, new BinaryNode('in', new ConstantNode('c'), $array)], + [true, new BinaryNode('not in', new ConstantNode('c'), $array)], + [false, new BinaryNode('not in', new ConstantNode('a'), $array)], + + [[1, 2, 3], new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))], + + [1, new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+$/'))], + ]; + } + + public function getCompileData() + { + $array = new ArrayNode(); + $array->addElement(new ConstantNode('a')); + $array->addElement(new ConstantNode('b')); + + return [ + ['(true || false)', new BinaryNode('or', new ConstantNode(true), new ConstantNode(false))], + ['(true || false)', new BinaryNode('||', new ConstantNode(true), new ConstantNode(false))], + ['(true && false)', new BinaryNode('and', new ConstantNode(true), new ConstantNode(false))], + ['(true && false)', new BinaryNode('&&', new ConstantNode(true), new ConstantNode(false))], + + ['(2 & 4)', new BinaryNode('&', new ConstantNode(2), new ConstantNode(4))], + ['(2 | 4)', new BinaryNode('|', new ConstantNode(2), new ConstantNode(4))], + ['(2 ^ 4)', new BinaryNode('^', new ConstantNode(2), new ConstantNode(4))], + + ['(1 < 2)', new BinaryNode('<', new ConstantNode(1), new ConstantNode(2))], + ['(1 <= 2)', new BinaryNode('<=', new ConstantNode(1), new ConstantNode(2))], + ['(1 <= 1)', new BinaryNode('<=', new ConstantNode(1), new ConstantNode(1))], + + ['(1 > 2)', new BinaryNode('>', new ConstantNode(1), new ConstantNode(2))], + ['(1 >= 2)', new BinaryNode('>=', new ConstantNode(1), new ConstantNode(2))], + ['(1 >= 1)', new BinaryNode('>=', new ConstantNode(1), new ConstantNode(1))], + + ['(true === true)', new BinaryNode('===', new ConstantNode(true), new ConstantNode(true))], + ['(true !== true)', new BinaryNode('!==', new ConstantNode(true), new ConstantNode(true))], + + ['(2 == 1)', new BinaryNode('==', new ConstantNode(2), new ConstantNode(1))], + ['(2 != 1)', new BinaryNode('!=', new ConstantNode(2), new ConstantNode(1))], + + ['(1 - 2)', new BinaryNode('-', new ConstantNode(1), new ConstantNode(2))], + ['(1 + 2)', new BinaryNode('+', new ConstantNode(1), new ConstantNode(2))], + ['(2 * 2)', new BinaryNode('*', new ConstantNode(2), new ConstantNode(2))], + ['(2 / 2)', new BinaryNode('/', new ConstantNode(2), new ConstantNode(2))], + ['(5 % 2)', new BinaryNode('%', new ConstantNode(5), new ConstantNode(2))], + ['pow(5, 2)', new BinaryNode('**', new ConstantNode(5), new ConstantNode(2))], + ['("a" . "b")', new BinaryNode('~', new ConstantNode('a'), new ConstantNode('b'))], + + ['in_array("a", [0 => "a", 1 => "b"])', new BinaryNode('in', new ConstantNode('a'), $array)], + ['in_array("c", [0 => "a", 1 => "b"])', new BinaryNode('in', new ConstantNode('c'), $array)], + ['!in_array("c", [0 => "a", 1 => "b"])', new BinaryNode('not in', new ConstantNode('c'), $array)], + ['!in_array("a", [0 => "a", 1 => "b"])', new BinaryNode('not in', new ConstantNode('a'), $array)], + + ['range(1, 3)', new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))], + + ['preg_match("/^[a-z]+/i\$/", "abc")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+/i$/'))], + ]; + } + + public function getDumpData() + { + $array = new ArrayNode(); + $array->addElement(new ConstantNode('a')); + $array->addElement(new ConstantNode('b')); + + return [ + ['(true or false)', new BinaryNode('or', new ConstantNode(true), new ConstantNode(false))], + ['(true || false)', new BinaryNode('||', new ConstantNode(true), new ConstantNode(false))], + ['(true and false)', new BinaryNode('and', new ConstantNode(true), new ConstantNode(false))], + ['(true && false)', new BinaryNode('&&', new ConstantNode(true), new ConstantNode(false))], + + ['(2 & 4)', new BinaryNode('&', new ConstantNode(2), new ConstantNode(4))], + ['(2 | 4)', new BinaryNode('|', new ConstantNode(2), new ConstantNode(4))], + ['(2 ^ 4)', new BinaryNode('^', new ConstantNode(2), new ConstantNode(4))], + + ['(1 < 2)', new BinaryNode('<', new ConstantNode(1), new ConstantNode(2))], + ['(1 <= 2)', new BinaryNode('<=', new ConstantNode(1), new ConstantNode(2))], + ['(1 <= 1)', new BinaryNode('<=', new ConstantNode(1), new ConstantNode(1))], + + ['(1 > 2)', new BinaryNode('>', new ConstantNode(1), new ConstantNode(2))], + ['(1 >= 2)', new BinaryNode('>=', new ConstantNode(1), new ConstantNode(2))], + ['(1 >= 1)', new BinaryNode('>=', new ConstantNode(1), new ConstantNode(1))], + + ['(true === true)', new BinaryNode('===', new ConstantNode(true), new ConstantNode(true))], + ['(true !== true)', new BinaryNode('!==', new ConstantNode(true), new ConstantNode(true))], + + ['(2 == 1)', new BinaryNode('==', new ConstantNode(2), new ConstantNode(1))], + ['(2 != 1)', new BinaryNode('!=', new ConstantNode(2), new ConstantNode(1))], + + ['(1 - 2)', new BinaryNode('-', new ConstantNode(1), new ConstantNode(2))], + ['(1 + 2)', new BinaryNode('+', new ConstantNode(1), new ConstantNode(2))], + ['(2 * 2)', new BinaryNode('*', new ConstantNode(2), new ConstantNode(2))], + ['(2 / 2)', new BinaryNode('/', new ConstantNode(2), new ConstantNode(2))], + ['(5 % 2)', new BinaryNode('%', new ConstantNode(5), new ConstantNode(2))], + ['(5 ** 2)', new BinaryNode('**', new ConstantNode(5), new ConstantNode(2))], + ['("a" ~ "b")', new BinaryNode('~', new ConstantNode('a'), new ConstantNode('b'))], + + ['("a" in ["a", "b"])', new BinaryNode('in', new ConstantNode('a'), $array)], + ['("c" in ["a", "b"])', new BinaryNode('in', new ConstantNode('c'), $array)], + ['("c" not in ["a", "b"])', new BinaryNode('not in', new ConstantNode('c'), $array)], + ['("a" not in ["a", "b"])', new BinaryNode('not in', new ConstantNode('a'), $array)], + + ['(1 .. 3)', new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))], + + ['("abc" matches "/^[a-z]+/i$/")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+/i$/'))], + ]; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ConditionalNodeTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ConditionalNodeTest.php new file mode 100644 index 0000000..13b7cbd --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ConditionalNodeTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests\Node; + +use Symfony\Component\ExpressionLanguage\Node\ConditionalNode; +use Symfony\Component\ExpressionLanguage\Node\ConstantNode; + +class ConditionalNodeTest extends AbstractNodeTest +{ + public function getEvaluateData() + { + return [ + [1, new ConditionalNode(new ConstantNode(true), new ConstantNode(1), new ConstantNode(2))], + [2, new ConditionalNode(new ConstantNode(false), new ConstantNode(1), new ConstantNode(2))], + ]; + } + + public function getCompileData() + { + return [ + ['((true) ? (1) : (2))', new ConditionalNode(new ConstantNode(true), new ConstantNode(1), new ConstantNode(2))], + ['((false) ? (1) : (2))', new ConditionalNode(new ConstantNode(false), new ConstantNode(1), new ConstantNode(2))], + ]; + } + + public function getDumpData() + { + return [ + ['(true ? 1 : 2)', new ConditionalNode(new ConstantNode(true), new ConstantNode(1), new ConstantNode(2))], + ['(false ? 1 : 2)', new ConditionalNode(new ConstantNode(false), new ConstantNode(1), new ConstantNode(2))], + ]; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ConstantNodeTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ConstantNodeTest.php new file mode 100644 index 0000000..fee9f5b --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/ConstantNodeTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests\Node; + +use Symfony\Component\ExpressionLanguage\Node\ConstantNode; + +class ConstantNodeTest extends AbstractNodeTest +{ + public function getEvaluateData() + { + return [ + [false, new ConstantNode(false)], + [true, new ConstantNode(true)], + [null, new ConstantNode(null)], + [3, new ConstantNode(3)], + [3.3, new ConstantNode(3.3)], + ['foo', new ConstantNode('foo')], + [[1, 'b' => 'a'], new ConstantNode([1, 'b' => 'a'])], + ]; + } + + public function getCompileData() + { + return [ + ['false', new ConstantNode(false)], + ['true', new ConstantNode(true)], + ['null', new ConstantNode(null)], + ['3', new ConstantNode(3)], + ['3.3', new ConstantNode(3.3)], + ['"foo"', new ConstantNode('foo')], + ['[0 => 1, "b" => "a"]', new ConstantNode([1, 'b' => 'a'])], + ]; + } + + public function getDumpData() + { + return [ + ['false', new ConstantNode(false)], + ['true', new ConstantNode(true)], + ['null', new ConstantNode(null)], + ['3', new ConstantNode(3)], + ['3.3', new ConstantNode(3.3)], + ['"foo"', new ConstantNode('foo')], + ['foo', new ConstantNode('foo', true)], + ['{0: 1, "b": "a", 1: true}', new ConstantNode([1, 'b' => 'a', true])], + ['{"a\\"b": "c", "a\\\\b": "d"}', new ConstantNode(['a"b' => 'c', 'a\\b' => 'd'])], + ['["c", "d"]', new ConstantNode(['c', 'd'])], + ['{"a": ["b"]}', new ConstantNode(['a' => ['b']])], + ]; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/FunctionNodeTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/FunctionNodeTest.php new file mode 100644 index 0000000..c6cb02c --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/FunctionNodeTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests\Node; + +use Symfony\Component\ExpressionLanguage\Node\ConstantNode; +use Symfony\Component\ExpressionLanguage\Node\FunctionNode; +use Symfony\Component\ExpressionLanguage\Node\Node; + +class FunctionNodeTest extends AbstractNodeTest +{ + public function getEvaluateData() + { + return [ + ['bar', new FunctionNode('foo', new Node([new ConstantNode('bar')])), [], ['foo' => $this->getCallables()]], + ]; + } + + public function getCompileData() + { + return [ + ['foo("bar")', new FunctionNode('foo', new Node([new ConstantNode('bar')])), ['foo' => $this->getCallables()]], + ]; + } + + public function getDumpData() + { + return [ + ['foo("bar")', new FunctionNode('foo', new Node([new ConstantNode('bar')])), ['foo' => $this->getCallables()]], + ]; + } + + protected function getCallables() + { + return [ + 'compiler' => function ($arg) { + return sprintf('foo(%s)', $arg); + }, + 'evaluator' => function ($variables, $arg) { + return $arg; + }, + ]; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/GetAttrNodeTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/GetAttrNodeTest.php new file mode 100644 index 0000000..c790f33 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/GetAttrNodeTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests\Node; + +use Symfony\Component\ExpressionLanguage\Node\ArrayNode; +use Symfony\Component\ExpressionLanguage\Node\ConstantNode; +use Symfony\Component\ExpressionLanguage\Node\GetAttrNode; +use Symfony\Component\ExpressionLanguage\Node\NameNode; + +class GetAttrNodeTest extends AbstractNodeTest +{ + public function getEvaluateData() + { + return [ + ['b', new GetAttrNode(new NameNode('foo'), new ConstantNode(0), $this->getArrayNode(), GetAttrNode::ARRAY_CALL), ['foo' => ['b' => 'a', 'b']]], + ['a', new GetAttrNode(new NameNode('foo'), new ConstantNode('b'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL), ['foo' => ['b' => 'a', 'b']]], + + ['bar', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::PROPERTY_CALL), ['foo' => new Obj()]], + + ['baz', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), ['foo' => new Obj()]], + ['a', new GetAttrNode(new NameNode('foo'), new NameNode('index'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL), ['foo' => ['b' => 'a', 'b'], 'index' => 'b']], + ]; + } + + public function getCompileData() + { + return [ + ['$foo[0]', new GetAttrNode(new NameNode('foo'), new ConstantNode(0), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)], + ['$foo["b"]', new GetAttrNode(new NameNode('foo'), new ConstantNode('b'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)], + + ['$foo->foo', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::PROPERTY_CALL), ['foo' => new Obj()]], + + ['$foo->foo(["b" => "a", 0 => "b"])', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), ['foo' => new Obj()]], + ['$foo[$index]', new GetAttrNode(new NameNode('foo'), new NameNode('index'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)], + ]; + } + + public function getDumpData() + { + return [ + ['foo[0]', new GetAttrNode(new NameNode('foo'), new ConstantNode(0), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)], + ['foo["b"]', new GetAttrNode(new NameNode('foo'), new ConstantNode('b'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)], + + ['foo.foo', new GetAttrNode(new NameNode('foo'), new NameNode('foo'), $this->getArrayNode(), GetAttrNode::PROPERTY_CALL), ['foo' => new Obj()]], + + ['foo.foo({"b": "a", 0: "b"})', new GetAttrNode(new NameNode('foo'), new NameNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), ['foo' => new Obj()]], + ['foo[index]', new GetAttrNode(new NameNode('foo'), new NameNode('index'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)], + ]; + } + + protected function getArrayNode() + { + $array = new ArrayNode(); + $array->addElement(new ConstantNode('a'), new ConstantNode('b')); + $array->addElement(new ConstantNode('b')); + + return $array; + } +} + +class Obj +{ + public $foo = 'bar'; + + public function foo() + { + return 'baz'; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/NameNodeTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/NameNodeTest.php new file mode 100644 index 0000000..a30c27b --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/NameNodeTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests\Node; + +use Symfony\Component\ExpressionLanguage\Node\NameNode; + +class NameNodeTest extends AbstractNodeTest +{ + public function getEvaluateData() + { + return [ + ['bar', new NameNode('foo'), ['foo' => 'bar']], + ]; + } + + public function getCompileData() + { + return [ + ['$foo', new NameNode('foo')], + ]; + } + + public function getDumpData() + { + return [ + ['foo', new NameNode('foo')], + ]; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/NodeTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/NodeTest.php new file mode 100644 index 0000000..351da05 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/NodeTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ExpressionLanguage\Node\ConstantNode; +use Symfony\Component\ExpressionLanguage\Node\Node; + +class NodeTest extends TestCase +{ + public function testToString() + { + $node = new Node([new ConstantNode('foo')]); + + $this->assertEquals(<<<'EOF' +Node( + ConstantNode(value: 'foo') +) +EOF + , (string) $node); + } + + public function testSerialization() + { + $node = new Node(['foo' => 'bar'], ['bar' => 'foo']); + + $serializedNode = serialize($node); + $unserializedNode = unserialize($serializedNode); + + $this->assertEquals($node, $unserializedNode); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/UnaryNodeTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/UnaryNodeTest.php new file mode 100644 index 0000000..bac75d2 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/Node/UnaryNodeTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests\Node; + +use Symfony\Component\ExpressionLanguage\Node\ConstantNode; +use Symfony\Component\ExpressionLanguage\Node\UnaryNode; + +class UnaryNodeTest extends AbstractNodeTest +{ + public function getEvaluateData() + { + return [ + [-1, new UnaryNode('-', new ConstantNode(1))], + [3, new UnaryNode('+', new ConstantNode(3))], + [false, new UnaryNode('!', new ConstantNode(true))], + [false, new UnaryNode('not', new ConstantNode(true))], + ]; + } + + public function getCompileData() + { + return [ + ['(-1)', new UnaryNode('-', new ConstantNode(1))], + ['(+3)', new UnaryNode('+', new ConstantNode(3))], + ['(!true)', new UnaryNode('!', new ConstantNode(true))], + ['(!true)', new UnaryNode('not', new ConstantNode(true))], + ]; + } + + public function getDumpData() + { + return [ + ['(- 1)', new UnaryNode('-', new ConstantNode(1))], + ['(+ 3)', new UnaryNode('+', new ConstantNode(3))], + ['(! true)', new UnaryNode('!', new ConstantNode(true))], + ['(not true)', new UnaryNode('not', new ConstantNode(true))], + ]; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ParsedExpressionTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ParsedExpressionTest.php new file mode 100644 index 0000000..d28101f --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ParsedExpressionTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ExpressionLanguage\Node\ConstantNode; +use Symfony\Component\ExpressionLanguage\ParsedExpression; + +class ParsedExpressionTest extends TestCase +{ + public function testSerialization() + { + $expression = new ParsedExpression('25', new ConstantNode('25')); + + $serializedExpression = serialize($expression); + $unserializedExpression = unserialize($serializedExpression); + + $this->assertEquals($expression, $unserializedExpression); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ParserCache/ParserCacheAdapterTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ParserCache/ParserCacheAdapterTest.php new file mode 100644 index 0000000..2e92633 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ParserCache/ParserCacheAdapterTest.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ExpressionLanguage\Node\Node; +use Symfony\Component\ExpressionLanguage\ParsedExpression; +use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheAdapter; + +/** + * @group legacy + */ +class ParserCacheAdapterTest extends TestCase +{ + public function testGetItem() + { + $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock(); + + $key = 'key'; + $value = 'value'; + $parserCacheAdapter = new ParserCacheAdapter($poolMock); + + $poolMock + ->expects($this->once()) + ->method('fetch') + ->with($key) + ->willReturn($value) + ; + + $cacheItem = $parserCacheAdapter->getItem($key); + + $this->assertEquals($cacheItem->get(), $value); + $this->assertEquals($cacheItem->isHit(), true); + } + + public function testSave() + { + $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock(); + $cacheItemMock = $this->getMockBuilder('Psr\Cache\CacheItemInterface')->getMock(); + $key = 'key'; + $value = new ParsedExpression('1 + 1', new Node([], [])); + $parserCacheAdapter = new ParserCacheAdapter($poolMock); + + $poolMock + ->expects($this->once()) + ->method('save') + ->with($key, $value) + ; + + $cacheItemMock + ->expects($this->once()) + ->method('getKey') + ->willReturn($key) + ; + + $cacheItemMock + ->expects($this->once()) + ->method('get') + ->willReturn($value) + ; + + $parserCacheAdapter->save($cacheItemMock); + } + + public function testGetItems() + { + $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock(); + $parserCacheAdapter = new ParserCacheAdapter($poolMock); + $this->expectException(\BadMethodCallException::class); + + $parserCacheAdapter->getItems(); + } + + public function testHasItem() + { + $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock(); + $key = 'key'; + $parserCacheAdapter = new ParserCacheAdapter($poolMock); + $this->expectException(\BadMethodCallException::class); + + $parserCacheAdapter->hasItem($key); + } + + public function testClear() + { + $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock(); + $parserCacheAdapter = new ParserCacheAdapter($poolMock); + $this->expectException(\BadMethodCallException::class); + + $parserCacheAdapter->clear(); + } + + public function testDeleteItem() + { + $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock(); + $key = 'key'; + $parserCacheAdapter = new ParserCacheAdapter($poolMock); + $this->expectException(\BadMethodCallException::class); + + $parserCacheAdapter->deleteItem($key); + } + + public function testDeleteItems() + { + $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock(); + $keys = ['key']; + $parserCacheAdapter = new ParserCacheAdapter($poolMock); + $this->expectException(\BadMethodCallException::class); + + $parserCacheAdapter->deleteItems($keys); + } + + public function testSaveDeferred() + { + $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock(); + $parserCacheAdapter = new ParserCacheAdapter($poolMock); + $cacheItemMock = $this->getMockBuilder('Psr\Cache\CacheItemInterface')->getMock(); + $this->expectException(\BadMethodCallException::class); + + $parserCacheAdapter->saveDeferred($cacheItemMock); + } + + public function testCommit() + { + $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock(); + $parserCacheAdapter = new ParserCacheAdapter($poolMock); + $this->expectException(\BadMethodCallException::class); + + $parserCacheAdapter->commit(); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ParserTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ParserTest.php new file mode 100644 index 0000000..2d5a0a6 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Tests/ParserTest.php @@ -0,0 +1,237 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ExpressionLanguage\Lexer; +use Symfony\Component\ExpressionLanguage\Node; +use Symfony\Component\ExpressionLanguage\Parser; + +class ParserTest extends TestCase +{ + public function testParseWithInvalidName() + { + $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError'); + $this->expectExceptionMessage('Variable "foo" is not valid around position 1 for expression `foo`.'); + $lexer = new Lexer(); + $parser = new Parser([]); + $parser->parse($lexer->tokenize('foo')); + } + + public function testParseWithZeroInNames() + { + $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError'); + $this->expectExceptionMessage('Variable "foo" is not valid around position 1 for expression `foo`.'); + $lexer = new Lexer(); + $parser = new Parser([]); + $parser->parse($lexer->tokenize('foo'), [0]); + } + + /** + * @dataProvider getParseData + */ + public function testParse($node, $expression, $names = []) + { + $lexer = new Lexer(); + $parser = new Parser([]); + $this->assertEquals($node, $parser->parse($lexer->tokenize($expression), $names)); + } + + public function getParseData() + { + $arguments = new Node\ArgumentsNode(); + $arguments->addElement(new Node\ConstantNode('arg1')); + $arguments->addElement(new Node\ConstantNode(2)); + $arguments->addElement(new Node\ConstantNode(true)); + + $arrayNode = new Node\ArrayNode(); + $arrayNode->addElement(new Node\NameNode('bar')); + + return [ + [ + new Node\NameNode('a'), + 'a', + ['a'], + ], + [ + new Node\ConstantNode('a'), + '"a"', + ], + [ + new Node\ConstantNode(3), + '3', + ], + [ + new Node\ConstantNode(false), + 'false', + ], + [ + new Node\ConstantNode(true), + 'true', + ], + [ + new Node\ConstantNode(null), + 'null', + ], + [ + new Node\UnaryNode('-', new Node\ConstantNode(3)), + '-3', + ], + [ + new Node\BinaryNode('-', new Node\ConstantNode(3), new Node\ConstantNode(3)), + '3 - 3', + ], + [ + new Node\BinaryNode('*', + new Node\BinaryNode('-', new Node\ConstantNode(3), new Node\ConstantNode(3)), + new Node\ConstantNode(2) + ), + '(3 - 3) * 2', + ], + [ + new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode('bar', true), new Node\ArgumentsNode(), Node\GetAttrNode::PROPERTY_CALL), + 'foo.bar', + ['foo'], + ], + [ + new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode('bar', true), new Node\ArgumentsNode(), Node\GetAttrNode::METHOD_CALL), + 'foo.bar()', + ['foo'], + ], + [ + new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode('not', true), new Node\ArgumentsNode(), Node\GetAttrNode::METHOD_CALL), + 'foo.not()', + ['foo'], + ], + [ + new Node\GetAttrNode( + new Node\NameNode('foo'), + new Node\ConstantNode('bar', true), + $arguments, + Node\GetAttrNode::METHOD_CALL + ), + 'foo.bar("arg1", 2, true)', + ['foo'], + ], + [ + new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode(3), new Node\ArgumentsNode(), Node\GetAttrNode::ARRAY_CALL), + 'foo[3]', + ['foo'], + ], + [ + new Node\ConditionalNode(new Node\ConstantNode(true), new Node\ConstantNode(true), new Node\ConstantNode(false)), + 'true ? true : false', + ], + [ + new Node\BinaryNode('matches', new Node\ConstantNode('foo'), new Node\ConstantNode('/foo/')), + '"foo" matches "/foo/"', + ], + + // chained calls + [ + $this->createGetAttrNode( + $this->createGetAttrNode( + $this->createGetAttrNode( + $this->createGetAttrNode(new Node\NameNode('foo'), 'bar', Node\GetAttrNode::METHOD_CALL), + 'foo', Node\GetAttrNode::METHOD_CALL), + 'baz', Node\GetAttrNode::PROPERTY_CALL), + '3', Node\GetAttrNode::ARRAY_CALL), + 'foo.bar().foo().baz[3]', + ['foo'], + ], + + [ + new Node\NameNode('foo'), + 'bar', + ['foo' => 'bar'], + ], + + // Operators collisions + [ + new Node\BinaryNode( + 'in', + new Node\GetAttrNode( + new Node\NameNode('foo'), + new Node\ConstantNode('not', true), + new Node\ArgumentsNode(), + Node\GetAttrNode::PROPERTY_CALL + ), + $arrayNode + ), + 'foo.not in [bar]', + ['foo', 'bar'], + ], + [ + new Node\BinaryNode( + 'or', + new Node\UnaryNode('not', new Node\NameNode('foo')), + new Node\GetAttrNode( + new Node\NameNode('foo'), + new Node\ConstantNode('not', true), + new Node\ArgumentsNode(), + Node\GetAttrNode::PROPERTY_CALL + ) + ), + 'not foo or foo.not', + ['foo'], + ], + ]; + } + + private function createGetAttrNode($node, $item, $type) + { + return new Node\GetAttrNode($node, new Node\ConstantNode($item, Node\GetAttrNode::ARRAY_CALL !== $type), new Node\ArgumentsNode(), $type); + } + + /** + * @dataProvider getInvalidPostfixData + */ + public function testParseWithInvalidPostfixData($expr, $names = []) + { + $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError'); + $lexer = new Lexer(); + $parser = new Parser([]); + $parser->parse($lexer->tokenize($expr), $names); + } + + public function getInvalidPostfixData() + { + return [ + [ + 'foo."#"', + ['foo'], + ], + [ + 'foo."bar"', + ['foo'], + ], + [ + 'foo.**', + ['foo'], + ], + [ + 'foo.123', + ['foo'], + ], + ]; + } + + public function testNameProposal() + { + $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError'); + $this->expectExceptionMessage('Did you mean "baz"?'); + $lexer = new Lexer(); + $parser = new Parser([]); + + $parser->parse($lexer->tokenize('foo > bar'), ['foo', 'baz']); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Token.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Token.php new file mode 100644 index 0000000..4517335 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/Token.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents a Token. + * + * @author Fabien Potencier + */ +class Token +{ + public $value; + public $type; + public $cursor; + + const EOF_TYPE = 'end of expression'; + const NAME_TYPE = 'name'; + const NUMBER_TYPE = 'number'; + const STRING_TYPE = 'string'; + const OPERATOR_TYPE = 'operator'; + const PUNCTUATION_TYPE = 'punctuation'; + + /** + * @param string $type The type of the token (self::*_TYPE) + * @param string|int|float|null $value The token value + * @param int $cursor The cursor position in the source + */ + public function __construct($type, $value, $cursor) + { + $this->type = $type; + $this->value = $value; + $this->cursor = $cursor; + } + + /** + * Returns a string representation of the token. + * + * @return string A string representation of the token + */ + public function __toString() + { + return sprintf('%3d %-11s %s', $this->cursor, strtoupper($this->type), $this->value); + } + + /** + * Tests the current token for a type and/or a value. + * + * @param array|int $type The type to test + * @param string|null $value The token value + * + * @return bool + */ + public function test($type, $value = null) + { + return $this->type === $type && (null === $value || $this->value == $value); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/TokenStream.php b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/TokenStream.php new file mode 100644 index 0000000..09d3854 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/TokenStream.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents a token stream. + * + * @author Fabien Potencier + */ +class TokenStream +{ + public $current; + + private $tokens; + private $position = 0; + private $expression; + + /** + * @param array $tokens An array of tokens + * @param string $expression + */ + public function __construct(array $tokens, $expression = '') + { + $this->tokens = $tokens; + $this->current = $tokens[0]; + $this->expression = $expression; + } + + /** + * Returns a string representation of the token stream. + * + * @return string + */ + public function __toString() + { + return implode("\n", $this->tokens); + } + + /** + * Sets the pointer to the next token and returns the old one. + */ + public function next() + { + ++$this->position; + + if (!isset($this->tokens[$this->position])) { + throw new SyntaxError('Unexpected end of expression.', $this->current->cursor, $this->expression); + } + + $this->current = $this->tokens[$this->position]; + } + + /** + * Tests a token. + * + * @param array|int $type The type to test + * @param string|null $value The token value + * @param string|null $message The syntax error message + */ + public function expect($type, $value = null, $message = null) + { + $token = $this->current; + if (!$token->test($type, $value)) { + throw new SyntaxError(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s).', $message ? $message.'. ' : '', $token->type, $token->value, $type, $value ? sprintf(' with value "%s"', $value) : ''), $token->cursor, $this->expression); + } + $this->next(); + } + + /** + * Checks if end of stream was reached. + * + * @return bool + */ + public function isEOF() + { + return Token::EOF_TYPE === $this->current->type; + } + + /** + * @internal + * + * @return string + */ + public function getExpression() + { + return $this->expression; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/composer.json b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/composer.json new file mode 100644 index 0000000..ee44185 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/expression-language", + "type": "library", + "description": "Symfony ExpressionLanguage Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/cache": "~3.1|~4.0", + "symfony/polyfill-php70": "~1.6" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\ExpressionLanguage\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/phpunit.xml.dist b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/phpunit.xml.dist new file mode 100644 index 0000000..ae84dcd --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/expression-language/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/LICENSE b/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Php70.php b/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Php70.php new file mode 100644 index 0000000..7f1ad08 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Php70.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php70; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class Php70 +{ + public static function intdiv($dividend, $divisor) + { + $dividend = self::intArg($dividend, __FUNCTION__, 1); + $divisor = self::intArg($divisor, __FUNCTION__, 2); + + if (0 === $divisor) { + throw new \DivisionByZeroError('Division by zero'); + } + if (-1 === $divisor && ~PHP_INT_MAX === $dividend) { + throw new \ArithmeticError('Division of PHP_INT_MIN by -1 is not an integer'); + } + + return ($dividend - ($dividend % $divisor)) / $divisor; + } + + public static function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0) + { + $count = 0; + $result = (string) $subject; + if (0 === $limit = self::intArg($limit, __FUNCTION__, 3)) { + return $result; + } + + foreach ($patterns as $pattern => $callback) { + $result = preg_replace_callback($pattern, $callback, $result, $limit, $c); + $count += $c; + } + + return $result; + } + + public static function error_clear_last() + { + static $handler; + if (!$handler) { + $handler = function () { return false; }; + } + set_error_handler($handler); + @trigger_error(''); + restore_error_handler(); + } + + private static function intArg($value, $caller, $pos) + { + if (\is_int($value)) { + return $value; + } + if (!\is_numeric($value) || PHP_INT_MAX <= ($value += 0) || ~PHP_INT_MAX >= $value) { + throw new \TypeError(sprintf('%s() expects parameter %d to be integer, %s given', $caller, $pos, \gettype($value))); + } + + return (int) $value; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/README.md b/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/README.md new file mode 100644 index 0000000..abd5488 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/README.md @@ -0,0 +1,28 @@ +Symfony Polyfill / Php70 +======================== + +This component provides features unavailable in releases prior to PHP 7.0: + +- [`intdiv`](https://php.net/intdiv) +- [`preg_replace_callback_array`](https://php.net/preg_replace_callback_array) +- [`error_clear_last`](https://php.net/error_clear_last) +- `random_bytes` and `random_int` (from [paragonie/random_compat](https://github.com/paragonie/random_compat)) +- [`*Error` throwable classes](https://php.net/Error) +- [`PHP_INT_MIN`](https://php.net/reserved.constants#constant.php-int-min) +- `SessionUpdateTimestampHandlerInterface` + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +Compatibility notes +=================== + +To write portable code between PHP5 and PHP7, some care must be taken: +- `\*Error` exceptions must be caught before `\Exception`; +- after calling `error_clear_last()`, the result of `$e = error_get_last()` must be + verified using `isset($e['message'][0])` instead of `null !== $e`. + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php b/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php new file mode 100644 index 0000000..6819124 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php @@ -0,0 +1,5 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php70 as p; + +if (PHP_VERSION_ID < 70000) { + if (!defined('PHP_INT_MIN')) { + define('PHP_INT_MIN', ~PHP_INT_MAX); + } + if (!function_exists('intdiv')) { + function intdiv($dividend, $divisor) { return p\Php70::intdiv($dividend, $divisor); } + } + if (!function_exists('preg_replace_callback_array')) { + function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0) { return p\Php70::preg_replace_callback_array($patterns, $subject, $limit, $count); } + } + if (!function_exists('error_clear_last')) { + function error_clear_last() { return p\Php70::error_clear_last(); } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/composer.json b/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/composer.json new file mode 100644 index 0000000..98599f0 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/polyfill-php70/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/polyfill-php70", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3", + "paragonie/random_compat": "~1.0|~2.0|~9.99" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php70\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/.gitignore b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/LICENSE b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/LICENSE new file mode 100644 index 0000000..3f853aa --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/README.md b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/README.md new file mode 100644 index 0000000..d033a43 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Service Contracts +========================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/master/README.md for more information. diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ResetInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ResetInterface.php new file mode 100644 index 0000000..1af1075 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ResetInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * Provides a way to reset an object to its initial state. + * + * When calling the "reset()" method on an object, it should be put back to its + * initial state. This usually means clearing any internal buffers and forwarding + * the call to internal dependencies. All properties of the object should be put + * back to the same state it had when it was first ready to use. + * + * This method could be called, for example, to recycle objects that are used as + * services, so that they can be used to handle several requests in the same + * process loop (note that we advise making your services stateless instead of + * implementing this interface when possible.) + */ +interface ResetInterface +{ + public function reset(); +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 0000000..4ec6eb4 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceLocatorTrait.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +/** + * A trait to help implement ServiceProviderInterface. + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait ServiceLocatorTrait +{ + private $factories; + private $loading = []; + private $providedTypes; + + /** + * @param callable[] $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has($id) + { + return isset($this->factories[$id]); + } + + /** + * {@inheritdoc} + */ + public function get($id) + { + if (!isset($this->factories[$id])) { + throw $this->createNotFoundException($id); + } + + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = \array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw $this->createCircularReferenceException($id, $ids); + } + + $this->loading[$id] = $id; + try { + return $this->factories[$id]($this); + } finally { + unset($this->loading[$id]); + } + } + + /** + * {@inheritdoc} + */ + public function getProvidedServices(): array + { + if (null === $this->providedTypes) { + $this->providedTypes = []; + + foreach ($this->factories as $name => $factory) { + if (!\is_callable($factory)) { + $this->providedTypes[$name] = '?'; + } else { + $type = (new \ReflectionFunction($factory))->getReturnType(); + + $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').$type->getName() : '?'; + } + } + } + + return $this->providedTypes; + } + + private function createNotFoundException(string $id): NotFoundExceptionInterface + { + if (!$alternatives = array_keys($this->factories)) { + $message = 'is empty...'; + } else { + $last = array_pop($alternatives); + if ($alternatives) { + $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + } else { + $message = sprintf('only knows about the "%s" service.', $last); + } + } + + if ($this->loading) { + $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + } else { + $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); + } + + return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { + }; + } + + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + }; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceProviderInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 0000000..c60ad0b --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceProviderInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container. + * + * @author Nicolas Grekas + * @author Mateusz Sip + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * Returns an associative array of service types keyed by the identifiers provided by the current container. + * + * Examples: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface + * * ['foo' => '?'] means the container provides service name "foo" of unspecified type + * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null + * + * @return string[] The provided service types, keyed by service names + */ + public function getProvidedServices(): array; +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceSubscriberInterface.php new file mode 100644 index 0000000..8bb320f --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceSubscriberInterface.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. + * + * The getSubscribedServices method returns an array of service types required by such instances, + * optionally keyed by the service names used internally. Service types that start with an interrogation + * mark "?" are optional, while the other ones are mandatory service dependencies. + * + * The injected service locators SHOULD NOT allow access to any other services not specified by the method. + * + * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally. + * This interface does not dictate any injection method for these service locators, although constructor + * injection is recommended. + * + * @author Nicolas Grekas + */ +interface ServiceSubscriberInterface +{ + /** + * Returns an array of service types required by such instances, optionally keyed by the service names used internally. + * + * For mandatory dependencies: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name + * internally to fetch a service which must implement Psr\Log\LoggerInterface. + * * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name + * internally to fetch an iterable of Psr\Log\LoggerInterface instances. + * * ['Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface'] + * + * otherwise: + * + * * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency + * * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency + * * ['?Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] + * + * @return array The required service types, optionally keyed by service names + */ + public static function getSubscribedServices(); +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 0000000..5d9d456 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/ServiceSubscriberTrait.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * private method return types. Service ids are available as "ClassName::methodName". + * + * @author Kevin Bond + */ +trait ServiceSubscriberTrait +{ + /** @var ContainerInterface */ + protected $container; + + public static function getSubscribedServices(): array + { + static $services; + + if (null !== $services) { + return $services; + } + + $services = \is_callable(['parent', __FUNCTION__]) ? parent::getSubscribedServices() : []; + + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + continue; + } + + if (self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !$returnType->isBuiltin()) { + $services[self::class.'::'.$method->name] = '?'.$returnType->getName(); + } + } + + return $services; + } + + /** + * @required + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + + if (\is_callable(['parent', __FUNCTION__])) { + return parent::setContainer($container); + } + + return null; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php new file mode 100644 index 0000000..5ed9149 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; + +abstract class ServiceLocatorTest extends TestCase +{ + protected function getServiceLocator(array $factories) + { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + function () { return 'dummy'; }, + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('baz', $locator->get('bar')); + } + + public function testGetDoesNotMemoize() + { + $i = 0; + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$i) { + ++$i; + + return 'bar'; + }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame(2, $i); + } + + public function testThrowsOnUndefinedInternalService() + { + if (!$this->getExpectedException()) { + $this->expectException('Psr\Container\NotFoundExceptionInterface'); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + } + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $this->expectException('Psr\Container\ContainerExceptionInterface'); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/composer.json b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/composer.json new file mode 100644 index 0000000..cbd491b --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/service-contracts/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/service-contracts", + "type": "library", + "description": "Generic abstractions related to writing services", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Service\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/CHANGELOG.md b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/CHANGELOG.md new file mode 100644 index 0000000..9aa4a8b --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +4.2.0 +----- + + * added the component diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php new file mode 100644 index 0000000..4cebe44 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class ClassNotFoundException extends \Exception implements ExceptionInterface +{ + public function __construct(string $class, \Throwable $previous = null) + { + parent::__construct(sprintf('Class "%s" not found.', $class), 0, $previous); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Exception/ExceptionInterface.php b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Exception/ExceptionInterface.php new file mode 100644 index 0000000..adfaed4 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php new file mode 100644 index 0000000..7ca4884 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class NotInstantiableTypeException extends \Exception implements ExceptionInterface +{ + public function __construct(string $type) + { + parent::__construct(sprintf('Type "%s" is not instantiable.', $type)); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Instantiator.php b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Instantiator.php new file mode 100644 index 0000000..9fd8cf3 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Instantiator.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\ExceptionInterface; +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\Registry; + +/** + * A utility class to create objects without calling their constructor. + * + * @author Nicolas Grekas + */ +final class Instantiator +{ + /** + * Creates an object and sets its properties without calling its constructor nor any other methods. + * + * For example: + * + * // creates an empty instance of Foo + * Instantiator::instantiate(Foo::class); + * + * // creates a Foo instance and sets one of its properties + * Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]); + * + * // creates a Foo instance and sets a private property defined on its parent Bar class + * Instantiator::instantiate(Foo::class, [], [ + * Bar::class => ['privateBarProperty' => $propertyValue], + * ]); + * + * Instances of ArrayObject, ArrayIterator and SplObjectHash can be created + * by using the special "\0" property name to define their internal value: + * + * // creates an SplObjectHash where $info1 is attached to $obj1, etc. + * Instantiator::instantiate(SplObjectStorage::class, ["\0" => [$obj1, $info1, $obj2, $info2...]]); + * + * // creates an ArrayObject populated with $inputArray + * Instantiator::instantiate(ArrayObject::class, ["\0" => [$inputArray]]); + * + * @param string $class The class of the instance to create + * @param array $properties The properties to set on the instance + * @param array $privateProperties The private properties to set on the instance, + * keyed by their declaring class + * + * @return object The created instance + * + * @throws ExceptionInterface When the instance cannot be created + */ + public static function instantiate(string $class, array $properties = [], array $privateProperties = []): object + { + $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); + + if (Registry::$cloneable[$class]) { + $wrappedInstance = [clone Registry::$prototypes[$class]]; + } elseif (Registry::$instantiableWithoutConstructor[$class]) { + $wrappedInstance = [$reflector->newInstanceWithoutConstructor()]; + } elseif (null === Registry::$prototypes[$class]) { + throw new NotInstantiableTypeException($class); + } elseif ($reflector->implementsInterface('Serializable') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__unserialize'))) { + $wrappedInstance = [unserialize('C:'.\strlen($class).':"'.$class.'":0:{}')]; + } else { + $wrappedInstance = [unserialize('O:'.\strlen($class).':"'.$class.'":0:{}')]; + } + + if ($properties) { + $privateProperties[$class] = isset($privateProperties[$class]) ? $properties + $privateProperties[$class] : $properties; + } + + foreach ($privateProperties as $class => $properties) { + if (!$properties) { + continue; + } + foreach ($properties as $name => $value) { + // because they're also used for "unserialization", hydrators + // deal with array of instances, so we need to wrap values + $properties[$name] = [$value]; + } + (Hydrator::$hydrators[$class] ?? Hydrator::getHydrator($class))($properties, $wrappedInstance); + } + + return $wrappedInstance[0]; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Exporter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Exporter.php new file mode 100644 index 0000000..38f7fbf --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Exporter.php @@ -0,0 +1,405 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Exporter +{ + /** + * Prepares an array of values for VarExporter. + * + * For performance this method is public and has no type-hints. + * + * @param array &$values + * @param \SplObjectStorage $objectsPool + * @param array &$refsPool + * @param int &$objectsCount + * @param bool &$valuesAreStatic + * + * @throws NotInstantiableTypeException When a value cannot be serialized + */ + public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic): array + { + $refs = $values; + foreach ($values as $k => $value) { + if (\is_resource($value)) { + throw new NotInstantiableTypeException(get_resource_type($value).' resource'); + } + $refs[$k] = $objectsPool; + + if ($isRef = !$valueIsStatic = $values[$k] !== $objectsPool) { + $values[$k] = &$value; // Break hard references to make $values completely + unset($value); // independent from the original structure + $refs[$k] = $value = $values[$k]; + if ($value instanceof Reference && 0 > $value->id) { + $valuesAreStatic = false; + ++$value->count; + continue; + } + $refsPool[] = [&$refs[$k], $value, &$value]; + $refs[$k] = $values[$k] = new Reference(-\count($refsPool), $value); + } + + if (\is_array($value)) { + if ($value) { + $value = self::prepare($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + } + goto handle_value; + } elseif (!\is_object($value) && !$value instanceof \__PHP_Incomplete_Class) { + goto handle_value; + } + + $valueIsStatic = false; + if (isset($objectsPool[$value])) { + ++$objectsCount; + $value = new Reference($objectsPool[$value][0]); + goto handle_value; + } + + $class = \get_class($value); + $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); + + if ($reflector->hasMethod('__serialize')) { + if (!$reflector->getMethod('__serialize')->isPublic()) { + throw new \Error(sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class)); + } + + if (!\is_array($properties = $value->__serialize())) { + throw new \Typerror($class.'::__serialize() must return an array'); + } + + goto prepare_value; + } + + $properties = []; + $sleep = null; + $arrayValue = (array) $value; + $proto = Registry::$prototypes[$class]; + + if (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) { + // ArrayIterator and ArrayObject need special care because their "flags" + // option changes the behavior of the (array) casting operator. + $properties = self::getArrayObjectProperties($value, $arrayValue, $proto); + + // populates Registry::$prototypes[$class] with a new instance + Registry::getClassReflector($class, Registry::$instantiableWithoutConstructor[$class], Registry::$cloneable[$class]); + } elseif ($value instanceof \SplObjectStorage && Registry::$cloneable[$class] && null !== $proto) { + // By implementing Serializable, SplObjectStorage breaks + // internal references; let's deal with it on our own. + foreach (clone $value as $v) { + $properties[] = $v; + $properties[] = $value[$v]; + } + $properties = ['SplObjectStorage' => ["\0" => $properties]]; + } elseif ($value instanceof \Serializable || $value instanceof \__PHP_Incomplete_Class) { + ++$objectsCount; + $objectsPool[$value] = [$id = \count($objectsPool), serialize($value), [], 0]; + $value = new Reference($id); + goto handle_value; + } + + if (method_exists($class, '__sleep')) { + if (!\is_array($sleep = $value->__sleep())) { + trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', E_USER_NOTICE); + $value = null; + goto handle_value; + } + foreach ($sleep as $name) { + if (property_exists($value, $name) && !$reflector->hasProperty($name)) { + $arrayValue[$name] = $value->$name; + } + } + $sleep = array_flip($sleep); + } + + $proto = (array) $proto; + + foreach ($arrayValue as $name => $v) { + $i = 0; + $n = (string) $name; + if ('' === $n || "\0" !== $n[0]) { + $c = 'stdClass'; + } elseif ('*' === $n[1]) { + $n = substr($n, 3); + $c = $reflector->getProperty($n)->class; + if ('Error' === $c) { + $c = 'TypeError'; + } elseif ('Exception' === $c) { + $c = 'ErrorException'; + } + } else { + $i = strpos($n, "\0", 2); + $c = substr($n, 1, $i - 1); + $n = substr($n, 1 + $i); + } + if (null !== $sleep) { + if (!isset($sleep[$n]) || ($i && $c !== $class)) { + continue; + } + $sleep[$n] = false; + } + if (!\array_key_exists($name, $proto) || $proto[$name] !== $v || "\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) { + $properties[$c][$n] = $v; + } + } + if ($sleep) { + foreach ($sleep as $n => $v) { + if (false !== $v) { + trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), E_USER_NOTICE); + } + } + } + + prepare_value: + $objectsPool[$value] = [$id = \count($objectsPool)]; + $properties = self::prepare($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + ++$objectsCount; + $objectsPool[$value] = [$id, $class, $properties, method_exists($class, '__unserialize') ? -$objectsCount : (method_exists($class, '__wakeup') ? $objectsCount : 0)]; + + $value = new Reference($id); + + handle_value: + if ($isRef) { + unset($value); // Break the hard reference created above + } elseif (!$valueIsStatic) { + $values[$k] = $value; + } + $valuesAreStatic = $valueIsStatic && $valuesAreStatic; + } + + return $values; + } + + public static function export($value, string $indent = '') + { + switch (true) { + case \is_int($value) || \is_float($value): return var_export($value, true); + case [] === $value: return '[]'; + case false === $value: return 'false'; + case true === $value: return 'true'; + case null === $value: return 'null'; + case '' === $value: return "''"; + } + + if ($value instanceof Reference) { + if (0 <= $value->id) { + return '$o['.$value->id.']'; + } + if (!$value->count) { + return self::export($value->value, $indent); + } + $value = -$value->id; + + return '&$r['.$value.']'; + } + $subIndent = $indent.' '; + + if (\is_string($value)) { + $code = sprintf("'%s'", addcslashes($value, "'\\")); + + $code = preg_replace_callback('/([\0\r\n]++)(.)/', function ($m) use ($subIndent) { + $m[1] = sprintf('\'."%s".\'', str_replace( + ["\0", "\r", "\n", '\n\\'], + ['\0', '\r', '\n', '\n"'."\n".$subIndent.'."\\'], + $m[1] + )); + + if ("'" === $m[2]) { + return substr($m[1], 0, -2); + } + + if ('n".\'' === substr($m[1], -4)) { + return substr_replace($m[1], "\n".$subIndent.".'".$m[2], -2); + } + + return $m[1].$m[2]; + }, $code, -1, $count); + + if ($count && 0 === strpos($code, "''.")) { + $code = substr($code, 3); + } + + return $code; + } + + if (\is_array($value)) { + $j = -1; + $code = ''; + foreach ($value as $k => $v) { + $code .= $subIndent; + if (!\is_int($k) || 1 !== $k - $j) { + $code .= self::export($k, $subIndent).' => '; + } + if (\is_int($k) && $k > $j) { + $j = $k; + } + $code .= self::export($v, $subIndent).",\n"; + } + + return "[\n".$code.$indent.']'; + } + + if ($value instanceof Values) { + $code = $subIndent."\$r = [],\n"; + foreach ($value->values as $k => $v) { + $code .= $subIndent.'$r['.$k.'] = '.self::export($v, $subIndent).",\n"; + } + + return "[\n".$code.$indent.']'; + } + + if ($value instanceof Registry) { + return self::exportRegistry($value, $indent, $subIndent); + } + + if ($value instanceof Hydrator) { + return self::exportHydrator($value, $indent, $subIndent); + } + + throw new \UnexpectedValueException(sprintf('Cannot export value of type "%s".', \is_object($value) ? \get_class($value) : \gettype($value))); + } + + private static function exportRegistry(Registry $value, string $indent, string $subIndent): string + { + $code = ''; + $serializables = []; + $seen = []; + $prototypesAccess = 0; + $factoriesAccess = 0; + $r = '\\'.Registry::class; + $j = -1; + + foreach ($value as $k => $class) { + if (':' === ($class[1] ?? null)) { + $serializables[$k] = $class; + continue; + } + if (!Registry::$instantiableWithoutConstructor[$class]) { + if (is_subclass_of($class, 'Serializable') && !method_exists($class, '__unserialize')) { + $serializables[$k] = 'C:'.\strlen($class).':"'.$class.'":0:{}'; + } else { + $serializables[$k] = 'O:'.\strlen($class).':"'.$class.'":0:{}'; + } + if (is_subclass_of($class, 'Throwable')) { + $eol = is_subclass_of($class, 'Error') ? "\0Error\0" : "\0Exception\0"; + $serializables[$k] = substr_replace($serializables[$k], '1:{s:'.(5 + \strlen($eol)).':"'.$eol.'trace";a:0:{}}', -4); + } + continue; + } + $code .= $subIndent.(1 !== $k - $j ? $k.' => ' : ''); + $j = $k; + $eol = ",\n"; + $c = '['.self::export($class).']'; + + if ($seen[$class] ?? false) { + if (Registry::$cloneable[$class]) { + ++$prototypesAccess; + $code .= 'clone $p'.$c; + } else { + ++$factoriesAccess; + $code .= '$f'.$c.'()'; + } + } else { + $seen[$class] = true; + if (Registry::$cloneable[$class]) { + $code .= 'clone ('.($prototypesAccess++ ? '$p' : '($p = &'.$r.'::$prototypes)').$c.' ?? '.$r.'::p'; + } else { + $code .= '('.($factoriesAccess++ ? '$f' : '($f = &'.$r.'::$factories)').$c.' ?? '.$r.'::f'; + $eol = '()'.$eol; + } + $code .= '('.substr($c, 1, -1).'))'; + } + $code .= $eol; + } + + if (1 === $prototypesAccess) { + $code = str_replace('($p = &'.$r.'::$prototypes)', $r.'::$prototypes', $code); + } + if (1 === $factoriesAccess) { + $code = str_replace('($f = &'.$r.'::$factories)', $r.'::$factories', $code); + } + if ('' !== $code) { + $code = "\n".$code.$indent; + } + + if ($serializables) { + $code = $r.'::unserialize(['.$code.'], '.self::export($serializables, $indent).')'; + } else { + $code = '['.$code.']'; + } + + return '$o = '.$code; + } + + private static function exportHydrator(Hydrator $value, string $indent, string $subIndent): string + { + $code = ''; + foreach ($value->properties as $class => $properties) { + $code .= $subIndent.' '.self::export($class).' => '.self::export($properties, $subIndent.' ').",\n"; + } + + $code = [ + self::export($value->registry, $subIndent), + self::export($value->values, $subIndent), + '' !== $code ? "[\n".$code.$subIndent.']' : '[]', + self::export($value->value, $subIndent), + self::export($value->wakeups, $subIndent), + ]; + + return '\\'.\get_class($value)."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')'; + } + + /** + * @param \ArrayIterator|\ArrayObject $value + * @param \ArrayIterator|\ArrayObject $proto + */ + private static function getArrayObjectProperties($value, array &$arrayValue, $proto): array + { + $reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject'; + $reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector); + + $properties = [ + $arrayValue, + $reflector->getMethod('getFlags')->invoke($value), + $value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator', + ]; + + $reflector = $reflector->getMethod('setFlags'); + $reflector->invoke($proto, \ArrayObject::STD_PROP_LIST); + + if ($properties[1] & \ArrayObject::STD_PROP_LIST) { + $reflector->invoke($value, 0); + $properties[0] = (array) $value; + } else { + $reflector->invoke($value, \ArrayObject::STD_PROP_LIST); + $arrayValue = (array) $value; + } + $reflector->invoke($value, $properties[1]); + + if ([[], 0, 'ArrayIterator'] === $properties) { + $properties = []; + } else { + if ('ArrayIterator' === $properties[2]) { + unset($properties[2]); + } + $properties = [$reflector->class => ["\0" => $properties]]; + } + + return $properties; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Hydrator.php b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Hydrator.php new file mode 100644 index 0000000..364d292 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Hydrator.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Hydrator +{ + public static $hydrators = []; + + public $registry; + public $values; + public $properties; + public $value; + public $wakeups; + + public function __construct(?Registry $registry, ?Values $values, array $properties, $value, array $wakeups) + { + $this->registry = $registry; + $this->values = $values; + $this->properties = $properties; + $this->value = $value; + $this->wakeups = $wakeups; + } + + public static function hydrate($objects, $values, $properties, $value, $wakeups) + { + foreach ($properties as $class => $vars) { + (self::$hydrators[$class] ?? self::getHydrator($class))($vars, $objects); + } + foreach ($wakeups as $k => $v) { + if (\is_array($v)) { + $objects[-$k]->__unserialize($v); + } else { + $objects[$v]->__wakeup(); + } + } + + return $value; + } + + public static function getHydrator($class) + { + if ('stdClass' === $class) { + return self::$hydrators[$class] = static function ($properties, $objects) { + foreach ($properties as $name => $values) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } + + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $classReflector = new \ReflectionClass($class); + + if (!$classReflector->isInternal()) { + return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, $class); + } + + if ($classReflector->name !== $class) { + return self::$hydrators[$classReflector->name] ?? self::getHydrator($classReflector->name); + } + + switch ($class) { + case 'ArrayIterator': + case 'ArrayObject': + $constructor = \Closure::fromCallable([$classReflector->getConstructor(), 'invokeArgs']); + + return self::$hydrators[$class] = static function ($properties, $objects) use ($constructor) { + foreach ($properties as $name => $values) { + if ("\0" !== $name) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + } + foreach ($properties["\0"] ?? [] as $i => $v) { + $constructor($objects[$i], $v); + } + }; + + case 'ErrorException': + return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \ErrorException { + }); + + case 'TypeError': + return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \Error { + }); + + case 'SplObjectStorage': + return self::$hydrators[$class] = static function ($properties, $objects) { + foreach ($properties as $name => $values) { + if ("\0" === $name) { + foreach ($values as $i => $v) { + for ($j = 0; $j < \count($v); ++$j) { + $objects[$i]->attach($v[$j], $v[++$j]); + } + } + continue; + } + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } + + $propertySetters = []; + foreach ($classReflector->getProperties() as $propertyReflector) { + if (!$propertyReflector->isStatic()) { + $propertyReflector->setAccessible(true); + $propertySetters[$propertyReflector->name] = \Closure::fromCallable([$propertyReflector, 'setValue']); + } + } + + if (!$propertySetters) { + return self::$hydrators[$class] = self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'); + } + + return self::$hydrators[$class] = static function ($properties, $objects) use ($propertySetters) { + foreach ($properties as $name => $values) { + if ($setValue = $propertySetters[$name] ?? null) { + foreach ($values as $i => $v) { + $setValue($objects[$i], $v); + } + continue; + } + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Reference.php b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Reference.php new file mode 100644 index 0000000..e371c07 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Reference.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Reference +{ + public $id; + public $value; + public $count = 0; + + public function __construct(int $id, $value = null) + { + $this->id = $id; + $this->value = $value; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Registry.php b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Registry.php new file mode 100644 index 0000000..19d91c9 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Registry.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Registry +{ + public static $reflectors = []; + public static $prototypes = []; + public static $factories = []; + public static $cloneable = []; + public static $instantiableWithoutConstructor = []; + + public function __construct(array $classes) + { + foreach ($classes as $i => $class) { + $this->$i = $class; + } + } + + public static function unserialize($objects, $serializables) + { + $unserializeCallback = ini_set('unserialize_callback_func', __CLASS__.'::getClassReflector'); + + try { + foreach ($serializables as $k => $v) { + $objects[$k] = unserialize($v); + } + } finally { + ini_set('unserialize_callback_func', $unserializeCallback); + } + + return $objects; + } + + public static function p($class) + { + self::getClassReflector($class, true, true); + + return self::$prototypes[$class]; + } + + public static function f($class) + { + $reflector = self::$reflectors[$class] ?? self::getClassReflector($class, true, false); + + return self::$factories[$class] = \Closure::fromCallable([$reflector, 'newInstanceWithoutConstructor']); + } + + public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null) + { + if (!($isClass = class_exists($class)) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $reflector = new \ReflectionClass($class); + + if ($instantiableWithoutConstructor) { + $proto = $reflector->newInstanceWithoutConstructor(); + } elseif (!$isClass || $reflector->isAbstract()) { + throw new NotInstantiableTypeException($class); + } elseif ($reflector->name !== $class) { + $reflector = self::$reflectors[$name = $reflector->name] ?? self::getClassReflector($name, false, $cloneable); + self::$cloneable[$class] = self::$cloneable[$name]; + self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name]; + self::$prototypes[$class] = self::$prototypes[$name]; + + return self::$reflectors[$class] = $reflector; + } else { + try { + $proto = $reflector->newInstanceWithoutConstructor(); + $instantiableWithoutConstructor = true; + } catch (\ReflectionException $e) { + $proto = $reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize') ? 'C:' : 'O:'; + if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) { + $proto = null; + } elseif (false === $proto = @unserialize($proto.\strlen($class).':"'.$class.'":0:{}')) { + throw new NotInstantiableTypeException($class); + } + } + if (null !== $proto && !$proto instanceof \Throwable && !$proto instanceof \Serializable && !method_exists($class, '__sleep') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__serialize'))) { + try { + serialize($proto); + } catch (\Exception $e) { + throw new NotInstantiableTypeException($class, $e); + } + } + } + + if (null === $cloneable) { + if (($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType || $proto instanceof \IteratorIterator || $proto instanceof \RecursiveIteratorIterator) && (!$proto instanceof \Serializable && !method_exists($proto, '__wakeup') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__unserialize')))) { + throw new NotInstantiableTypeException($class); + } + + $cloneable = $reflector->isCloneable() && !$reflector->hasMethod('__clone'); + } + + self::$cloneable[$class] = $cloneable; + self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor; + self::$prototypes[$class] = $proto; + + if ($proto instanceof \Throwable) { + static $setTrace; + + if (null === $setTrace) { + $setTrace = [ + new \ReflectionProperty(\Error::class, 'trace'), + new \ReflectionProperty(\Exception::class, 'trace'), + ]; + $setTrace[0]->setAccessible(true); + $setTrace[1]->setAccessible(true); + $setTrace[0] = \Closure::fromCallable([$setTrace[0], 'setValue']); + $setTrace[1] = \Closure::fromCallable([$setTrace[1], 'setValue']); + } + + $setTrace[$proto instanceof \Exception]($proto, []); + } + + return self::$reflectors[$class] = $reflector; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Values.php b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Values.php new file mode 100644 index 0000000..21ae04e --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/Internal/Values.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Values +{ + public $values; + + public function __construct(array $values) + { + $this->values = $values; + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/LICENSE b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/LICENSE new file mode 100644 index 0000000..69d925b --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2020 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/README.md b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/README.md new file mode 100644 index 0000000..bb13960 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/README.md @@ -0,0 +1,38 @@ +VarExporter Component +===================== + +The VarExporter component allows exporting any serializable PHP data structure to +plain PHP code. While doing so, it preserves all the semantics associated with +the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`, +`__serialize`, `__unserialize`). + +It also provides an instantiator that allows creating and populating objects +without calling their constructor nor any other methods. + +The reason to use this component *vs* `serialize()` or +[igbinary](https://github.com/igbinary/igbinary) is performance: thanks to +OPcache, the resulting code is significantly faster and more memory efficient +than using `unserialize()` or `igbinary_unserialize()`. + +Unlike `var_export()`, this works on any serializable PHP value. + +It also provides a few improvements over `var_export()`/`serialize()`: + + * the output is PSR-2 compatible; + * the output can be re-indented without messing up with `\r` or `\n` in the data + * missing classes throw a `ClassNotFoundException` instead of being unserialized to + `PHP_Incomplete_Class` objects; + * references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator` + instances are preserved; + * `Reflection*`, `IteratorIterator` and `RecursiveIteratorIterator` classes + throw an exception when being serialized (their unserialized version is broken + anyway, see https://bugs.php.net/76737). + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/var_exporter.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/VarExporter.php b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/VarExporter.php new file mode 100644 index 0000000..da9a8d4 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/VarExporter.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\ExceptionInterface; +use Symfony\Component\VarExporter\Internal\Exporter; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\Registry; +use Symfony\Component\VarExporter\Internal\Values; + +/** + * Exports serializable PHP values to PHP code. + * + * VarExporter allows serializing PHP data structures to plain PHP code (like var_export()) + * while preserving all the semantics associated with serialize() (unlike var_export()). + * + * By leveraging OPcache, the generated PHP code is faster than doing the same with unserialize(). + * + * @author Nicolas Grekas + */ +final class VarExporter +{ + /** + * Exports a serializable PHP value to PHP code. + * + * @param mixed $value The value to export + * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise + * + * @return string The value exported as PHP code + * + * @throws ExceptionInterface When the provided value cannot be serialized + */ + public static function export($value, bool &$isStaticValue = null): string + { + $isStaticValue = true; + + if (!\is_object($value) && !(\is_array($value) && $value) && !$value instanceof \__PHP_Incomplete_Class && !\is_resource($value)) { + return Exporter::export($value); + } + + $objectsPool = new \SplObjectStorage(); + $refsPool = []; + $objectsCount = 0; + + try { + $value = Exporter::prepare([$value], $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0]; + } finally { + $references = []; + foreach ($refsPool as $i => $v) { + if ($v[0]->count) { + $references[1 + $i] = $v[2]; + } + $v[0] = $v[1]; + } + } + + if ($isStaticValue) { + return Exporter::export($value); + } + + $classes = []; + $values = []; + $states = []; + foreach ($objectsPool as $i => $v) { + list(, $classes[], $values[], $wakeup) = $objectsPool[$v]; + if (0 < $wakeup) { + $states[$wakeup] = $i; + } elseif (0 > $wakeup) { + $states[-$wakeup] = [$i, array_pop($values)]; + $values[] = []; + } + } + ksort($states); + + $wakeups = [null]; + foreach ($states as $k => $v) { + if (\is_array($v)) { + $wakeups[-$v[0]] = $v[1]; + } else { + $wakeups[] = $v; + } + } + + if (null === $wakeups[0]) { + unset($wakeups[0]); + } + + $properties = []; + foreach ($values as $i => $vars) { + foreach ($vars as $class => $values) { + foreach ($values as $name => $v) { + $properties[$class][$name][$i] = $v; + } + } + } + + if ($classes || $references) { + $value = new Hydrator(new Registry($classes), $references ? new Values($references) : null, $properties, $value, $wakeups); + } else { + $isStaticValue = true; + } + + return Exporter::export($value); + } +} diff --git a/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/composer.json b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/composer.json new file mode 100644 index 0000000..6ccfc63 --- /dev/null +++ b/user/plugins/admin-addon-user-manager/vendor/symfony/var-exporter/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/var-exporter", + "type": "library", + "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", + "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.2.5" + }, + "require-dev": { + "symfony/var-dumper": "^4.4|^5.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\VarExporter\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + } +} diff --git a/user/plugins/advanced-pagecache/CHANGELOG.md b/user/plugins/advanced-pagecache/CHANGELOG.md new file mode 100644 index 0000000..ad79ab9 --- /dev/null +++ b/user/plugins/advanced-pagecache/CHANGELOG.md @@ -0,0 +1,53 @@ +# v3.0.2 +## 06/21/2020 + +1. [](#improved) + * fix default value for per_user_caching in admin [#18](https://github.com/getgrav/grav-plugin-advanced-pagecache/pull/18) + +# v3.0.1 +## 06/21/2020 + +1. [](#bugfix) + * Check for user when login plugin is not installed [#19](https://github.com/getgrav/grav-plugin-advanced-pagecache/issues/19) + +# v3.0.0 +## 06/08/2020 + +1. [](#new) + * Allow disabling user on login [#17](https://github.com/getgrav/grav-plugin-advanced-pagecache/issues/17) +1. [](#improved) + * Fixed conflicting and misleading messages [#16](https://github.com/getgrav/grav-plugin-advanced-pagecache/issues/16) + +# v2.0.0 +## 04/27/2019 + +1. [](#new) + * Advanced page cache now works with language prefixes +1. [](#improved) + * Switch to a disable toggles for `querystrings` and `params`. Default is now to **not cache** these. + * Blacklist and Whitelist now work with extensions + * Optimized code to be more efficient + +# v1.2.0 +## 07/14/2016 + +1. [](#bugfix) + * Fix issue with URLs with extension being considered like the default page extension [#10](https://github.com/getgrav/grav-plugin-advanced-pagecache/issues/10) + +# v1.1.1 +## 09/02/2015 + +1. [](#improved) + * Switched to `value_only` arrays for whitelist and blacklist + +# v1.1.0 +## 08/25/2015 + +1. [](#improved) + * Added blueprints for Grav Admin plugin + +# v1.0.0 +## 06/16/2015 + +1. [](#new) + * ChangeLog started... diff --git a/user/plugins/advanced-pagecache/LICENSE b/user/plugins/advanced-pagecache/LICENSE new file mode 100644 index 0000000..1cc8e9d --- /dev/null +++ b/user/plugins/advanced-pagecache/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Grav + +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/user/plugins/advanced-pagecache/README.md b/user/plugins/advanced-pagecache/README.md new file mode 100644 index 0000000..e9e41c7 --- /dev/null +++ b/user/plugins/advanced-pagecache/README.md @@ -0,0 +1,56 @@ +# Grav AdvancedPageCache Plugin + +`AdvancedPageCache` is a powerful static page cache type plugin that caches the entire page output to the Grav cache and reuses this when the URL path is requested again. This can dramatically increase the performance of a Grav site. Due to the static nature of this cache, if enabled, you will need to **manually** clear the cache if you make any page modifications. This cache plugin (by default) will not cache pages that have URLs that contain either **querystring or grav-paramater** style values, you may want to disable this behavior. + +This plugin can provide dramatic performance boosts and is an ideal solution for sites with many pages and predominantly static content. + +| NOTE: Grav Debugger will not display when the page is cached + +# Installation + +Installing the AdvancedPageCache plugin can be done in one of two ways. Our GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file. + +## GPM Installation (Preferred) + +The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's Terminal (also called the command line). From the root of your Grav install type: + + bin/gpm install advanced-pagecache + +This will install the AdvancedPageCache plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/advanced-pagecache`. + +## Manual Installation + +To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `advanced-pagecache`. You can find these files either on [GitHub](https://github.com/getgrav/grav-plugin-precache) or via [GetGrav.org](http://getgrav.org/downloads/plugins#extras). + +You should now have all the plugin files under + + /your/site/grav/user/plugins/advanced-pagecache + +# Usage + +The default configuration provided in the `user/plugins/advanced-pagecache.yaml` file contains sensible defaults: + +``` +enabled: true # set to false to disable this plugin completely +disabled_with_params: true # disabled if there are params set on this URI (eg. /color:blue) +disabled_with_query: true # disabled if there are query options set on this URI (eg. ?color=blue) +disabled_on_login: true # disabled if a user is logged in on the frontend of the site +per_user_caching: false # enable per-user caching of pages (if not disabled_on_login) +disabled_extensions: [rss,xml,json] # disabled for these extensions +whitelist: # set to array of enabled page paths to enable only when in whitelist + - /cache-this-page +blacklist: # set to array and provide list of page paths to disable plugin for + - /error + - /login + - /random + - /dont-cache-this-page +``` + +If a **whitelist** array is provided, **only** pages specifically listed will be cached. Language prefixes are ignored, but URL extensions are taken into account. +If a **blacklist** array is provided, this plugin will cache all pages except those specifically listed. Language prefixes are ignored, but URL extensions are taken into account. + +## Important Notes + +This plugin is intended for **production** scenarios where optimal performance is desired and more important than convenience. `AdvancedPageCache` is not intended to be used in a development environment or a rapidly changing one. + +Many plugin events will not fire when a cached page is found because these are not processed by Grav, only the static page is returned. For example, because there is no RenderEvent with the cached page, the DebugBar will not show even if enabled. diff --git a/user/plugins/advanced-pagecache/advanced-pagecache.php b/user/plugins/advanced-pagecache/advanced-pagecache.php new file mode 100644 index 0000000..c66a99e --- /dev/null +++ b/user/plugins/advanced-pagecache/advanced-pagecache.php @@ -0,0 +1,103 @@ + ['onPluginsInitialized', 0] + ]; + } + + /** + * Return `true` if the page has no extension, or has the default page extension. + * Return `false` if for example is a RSS version of the page + */ + private function isValidExtension() { + /** @var Uri $uri */ + $uri = $this->grav['uri']; + $extension = $uri->extension(); + + if (!$extension) { + return true; + } + + $disabled_extensions = $this->grav['config']->get('plugins.advanced-pagecache.disabled_extensions'); + + if (in_array($extension, (array) $disabled_extensions)) { + return false; + } + + return true; + } + + /** + * Initialize configuration + */ + public function onPluginsInitialized() + { + $config = $this->grav['config']->get('plugins.advanced-pagecache'); + + /** @var Uri $uri */ + $uri = $this->grav['uri']; + $full_route = $uri->uri(); + $route = str_replace($uri->baseIncludingLanguage(), '', $full_route); + $params = $uri->params(null, true); + $query = $uri->query(null, true); + $user = $this->grav['user'] ?? new User(); + $lang = $this->grav['language']->getLanguageURLPrefix(); + + // Definitely don't run in admin plugin or is not a valid extension + if ($this->isAdmin() || !$this->isValidExtension()) { + return; + } + + // If this url is not whitelisted try some other tests + if (!in_array($route, (array)$config['whitelist'])) { + // do not run in these scenarios + if ($config['disabled_with_params'] && !empty($params) || + $config['disabled_with_query'] && !empty($query) || + $config['disabled_on_login'] && $user["authenticated"] || + in_array($route, (array)$config['blacklist'])) { + return; + } + } + + if ($config['per_user_caching']) { + $this->pagecache_key = md5('adv-pc-' . $lang . $full_route . $user["username"]); + } else { + $this->pagecache_key = md5('adv-pc-' . $lang . $full_route); + } + + // Should run and store page + $this->enable([ + 'onOutputGenerated' => ['onOutputGenerated', 0] + ]); + + $pagecache = $this->grav['cache']->fetch($this->pagecache_key); + if ($pagecache) { + echo $pagecache; + exit; + } + } + + /** + * Save the page to the cache + */ + public function onOutputGenerated() + { + $this->grav['cache']->save($this->pagecache_key, $this->grav->output); + } +} diff --git a/user/plugins/advanced-pagecache/advanced-pagecache.yaml b/user/plugins/advanced-pagecache/advanced-pagecache.yaml new file mode 100644 index 0000000..e4c30eb --- /dev/null +++ b/user/plugins/advanced-pagecache/advanced-pagecache.yaml @@ -0,0 +1,13 @@ +enabled: true # set to false to disable this plugin completely +disabled_with_params: true # disabled if there are params set on this URI (eg. /color:blue) +disabled_with_query: true # disabled if there are query options set on this URI (eg. ?color=blue) +disabled_on_login: true # disabled if a user is logged in on the frontend of the site +per_user_caching: false # enable per-user caching of pages (if not disabled_on_login) +disabled_extensions: [rss,xml,json] # disabled for these extensions +whitelist: # set to array of enabled page paths to enable only when in whitelist + - /cache-this-page +blacklist: # set to array and provide list of page paths to disable plugin for + - /error + - /login + - /random + - /dont-cache-this-page diff --git a/user/plugins/advanced-pagecache/blueprints.yaml b/user/plugins/advanced-pagecache/blueprints.yaml new file mode 100644 index 0000000..6482346 --- /dev/null +++ b/user/plugins/advanced-pagecache/blueprints.yaml @@ -0,0 +1,103 @@ +name: AdvancedPageCache +slug: advanced-pagecache +type: plugin +version: 3.0.2 +description: "AdvancedPageCache turbo charges your site by statically caching pages." +icon: dashboard +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +homepage: https://github.com/getgrav/grav-plugin-advanced-pagecache +keywords: "static, cache, plugin, performance" +bugs: https://github.com/getgrav/grav-plugin-advanced-pagecache/issues +license: MIT + +form: + validation: strict + fields: + + enabled: + type: toggle + label: Plugin Status + highlight: 1 + default: 1 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + + disabled_with_params: + type: toggle + label: Disabled with Params + highlight: 1 + default: 1 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + help: Disable caching if there are params set on the URI (eg. /color:blue) + + disabled_with_query: + type: toggle + label: Disabled with Query + highlight: 1 + default: 1 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + help: Disable caching if there are query options set on the URI (eg. ?color=blue) + + disabled_on_login: + type: toggle + label: Disabled for logged in users + highlight: 1 + default: 1 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + help: Disable caching if the user is logged in + + per_user_caching: + type: toggle + label: Per-user caching + highlight: 0 + default: 0 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + help: Enable per-user caching for logged in users (if caching is not disabled for logged in users) + + disabled_extensions: + type: selectize + size: large + placeholder: "e.g. rss, xml, json" + label: Disabled Extensions + help: URL extensions that should not be cached (e.g. for news feeds) + classes: fancy + validate: + type: commalist + + whitelist: + type: array + value_only: true + label: Whitelist + help: "Enable the plugin on these page paths regardless of other configuration options (don't include prefix, but do include extension)" + placeholder_key: + placeholder_value: /cache-this-route + + blacklist: + type: array + value_only: true + label: Blacklist + help: "Disable the plugin when calling these page paths (don't include language prefix, but do include extension)" + placeholder_key: + placeholder_value: /dont-cache-this-route diff --git a/user/plugins/aura/CHANGELOG.md b/user/plugins/aura/CHANGELOG.md new file mode 100644 index 0000000..0f69c39 --- /dev/null +++ b/user/plugins/aura/CHANGELOG.md @@ -0,0 +1,52 @@ +# v2.0.1 +## 07-07-2020 + +1. [](#bugfix) + * Added check to include metadata saved prior to v2.0.0 + +1. [](#bugfix) + * Updated date published/modified functionality to ensure output of valid timestamp + +# v2.0.0 +## 23-06-2020 + +1. [](#improved) + * Changed the way metadata is stored in frontmatter to capitalise on Grav page caching. **Important:** When upgrading from a previous version existing Aura metadata output will be disabled. You will not be required to re-enter any information, but you will need to actively re-save each page via the page editor to re-enable metadata output. + +1. [](#new) + * Metadata input moved from Options tab to Aura tab in page editor for central editing location and to enable overriding of individual meta tags + +1. [](#new) + * Added support for individual author per page via Aura Authors plugin + +1. [](#bugfix) + * Changed storage location of Organization logo so it will be retained after plugin updates + +1. [](#bugfix) + * Fixed issue with URL extension appearing within page image URL + +# v1.0.3 +## 29-02-2020 + +1. [](#bugfix) + * Adjusted scope of autoloader so it will not interfere with other 'Aura' prefixed plugins + +# v1.0.2 +## 09-02-2020 + +1. [](#bugfix) + * Now appends to existing metadata rather than replacing + +# v1.0.1 +## 06-09-2019 + +1. [](#improved) + * Get language defined in page frontmatter with fallbacks to active language then default language +1. [](#bugfix) + * Adjusted JSON output to suit Grav versions > 1.5 + +# v1.0.0 +## 21-08-2019 + +1. [](#new) + * ChangeLog started... diff --git a/user/plugins/aura/LICENSE b/user/plugins/aura/LICENSE new file mode 100644 index 0000000..4a23cf9 --- /dev/null +++ b/user/plugins/aura/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Matt Mulhall + +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/user/plugins/aura/README.md b/user/plugins/aura/README.md new file mode 100644 index 0000000..a119c7a --- /dev/null +++ b/user/plugins/aura/README.md @@ -0,0 +1,42 @@ +# Aura Plugin + +The **Aura** Plugin for [Grav CMS](https://github.com/getgrav/grav) adds meta tags and structured data to your pages for visually appealing and informative search results and social media sharing. + +![Aura Plugin for Grav - Demo](assets/demo-composition-min.png) + +## Features + +* Minimal configuration required to automatically inject a wealth of structured/meta data into all pages of your site. +* Supports the following standards and protocols: + * [Schema.org](https://schema.org/) structured data ld+json code snippet as used by major search providers Google, Microsoft, Yahoo! and Yandex + * [Open Graph](https://ogp.me/) meta tags as used by social platforms Facebook and Pinterest + * [Twitter Card](https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/abouts-cards.html) meta tags + * [LinkedIn Article](https://www.linkedin.com/help/linkedin/answer/46687/making-your-website-shareable-on-linkedin?lang=en) meta tags + +## Installation + +### Admin Plugin + +It is recommended to install Aura directly through the Admin Plugin by browsing to the `Plugins` tab and selecting `Add`. + +**Important:** If upgrading from a version < 2.0.0 existing Aura metadata output will be disabled due to a change in the way the metadata is stored. You will not be required to re-enter any information, but you will need to actively re-save each page via the page editor to re-enable metadata output. The new method of saving metadata capitalises on Grav's inbuilt page caching for optimal site performance. + +## Configuration + +You can configure Aura via the Admin Plugin by browsing to `Plugins` and selecting `Aura`. + +There are only two required fields to complete to get up and running: Organization Name and URL. However the more information you can provide, the richer your shared content will be. + +## Usage + +Global settings are configured at the plugin level as described above. These values are constant for each page of your site e.g. Oranization name, logo and social account details. + +There is an additional configuration tab titled `Aura` available on each page in the page editor. This is where to set page specific information such as the meta description. Other than meta description, data required at a page level (URL, title etc.) will be automatically inferred from Grav's internal system settings. + +Additional metadata can also be entered on this tab. For example to signal to web crawlers not to index the page, enter key: `robots` and value: `noindex,nofollow`. + +Individual Aura generated meta tags can be overridden with the Additional Metadata input as well. For example to display a specific title when sharing on Twitter only, enter a custom value for the key `twitter:title`. + +## Credits + +Big thanks to [Yoast](https://yoast.com/) for sharing their wealth of knowledge on structured data and SEO. diff --git a/user/plugins/aura/assets/demo-composition-min.png b/user/plugins/aura/assets/demo-composition-min.png new file mode 100644 index 0000000000000000000000000000000000000000..221c7a3522017810498649e5b7504a18bb94af99 GIT binary patch literal 135930 zcmcF~Ra{ix_ckJu(mk|viNMe$&5+XF-O?c`NJ@h=NJ}?JcT3IC4MRwG*MIQ)yL@lo zxtPykpS{<9*0XA#b%yXyiqhz)08}_QICPMVgbExSLINBdk{|MG*oaAXmIoXheDWuG zHA(5u@~{!u2LS=$zl;CAU;X?1cMT5@54(U3kdcuQlTiT3C_aiwn3|eONl7s>GMbs0 znVXx_(lfHKvcU#nBlPt2uo2jJR(3W60|RUt7B2qt^D`3@leDZHY>t4ih>(bwn7E{b zw2X+TIIn;pOg=p$^XBFjq7uKXyaFBp@!{bSB@NT{&CTQEBfpUF`NahhfQm-DXF`=dj{tB>Ka-f*g2k_p2;cRxwyHDOG<0&>eJ9Mass)QmX^7B`IJ7XL`BDx zmX)KR;k~1#9U30Fy1G&h+u!;77Zn-p`1FjGmG#feazR1C%*?F0r8Sm@*y_gi-@Sv6 z_>6?Q(qioBT#Tp;l&D=zmCAe=#vd_$B&UpZ*KZ!2w|8{b)>JKzG>=U}va+*C2#{43 z1pEWP+Btk?$E5V~{?hon$yo;7NJH#!x)bz~^IK?GL;26O_4UV%en$Z&YZEzLAWg6m zrWD(|NMq5I!pfS~o*-$SkD;d?8Xt))A;yw?VJW#&%j@P|q111Q%JLGuB2zm12id8K zJ?&JKNZG;lnJb;ep-QqpofY4+^0|l!-v`?ysmmUXlx3$xZ%6Sz_J2(`)M9$a`Z$sH zxLEzzYWI9J&sMs*RrO`G`1?|p<+m?}IhK4vUh|oWetRA9K~p!W+89#-T%HbQ2RqQZ znTzk=yatoLo)()4=wvpyImm07k8IpD`76dpSQlBDwK;LbG#-z|=py?z_5^v0f;0dw ze`hEOwOjUT%ux$!nnb;Pl&Dh>8r#p?I+oRTn~c;~93s0xQnfR2WU`wAX`7?b=Rt;FNiz0mwj zm;dzzG|4VoccZoIZb-Er4j%IaBq6Hixp0uF;IJqI2$?pvl;BIvf2BBxV(*+GTIRk% z8G!T(o(9>3(w-!YVE*ze(%)s4J96KE3|d=#BL|=vzJJLeQ+SgVdv;{eQ$&pMup+Ml#_fozUxPE@wKB4l1^vPz|(Q> z`vH-_BrI9zh<)&C$E^eN>Qe%a4bBAy7m6JhV{uYJOBqO~AcszYxY13)IWL`ntquGs z;mA-?_7`ZvR#A2#36Pxz^xGaL^`Jph7I9g!DM)oJ2VqllnIJ7vv?wRgd1_@}xM{8O z(+^D?aR@>Q96xZOgiKe&KNSLWcW6??UL<`kb)WvC2;ZAWH(9|!5S^U6r~0V3vLo5< zlk%HN*MUIV@Arl82$=J<^MH49Jp1P`s1S)jNhj6;%GTpboe#+KbPoqFN-BWAeeQM7 zp_reYE?kUk0{`?hGDy&)$6_kREGNl!p!Sbpu%O!du#jZ9&#*`51&J75gugY(B>Yji z{N}1q-7a@C|F^dhu-5DsWf}RNYEnPFfdSh_h=jqDORjWQmCq#acOEn6!=X*#j9;yM z7cAkj^A^O2Z>Px@rqOLw>R4Jb%0Pk5HIy~e`T1I(KW*h}=_eRv*~;Qi{%~_18~W=U zUxuPAo~t+RbJHd`byI*juen-1OXKDnIVXLPE}v%0HH4;4kDL8#%D+XnVK!f)67)@Q z>r?2Rcaf*KqK*w8;}jw2O<@LJv8@0u-JAZj1fL*@tmYEU=oZ{P)?pRkR8nUkXzF5R zf_;;Qjpa+x5~oYnH5EPUJ{L%0odNmXuAEgRC_F16{(#!oh|=3IUM_cwCN*EII!=wr zY^gsb!3O6xDXWP~Z0OsE57@#IDwG|M)w&w?XTD}4k|Aj{=-02Q3Oofv9fPu!;$1`x zxH5(jKKd~@$Z*_}Ue}~R$8dG-1CNmeF``8JsUn@Oy*iBf{R+H>)X0goKoP?JQxbzQA~f&WM%8>4a?Hz|Xr zT|IkMiHbgoonAdu#=))}6orGg803;iXB}!BSc7RUXIL8Tka63u*9GUIayZ9Yf!Qiz zi*NRp&)R~;cQ|3NY7DK$bR48ZeQw7!giR*vOFr;a5=uocscWlJM?<{ak&#bfh*VEy zr<;(r)1uxH?wl%$|bFl;H`XM?Z@ejEaEeo;s} z=B3}V<}W6w`t(C8Euocwuh{EdImvPv-6$)U_4>`8n`ZdVH4Xi!dtyR7H=lK37Gl!p z{zWU7_*riHT`0vNFFk%_Rs`mlCpCTQQDk59VlLA+JX&Wk%&2yJj6Enmm!x;Z|YVf4}QpdE>K2&8SvSlvYD#qt{Zx3iQc9BgWe_j>vq)K5Bz!aVR_a+d2X0?aOrZ_~wjz=L-`P?!t@7R*Aui_5(8ww227i)!_eoQ6y=`bQR;1FH!phig>HX zxe!9*67)LU`=I)x^xvZPofw%>rx>r2aA#=i49>lmSPeW9 z+$y}E4$Ol1pwo^mHtjRJ)%{)wQ26wulK0V#S0WvG1_!Mzvo?&5U;v#7YJI0-Mz zB<6=f80nk#K)+Fvj~0Re?Z@{~_>f5FC2GfQso?^$pu6pQw9^86y1ujKxeX^ZgOi5p zoSO6nUo`p>MP`&wW`Y}%B@zY-xqG>DXF_z5d$pfSwZ~YK0}|WRU8|esLRIhIoZ0%> zUb8Oo>NG69x(I3cOvZ)*Kc3pOt8{)~b6`)=bZX$S?X_Z;C8X`!<5i!2m%y@=dgEpi z^A-4he90YWl8D>D4L$ELcT_2d8gTB9Ib}zgG3Mi1_hX{RxAH(6vFm> z1Wy}iGt^p;bwqT4^#3Z=L~!TsC!z4YL2bPj#&|yb{O4JpE); z`F^PrZy0%lid^d8W0)lK0zfQERIs;2m3axf<~P?OLe2&y`Nm;4s>c+|Q9#t!PhVim z97-fB{l@}Nt60ksa^6#sZ!C{R4y8^Eg}l$wy8KB18>qi9(UBEwMJx0B^aHk_*=w>t zA`}-&Po4ROeP2{mOt7~$(s_)I+9ILy0%R87H z`@d?q)!i#je(hxG2-XYi^CiKaVY4-=_EQ^0rzS+EMI`RxcHY2)6lV#TOvvcmL7$R` z#$srQ+ZqM&yiK@vOfy+RsVq$Ta&MK^>A9Bmt|+to*OZGCXLZCaiMHS7b)@{avBFQY6!FhY57kKv{c)36>M<7MX%fmXl#6yDIhxi6 zc6*;N`$0aJPZ!Q@2G26L*);c8(%z6eXJ7ZUv!~H?9>Wg5@tc$N^U&kt@tIOP=<3tm z{W2 z;o#_YfSgZc#Pf1*daGzesZylP?{W9+Y})+J#XIl!{sqg9+C>FKwffe)`E+Oq)O=gy z3a!<=t!?vk^7M4QxBxB35IBFYWPq974JeK+e!fAOv@w64%x3s>ZmyK!ONu>Wez&{V zw>}AZT}#$Pniun8S9_?-bg3FUDl10z<5P`?aCEeM>=dRxreyS~^b5rf7MpFdxX__V zr-hs=qy7=Y0h3;JXb7Qn48Fj3=kGQDw&TvD>+hO870!=+@Z&;du8oBTcPnP{%uT$pz@PQGol zmYOcDF}(WwySVrA^0I}z={JBzq*-_^mqYS4S#q}s$%3z6D-jkL4QZ2M<4HuA(OFz& zgNXZgcja36r#Xask2dqO0}j-6D~mwBIvUn3BHI@j`z9d^r4ps!zF3V`d@&zI+S{wc z#bg65eLX$K=bD(vjdP5SCR+}wUOF_=+VXq=viX$GGvx%F^?*(n|tP<{ppB z`{`#XMGnc{qKor|*1)UFhldTZIVv43VeC{t{)c?iDOc4m zGH89QA!Oz7E5=+YAu8KQPvVH5VXv#Po~nP|g3HD$mvzm3I7&GCQGa$zbB~2**vFG= zKg9CL?x;&q8I6Ha63x~AVu{(kl=csKVdM1%v;xyRk6v+3I_NESx;ALL72RI7C6NT#XSm169Wx|t1Odt>|kdY<|Q{&QlvQCVN*KlYGp zU-0H_PH_0d8As_pqQCHA5pJnQ5KgzaX8{oWrJ9jAyzI;EOcLagRT*B6|za-&^cO z5Ynk`GbZZ{@%eqHp2J?ZcMZtvMEo;P0#j%@nV6b*Md~1ww-nK`=ryg;31Ca~rlscg z1gt_~(&I26rnhflWO4Nghk&JSH@Lb6ehU2aK(}AmuNL!I&&GJGr{ewBZOVNClx-qv{y%q~lZ0XU2|#N0ZGd%js~r zAEZgO1o%jlanU{4QP!yhy>##gKUqhySJYbR#ju&bOOM+9$yTBr*m4Yp9$3vX(hmGqFbB9pQ2w0)skw>Xf%*zE-K z(i{u?{piqZub;e+3a>Bey1S9eVaffgkLa5pg{(D7d#V_ik0i`}e&X zF8XKr7dz=8Wl<#okuvA zLv18iHcOUDM%biZpcV#e^B*i(^ot|Mbq?r!>ITQ4z*fyK)ZlF!C;SDhsEZ=~wSPf6 z4Q8sobRrt!J;a(WJ>5&kncjg;*ZcxYYkk%U7KwJ}Q~)mhlf{B=twYu;Kq0hc9y@kk z-!q;l$vie63D2s!?o91}aQtHjKj42{;M@B?5RC33A$+Vi_ze1-$)dwnk@cRZCe9`| zp)`sTKnn{24H@Rij}(3Ab#DTtcEU^T7TsNzxw_mc&T4sM)m-OR-7q4~QTty!&ww$? zRi_OdvM13cP*cFg)HFyKvhv}Vy{#8o`hLhP3&C>9qcx9t&p0zf0uM~GtiWGA9eYV@ zH*a`x#OerWM8j+1jY!%oQ`L=2n!)tHYm3wpxac*?FrFQ!>+Z11IN_~-wgCwGk|u4W zIZ|%n({UY)(MZF}lX=@tD-pI$c%4_B!=3ClUtNIJXePKbt4+-prpLkj| z<{ReVPniZw%k>NbNfTh6GvYQ;1&N4txmBNOdV9^bb9x#FHNsHkkEFqIF8C%uc^fX2 zIP~I=r3KT985%;o9AUIdX$=*N{*UWVYnPp*1IhxCC2A>}PZ-F(QZEjs0to-szh)~f zA|}r)rfBZuma`vnpDi*M#4zBbV1&iAga7*<`OW}kq&63K(s;Z5&^yP5ndV6ldiD^{-_&Bm6_W1VM0AMuk| zg*=az?WWL$gG06Q?LT6aCSyG&EG-fAl;_>1L3hYeo>KEBUp4#A*Zvll5&^`%GeoX)_>YlcvWpqvC5*IWs+KtmhMq|y z*})&P;z-ZbQ9c_g{Cjbnqo3VDWX&1k>Z`Y zk{0Y-?!|y$RZm^t8gJ*mQ8)y+B3d9Sbs0oiu)Z^Gye^6jXZqsQTfm0AWz3&Qvkx^k z3{;whKfB`ZDHcBvG-1OspY=8MZyu7PqfpJA`k+`AaW&5GrPpx(8NL$O9_4PfK1``` zNSPnu9b-YHd3=3^V;cp>{gU=|!=*eSlf-6#%{m7x6-;G9W4&r2qsXNfNI9o;($Mcl z2KFDtUIi~%zV%m{fxPm5AxcS>y80eFQ%@jJJ}BeWP;LYkO?LQr_(2lcndX;&OlI`E zOF$9$(a}_35vNBis#lE{MFto83by5aM~*S=p4sJnH1{NW*TM~s-G+zF6BuDhmC0tKm|4=;8p=awT z+nN~E{rjw)^PMD=aQYv~8vIR&iBfQRoav88_2UoW4~ijIGLF0DaqKnH?juA|3lHJZ z2RXuE>4zbrvkqeJUXqxBrfZ@{7>W_m*nuR^gORat1|hl^-}M0a>0DYswC}T3F}}UE z$eW*Nw&4va=32U{_A3*Zns_TM;-J49J!TiQTX7WPNV-ghLfS*6UrXlKW^>bL{Q<_I zLDbri5~MqS^1LS^C4bTyH`k*u7&}OBUQ?>Jlus)A3cWwYp5?}o;HrB<53<=IbhXM$ z2X$-~kJBQt4rP`ZOu`+Eh1}r(m(QcDwa}f*ScmGqaM5c1p>c2lWo~KZLHpvb?p zAW|V~&A|5(-i=^==~&&rIrXiXvAd(Rq3jkK&+aQubhF%T?M+3E$8-;OgAIa` zUpK|s)xgxAcBJ+fvpS2h**-fbXAx6}RqG{z^O>cl`onLFj-&GmEyowBlO7JbrtXjp zM|tSX0fyJ>Lwt4IudX`d5`h#i^{A8#{WxXN5_4c@yq903ad7sWkvPMjmJCu5vR{Mf zq94>T0f^oQ0%r>zzRg%**e*urHOkkrpDC?G|H{S52j(wQz~V$Oa=}}XzSD6S*5K zK`{z7b>umEs@8>`Qv9g3{=Ba2F366|a6lh6{*#fhk)+_CLJD0x1T=lTD-d#}#mSyz zPtPQDYdL=z%|QNnU}gEBFwWcwF1cA3lJ~2?!7avuo3lm&MY&p8CnZOoC6(ME9;G|- z*GHB%%cNhL2H!p6=Hx}-YFH#?yGg2K?X^6S;s(1_c!qi|gmyGUi8YPo;iuXzMSKLR z#s4T>XWT0{-l%5^-xAg?UJUby)FjqLAJKEDZkfDGlRD!;gcIzPLqXr7u6@>nwd5@S z1|DS}A0IlAZ|px#EX?hXnBl4l?EFBl0|i(T2~t~2^u10OJ`-vOFB^Q`+Ue5Dy)$v9 z`l$df2wg7-=@<4t%8~m^#nUE#NP`EzK-0-dZeU|&mbHM*YbPS90MR^=1|qnyZx zCX)kFGlb++YU!KSR>l&GvU(}V9!g7-CRTk((MT%N=M3Qh~on1&jpk!{qz37nOh)ylM z_m`k3B<2Bs7`QC$6EJLgrjjNV_E0F6x>z1;%e=LM=>DgEDj#JOA|-f1$a+Dv)b=M| zJ#Vq!1YX1uh=+&&NC@!A?4M=)SzXD9@Z&RQn3U)}Y$G_fJ|G8X-H@b<6PDYl+~0pD zK`_9Js{B(}UCA8$X1ByhA``(gAXyQRT7*&-lKd6xR1+f_Z5}?GKLRD7vxXj`)TzL2^AvUZR|b3QePuZF zw}*{#6EarK!?Qhv3il2$1wC?3@e4}nf5uI@s%Mu>c=*DoURl zFicc8GkbcZe+ITM_Ip4P1v?|>50t>3h=9K>_CCoVm|$MOa*uydh!kzzs~0n`@A3^$t(S^j)0hchxyhjo1e01oZoq;rlpM}$;1AsUg8yig7(rh_{WtaVQi6$f zs_V4+Zb__f{MLtDm}Yf7k$hggV}a$y%{G_-GW?(`n>TwS$!v|{&2!B^ax*k0!}F9A zk{#NlFfX(!vC&I@aycZxVV*b?81GBxaqI<~&}iIXKR@4`Lb+F(7ZXu5!kiZqCf}nO zYbO=91oYQ47nKs~%)Vfjo8X=LE@eJQ6A~2Y%`p)w)kMo0c4Z_p-!Mspv49h5l?1gy zARg%;`kw(OJ)MgQ(W#In@(km~%Vawyt;l3LiVaI|!fHg9z2D>-T;mcZ zPe^~G=G>#{loNtcoSdANG;xtmmW+(JQ!#yasSDD4KXjsX_4C-vmpq-wySqlm0U;{w z?a|Ea-wNCQe{qQeyx=d0lI~En{L$JgD`HNvJ8X?AH~6qXe8v1V4@{x5on+|1j}2sy zoBWt(C|c@j{t4#zjG=@)v|qfMhG%qQ9D57>x=e3JSbVRP=B1jMG*}|dz7NZ%b=yP@ zhWu&nOL5&=#ltKV<*GxmeFevMd9qE7;MqI#tB9~@-DFDWiwl7_>q6FS zRvwjiQIU`(u45Yw+TR9wCF)Rurtc(C)jL{W3!OvG6zXN;c1r|WroTG0QSB%E6KHMa zw%5^~j}3_v5qQqrc=+SL{N*00Z_(hSTu_(8fd>%97zH_Jj)+t+V@5HiG@r<@%hqiA zd@9e4*zFt<30Bv4J5fh@>`sJ*tqKNPbcls+{fMCh^_>!32NL`GY~?fZ2G>r3(M+Il zQb7OaaLmx1z$HD0{_vE~_VvZpkX5umZY*MyCHjk4X*~? zQ$?Mz*h@-p38&YC20d8MDPM(M35(P4qL)Uxv~t-X?iwE!O-hpJ;cLy`NF z1mU-7hhGm8j$OFj`|^g|3qMl!XL+t|>falxPv90hB?@um!=fy)Co=E!XgRP@JSsQW zR8}(tma&t)J)MeTCJv_j3nrYTo^;TjrYhAev)Y*iZZv1U zOs6)gAw3HfaF6>hpIB?{OciNTN43>=oYFx~60QB`Tz-ZSPS2)S1Dj{9NUcTs(LDor zl%Na6&lft1Bl;Wj87xVE?4P<5L$u=i=?lA3_i|0b6~I*z;TqRGh&I+?zoQT@4|Yp@ zSJ7d`$rsqrnDHa|Ce(e(IYjWr6F+f6kmpNnu!FqZUlsbFUBx!e4X+4iWv#B{O8=5x zvavDbv|JuV;qWiNR7(W5qq@UnW_#0GA%G9V=7$t!EPPo*-+CDdbk=4U@u&i$UrxV- z!{U^n2|siC$^8YBYU?`=Cq6;zZV0;)75{w7v;LPCd8{5T5u=WuF0VUw-8{JoCo{33 z45!U%d)ttFB&E_H*0K$MRBIfELhjx9X-C?uDsd-L6o!7#Y2tc3s@BkQ(aM=d<#l-q z5_w@9m*Wf33KX!9Xx~M@WMN5gn4s|eipQ$0R1jz4A7N;YPAGT9&9_>4)dWyg)(=fc zyZ57Pc@e%Kt^n!x{!C(Y|J{W?Gu+Y#OS##FJfr6+MN^vnzbOT_vtad1OUv;7xbOO- z|LPg7l}{ckx?I>GtBj-Wh6sjljFM?Xum7X%LlEr~6_Ids3hfiO@d;ss8h&iqYPksb z_kUG0>bdR9>VBj87cabplI0>Q#Y&=n^{O@Kfl!a9KS998F|julperYKXa9HxDO3!D z&;Dvrv3PmsHAV<~sMx~W+>&jxQlE2(x<={C0pmE@cD62`D%k4!K2O-b87r;zufF{I zZZu(U$CXx;HUfi=V`U@}*AE4MGYvAXG*)u=-=pb0uu}6!`Ds5=uuN{e#<0SjCMjyj zaV@Z`!f4*N(S9Lr+37INj~2*=8Gcn`^ei&zz)7u>$PRnl_oOpvyB%KDv0cKc<>886 z)`^59G3BV&lnmo|ABTybznmQ$C;72iA=B{e?Ag^k7qC2E6W>MpE2<1Pb@6NB(-zpO ziy*(D_oK3^$Hhg!r`B6^*NcqCRLn4L{la$@G zgJ!8d?bmqx)%0n)|4s|~oQ(R*{N`Y*GH-(lHcl~smn;dkuPlk8W_?9=MYH-#ae3XU zI}@J&P0EwpW6kbRKQYqhXCnWlAwBPz*Qd5vEFH59M|Z;#W>;_0++ElM2+bVh7^smf@x6@gM;t*0<- zpOzD)G@viIFoCXx%I8kMCE z&F3uQEAqcy4h0{@p$=`?h-bb^-h+CW?YJ=7&3OclX*uV1W)h@c6s|EaaDd zYRD~0n*sUPcp!CHGh4%TvN0c)c!SBW>cD%4#utF|FdIn&l0*iY%ipY66YvDcOwRRi zfo2FuEJw!|7^L^opZ_p6T})(rmAIp5bF>rdG_pgsCcaOX3W1YnJm0?_orAK&`dVGv z;gnH56f@cF{vqsgn(Ti6+BPyl_pV$c8bxCv=YB8UFWBG4j|ZL73&d;By?RTM-QAp6L-(u`F&HC${+pIg`B+kvs>KvE^;h)nZU4?#W$3dCyb%O z;ar3Fg^5)KnA6&O7Veg(PL^CO)9u_dcC`=hIa)sVo#V>6eU~FQM-^q9^WB7te0`!46#nQtodr+rUZC9 zK(qAt(yTRz3CSOs1K$_E;wAS5Pj^=d6AZzc3}VQRY(CMTCWf(2Yf2n$T_UMVWHGCqq1ury>yn%)7%m>}xN5f#Al~f`tTr z?z7SY<|=j+-~T3qKkfZaKIO7bEJW9A)=|_t&I78~N06sC)I&t|= zq9t`GO5$mID%QpCR%GYD2CJX*(X=q?C-tX;r;*b8DM?{LB}tw&z!Eo>?Qd|XC=RlX zHAMX`h0nYB3vz0%@aQVvxw1TmfS!#1o7&7+{L6`KNa$rJJ|ATELCN%IEpX*aOg;UlXT6gdUzIJ3~CW=ruT|{>IDf zI7MzjgReW!>?`Ekjk!z<*N~3js97x4s~&pjA=DY%HWu2 zYyd>Up>?OitFTm*$mAt`WpU2(QwX=NKAs>cGbPu3v;+9w#R-$uA1>U=5?JL7UU77z zNjU%%X4<&byiqWJ4#r$80Q=>AP!=-ku-w%%NRrvw>{{`OS#Ld|jWDFuP zHmBHIw=ogRTqkW&;HfC0S&ll5O!>E~>N)>!p)z|~^#xacsfK_wRgn-xt1PTY0*r4!ZiS*Yt@rGKJy3@i z!+@Y8$8xo30T3xYiNjc%hKQoHD7gYaD9yz=_`kZB_`WTfBK4;_LG%YB4So=4j6lz=t! z9$&Qk3&koUzT{NvOD;hexCpGi6aJ_^&1GNXdC4M#Ya7^^P-tWu%k?a|eXT`JqVlyW z_A{{c$35DA36m0tBdRpsZyslu+3)G3y;%jz1cY9mFHocaPV0DVwU?%lY2FF*h8YM}X9!jVEQa1;jz{)4IvSoh=ZnPl&m|0a-a6;o zT9cwV1>RS`yiB~$%h@nryyh#TaBQ|~A&l~TVDNsaI@nqCQHyq;0l`0`+Kz4NkS+>N zd(H#|tx3_vz_9RF4&8CPfnIS}qob>Xo&2E=!2W&jF(piS3jNjd5LepZjo&2Qk%EG3 zfK^B6;shm)HXGC&WEfx3uOxzo8AX5O5$x&z;*{yu2>Lp<26)8=ipI9b_F5uY(G(;{ zJe~!m)PKb(P;$aR$ZUHjt%?E*S%_4{HNTQ08UN_8kfvSxS?@th z5{I0o7y~RmU|<~q^JG>MMZapx?{xeFrNOgpR-}C~VmL4_=w0_>y>6TaVzX2-pWJGk z=2N}pjed#vTkwtJHAfT>BPL`ovjTzYEsQ)W!SsF+4Xf5$*n_YrfV67^n!6a?-@ zR2n~iLl?)J&0m&Dh`x9a>z%zkhh z9x5ns#A773VkGU;mYNjMKW&J)?_3|M(7+(!i&v-}nKP zIe#{l5csfYl(qhNS7T94pVlReLDj@$olbBs?e?d=ZL}(M@rU*Hn!$)!f%&sTSo^N* zBxkOHM)-!pYuP$eaP&z?b0NCD8{eD-ut$-!Ea|*P90bV^5=ZvDdqQSScoZUr$m6_A zFO(1Ko!d$qHtso3Q)+Q(PFq@4LQYNnjV3EziaisJm4K(pi9hq>(7lTzsa1nyyFShz zome+zG-u(x8dcR+J$&L@Ynw;o(FvOAKj@qxbayTjsGJzSzkZ@m$EOu0t$VPq4H&Z* z8`bctacj%Nqj_C1j<)lbrSbRXb0_AQHT3F?sXSQ8)H|0j;O_v1SqWZQ(R5=a_@N3C zBxupq9i*sD12eJaYZf+SN;zb|$NG38_qA&yV+t0HX+o#!YSE-kF>0**(%`Vkv zLZ&GSz!WaHUEn0t$9yKLSdb2jdel#Abt_GHbjB@jP`u5IPpHH}IT=BBfH-7Ey{?op zANlt|c^NTkT-qz%l(yU+V=JDwYIBWs+{IT@0Tct_AOat{uu`mqG!0y^+Hjo+s2yGL zK+|0Q=2xonWK4)bQ1T$76chqWtfVfa34Q=Udb&$WqzE0hfmon6eVcXTmz)G=22opb z%I-K3u@Mg1+)e@$6?((NsuVRN?GCrOlPiThCy~^pRiRX5`MiRHusBP5oqD`0!{mDN z$%)NEMB<{qhTn{_wQb}0kOVR>o=uJqycYXwKIpN0=rd~3HfkV#OAw4}tf}?prwlH8 zCjxMw+6V zyC_lsK~v3w`NdbjMz>`Q1h$WK`@KZ(*wd04UXirFiW=F-vIR%41=6`qznZ-!jcVPr zrTk>UR)tdX2JWxk9c3Ap>BCIn>}5#o_1#ZUKsGwOBoI`+tk~RzwqLZ2A0;9!W_m$y zDi*Fi6B!UQk!RByWJ~`)U<(4Y;Qk%U`4I13~H0EqrPTvJSQD7#|Z5KlN#22+rkKgmP5udt>28 zAKRXAfW?@l+SGP<@=HzneZjba1Hd6fk^ReXI&Fd}Y5ZK$PRc|l!8e_<)WH=zVL2e* z$bA8RE1Wsk*R9xOO3w9C&0TUt=;pw?3u_>12*5P?veg{p%R8(13Y#BD_icsl{5`Me z8_>rltjjz2;NWZN$37F4xUF(0tL-TQ6|6>1^e3<{7)T`)TnZbAC-#0x;U3-0rHgYF zuTQyt_VtaW96+ELZ4p;&u&*STa=;m9q)kBgcNBZQRQv7(-CVqxf5}B*0bei|^k@Jo z528CQvDN-$Hmw#%@|UZAbJn3ac*4!Z3f#%+#cvg#+f=w~^X@pUCx^9wv^9A0#`NN9 zv9U2vq@iYFEy43C5wP>^I29Fq3_&=b=px!MV-)&axTzx6~U{^g1}MJ@t?GjVk$C zjKGNx@?@}`xrUvRy^K($_FkzsDXGl7Qb^MvU@-r!9ex+<)%FL4cs&oeG&4xBnRV`A zT2P=kSNNZPAoB?TtS(?ujCM> zNt#eN$^z_e1J)3_z#Cq;+$q{9xR6D3Ami2=ZFtDLn3ec}#=Pff8K1i(q239aF?dL+ zyMXHt*PmCf6*h9>HOUwOq0OkwpSE?x@wjQWZpx{uRw4*Xo5D6aOGRzNWGQXqu|q5# zMsprP=wqi66Eq2m%^0HDGP+^&mA8)|y3`~WApwJQWVVnz(UQCG)+(}{d1+J1`#(Xi z`4`zr3HFu1;dGM>1vp~V(RAo~WWeyEOooiz8D9liK2NFv%i|vvKXkB%wEq{XK=H zZ&+4tcK51M%(H{ns60`!N_^{7*OL8WYW%{{JIXp(2(I^d;IUdCVEE16M#Z|SODc$+ zIK!_*i#%G&pl3On3hJErh;sx}>Wi8yeO(b@%i1;;e8EA`V%;by4#y3L(Js{TW_GLS zFsU(_0Oj7wAvMWuyxi?*v`<7&|I3NRU`W~1^$aUkLniv~GJ{z`eSMbS<=Pyc(^fKg z=>4z1^>pM}sjbH|!uG{#^|X4ReGlNMdiB|e2HVM=JcI@Tvnz6S{kbPbPrV3HVTo?j`f}zKe3M z;|edA%jKqy=*EkNWP8Hp)?QbC1d{h7kj8I)m|Ncp2(!`79#9{!%;$FGb|Zv2(DX-Z z=wJ{H)OgBWT+aD$&~a)QOoM$!QQnsgK0O(LG#$rk1+rf3aB69m$!3T_y&!>yliLk9 z@3wP5#?ej3FXm1*KNLanbTI%=URM_Cqdr>qB5}n6cy#0!Zl|xx`%yf{2)V}a20ZfM zcB=mO-gs79T_*GLhriG0KSu6uh^VC4i`4N zfK}qQxD$=*A!^GfN&RJ_;AYV)wzp9xYU#+d z;y>cP4`9(6yenwqVBO!@zdN|2L14S2djxf&i9@GL&v~toaU9n?u89;uDY@*N6T+mS zUT6RD;H+Y{vo+#O1IRCQJh|x;5<5ZXSoxh&QRq71>ZV<8p_zcyS|Tnr zFl5qdB}x2g!wo+cG33)#H#UZg6Zhhy86I8>~^Ic5Nc z1#Rz#b>>MdUQZxILC@IRnD0MtioxJWh}9L7*T0{UER}Q}B2dLJAOcK6D8l+prF=2a zPsW7_2AX~?W3}dQ^h`^-*7a*rsGu7RhA(7_Q324gOS~Vf)1|>QS?&>4u3IgZ!-HN9 zJ~&@JIzEL!Beg`Az@I>Dp6gaVQb3ggkV=45Dz+#xAKmycA1tICi*gSePa_H0-kS{E z@UWM!eakr=8I_Zu&P;PX*eR*Wt~uL+;mb6U!Lue&cxS*^0GomBWy2ahI`|F{gGggy{Pz-nh>Mw#eG1!5%+@N# z`M;`ClmMlC&|Boa9AYxQQ<%jSofgxQ^AEq{gfC775{5`!Fv{@!Jauq>b#HNo5$J`i zz=X8R^cxGPSbLm+1LycGmeQ&fVb8T|@-F{vYZbwBON;+eL_HJHCNOme5pKf9)F?s+UJ&-Zzc>re)xYN@C)x7GsGYmpir1!uwFrP(Wj{ zIEauuwANz`<&bPQ{x_%ZCD*?FJUt)nFgOYcaZLD1pCGAl zhFW>p1zH~zWF`%lp4sP=+x!zGrnI3K3jO{X#o?hAl}8YV7ozPeHUk#&It(jha!ErR zD?MrMna)@Vv=tGUNZgh_T+3JD3V$yZ`l>C6Q+oa28i|70CDKA8;em0!&o$}roSya- zd2>FWvJSHt+FjklshfAFZcM+2$iod})m*Q$A$#nPNrypP3c{fx1Ab@a}_Wj>0C8LU@;aZCwlOd^v-9)NQ9S(lJ4l zkJuWUU3dF*-|>{)cJ8G>v$%2de@wk~SXnEe^q5OL2-@ zptuKjid(Vb8XOA6Deh39@}r;c`&`%iXRTE_6`!_fvYLbWt6Ed`H#e0JbU! zvPNP>h7-z2O#iu8x87li3h`tx$!`ebU^T;@sR>uY#?rLXB9zu1l1&?90ptiJfp^Sl zu}&t3q+ZU17|8z4Q-7SxXn3FVpG-a2AO%{T&Fx*n%=?l~eG)^sy%~gNIi><^)KKNh6TlBiVCWp$>_r$F)a(1Q9?{*F+ zdsZ7rvfMc3X}VL-+q|=B#NL{O4K>aYSG{N(k12Gtwc!Bpf<+0gY>}i%C_*(z2L$Nw zfAq?;)ofzu(Q8Pfr^QXJw%diw6QVLM*#U@(x1(u^A}ZsbiyF`Zt*ugc;__zju%hXu zuw>f_Pa@RaKi)R{9o}wu>-Z*PFc2|UQzOv_WGnW3k*pY(vM)FHZUcWKhvIKZ8QTgw zT3ePT-ysEyl`J_RIBsO|#DhMVnUhe(=w0zEf`O!lb(cbStoDofdiYH4q>p^kV)2T|a4G5Bnv( zBn9U_2UmuY+n}ug&dJPOMbK{;A2c_4ZJC)EOT@oF7ezXu-+9uxu!($j?E^Qam497| zrGFiv-*LWvIA1g$blT{zZ}L!*htn_VJKJU8qmzpuMP@b)wK?KsRe%$B3Z6@(>b@nh zA&O0Rm|+gG<(yMZQ?VD6S`joX+>rqNo+#4M8$b)`o zs|=7rM7e4HM*rp-%KHAVZ>@Z5MX2?tw?QpiHr(Ka^2xiP`~GZw_3@G|O%PR_Jdd** zD8B8#u2Q>;rj%gA#Si=I)xT!>Kk?ragTx3ny8I0}aqAx8C>_nA;Y01Ojh}#c|>w;Y356t;1==nNtbHKGJCv3I^t*3ob zgIUnjT@!2WB5fBcm_iK8EeI(hM+cKGB;nk8#$XXdPF}*} zM=9L+cf=d}@R`oIb0~8+o3fQhUp4Q~E!ctBG;}cjNd;dB){iAp@u~77@HSMYwSsM_ zc1U5wJnlq^_lZ)-XD$B1X5ps?$WQe764Fh`W!w`Cd`w~*iZ9q#R+<4%qF1SC>UlIl zizsm-lp4XLE>mx87;V35dA;QZdWZo-@X!bEHX*zjL^|ZLgfx;Y?VBP1sdy@k^V2|^{9>5gTfJ?tq2KfNPqR5nffwBNOUeut&@@(8{~LFTqLb`$H>VEh!9d@8SV-y|%J8Pz0tTo( z4at_LWozZ%5O*~6%>h3hz$m%KRd$wq>h7b#7h6QTZ>f^NTW^z25qfYP9@2cAgH&K4 z$)0YsD1(6$EsVLF6^@yt+FU73+?hpW{_#g=<$QqCfu*FFk-F&N@Wh^GezGSJ@;QKnP8LjW(Pw5JZQ`$kfWg^&W(SdnLJJqF3s{aoUqrV_T+f54h&5giUbqe=ZpoaH5c&N)uXB@t^Cz#)r`(CQBwTKUb>L zx3D-6L%SY^&-;8_?mmJw5@$E#crlNTfSdNoB$jjXgyG?+w2T&;Hd-+eo)7#IEU#Mm zzzG$*j%+mU2AUMgwp1+6t3B=Rnzbpwk35Yi(RZfL=hKZoDd>L@@{W9*SzkInT6Sj0 z;Th)v3#MBQ6|9z41mb!R4-yRa&#s%7P$swo4Z+XkEUlkk8tM>tLl^ z5y6xMa)Ht!vUn=F;o=)Lieg!;!Vn^_qDyGXg z1OcUejd|$5wBS|c;}TF@E4dwK09|)NM5$7g$1}n&9|K)&HC6$zU9zv}mRietX+TnS zOgfq@fCdRhbQ!>~G7Nl4k5_IBP-dko6IhsnHzkL!x;iY{t2P*aKm}h|`ej@3DQ8J! zJ7I5~9F}~H$97qRP>=!KpWrIp*(kv|wI7A8^rqc{fHb&n+|c)?2$?T`oSsEGA_`7$ z+GJo3u8FI2VQIoj`+x`b<0HpG!EEOdxLD@rPbvOa^jm+h1TpE~l`bxSOU{kzKD*5aMpr|pR=y8*6&^Fcw5TR9wwJqHy^UH-sY?X@j<_2I&QJW_T zO>8B<7Uxaj>YO(QuNzGPhdpnTBX8MM^}nDID7)MzfRojkrhNmF!5&7Rh=gL{Km|u- zw~*8YK4&b~_d;GRzXo0u_us5$gG+b*kc77mN+_k@f!CAsic2vV1O{ht*QqiwK@L6S zbjf!8&o`wQvaI{_6=vpEwra%>&&hwIG`;h^k~((-HAIj3R$F)Yf7Ez9t+CCxne)%k zX(V>38-kOpoLIY4Whof`ING%3uHYV64V)!$j&QpBo}Lm4nlwQVWnk=K z=c*N8aLhg{at3iX`EUzuSIr|3IJJk1h~R}5eBZrUb90kg<^~VsL33}+qr4DTAsCig z)519Xv2Z@nI+@AMNuC?YJvd>-e&O(Fvg(aD3gZ;qcE3%(3Cvl{e{J6?{~pm1-m04q zJS+~_eQ8a2hId0L?EbCQ-=+6Q=*F-`${x%rkooX&Glha3MK%>YQe~cA*SXT~-{Hfp zCQ!?3(*5!bRXGRzT#szh#)b;Q8gBbHbnMs27y)Lv?o}l z-Am&A0JCS+%Wsm<5#-J%(=sE$I8%hO@uzwyeT`NW5OA7k2&Hzq0U2d>vQ-&Q0#&dkHdR-#% z=iB*1;jw!9O=DLPtj%Ba?>slusa>?ftlj_5_ClCcg*l?q7=bT~6G_Z{c)e&JYSav# zTVsV9Dlp}W^XAH}G>*rCT{F|!tYr>=&3%2BD!()T_)*OD;HpDS^l!hwyx+&26HSLX5XB_0NkRTzS#FVGN_ww?x)M@onUzpyy)BDLZ z?K~Om!W&kg;6U z+!%MN-MkiCRy=hzO9g48qZOF3gV{ILdU$G7C85o*X zG9xlap3*B-i6bl&5C(d?9C@RF8~stH(DB!Ox6h{mn?FM2Y=cJlH>g{TCB!}en01|f zIXPQI8eLy=$pv*zPR$|%>MfPllN&JG-+5veYPDjQ{Z(nQ_^ASu&-3?|c9#wI_nwQg%|sE%efF9v$Iu($O%fVvIH z*6kANTeHF@~u z*#^zd8!u9xkc|Vo9)CU!UDv%aeV10b(^ETnab%nF{!*7%zG9%Iot)HmhMVPGt=2%f z`#<+h&jIf|7U(o@tq5iy1#cHS&{Gh^&{_gtn8OP(8RRCVjbga7E|R-c(B;;+9{Fj-mRQw%(Rxy$h-2yv)z7APsPXJWm2(1CNlrXIwcP1&*L{N1MxriKt^kjU9ROblCp zB#qDz{qXz5jkh99^rqn@6$D_+zsf=jqV|W|i!#IoXUO+tJF$_&bO2oE)1F$3?jZRe zY)QP)YPfxJ(bIX~lh?EeW=S8EUIuGJidp8#bg~~6a%417rAU)`SemS7YN&eF);UCe zSgHVap({_%h1hV;v(-lhJG;~7z?t4^ujq6+^trG>@yDTBUn36+NZewABVg=#gNK{L z_=WasJX4BxA%qbkF!x+|U(uU6aNdLw!H$Cap28sfWIn@{h|3$3%wMoF2|w0YN2ke2 zOzs5(0Du6SwR=ZlT0z_ioJ>Tcz$N!Pl2lFvhSr%@^LDenl2vn*H66hk(RW`u7gi-PcB%A_d9F4!fIFWyGM6gYL} zh*#z6MOn=-#}ZLPJ8&5^?bc+6v{3}H&eg-EhD4JepyrFC8y>=A18 zauXgP;@?$%t?YUHin#8e*$Qtd=J5uCQne)j@uz48_&qW>x$t2IUbx@ZWfkhwY-MKbD{f0^%BsJd z1=L5=P{q}JC}53P&Ma8-);u2;fyMlZzXemYeDc(yS&(~P%9`iAC3vHxajN(uIKq2e zmXtou(3|<8lCWHXZC}DKPAso=JGftcdqeNy4|gUi?Wb4D=f?tFk>EsArkd7PVh;US z|KO|wor*2+4-^11rXdyaEC}RRj>7yKph;B{Mc2UawVa%%i2O(@)4znK^mn!=AH?~t2D@|EF=VY-x}u_<>|u`dWt5sxi3;&A714jriYwskb7Rh;EPoDU9)|n) zXw(qFBNU84zSLwJ)4GIwf98m zF?ZJzD<@7}P{xS|)!-O|>xbCdBsD;3gvxWr9k|a%*S0`tHW=$}FaCA!!YRq80M$Hv zOrTg@T#d~$+#v>{j{$?#Fbm;J&i}9iUy)*A z-izfA!WigOpCfB#>Bv6|Ti|p?CAt?X(CZOeDsT4oskIYXq@$^x2QyL6GCHp}AAfS^ z@1Qo34@2nCX1V%x0`(! zsXkvW8BxI^WL1M{Yn7O&IC^y3jDPzDHejf3uy|XDSsYkxM;U;NBcBP|3NuQ#`nIlS1o_*;9~A#R#N`;20&N8oo(LW%BFhm+P}uNe%F64(+*B|LmdQKh z(Z8y_lD+I$5pLsWc<%wD5Je;@hlvgzGPl;(2b_4>JXawJw$;`}$eZyMmZ@8{#s06E za8!vBOaLp?^?3uf3%31^&T-LH(hYo@?5Gz5-@zdogv}R$Q7}!vsBFUSm7}$(AmEf$KW?jOfC}P+C5?K3?9Kq%SgLO| z-%B6aRj**sd#thVGFeG{9W_t`_*&xqk9?~*jVl%uJH)Ydi z7ET}B1(o@cz!+SB-lG1KVlopuCkej+uMGuC_u~;Q`xtVw{EZ12&3p*{Yd7h(k3@7= zs6~+-DK=ghM%ba2*gj60%L~N|mKMBz%!smEIzX>D7fgx{P2)NIo|Zi{zLQn(JPGAG zIIWFBEcmOip{yR)sig-u$-r;zv}z)Kf=c`LG`Wz2K%Yk}$WW0ysL&ps7K(o24)W37 z7UODR5Eo;C-263!ho&>rt@zgd(=uMWOV`6KJ8YLmf5x@DUCtnC-7{2#C6qjh|Q6TuF5T z?WbH#x0U-fOjOM<>S|}ioRh8rUQp2Od~6Vluqb}<%p`J0MA^NV*@`H|kG2!}fF5j;iTS z4R^^!h`+3gOF+n+@AQNfw(R}=%6Ya5Rvjz!rp&C5i~}i%lg`b}&_B389G%JPI&6zJ z20x8nZkG^pg=vtq?+z-g{St|1uJx+axp!LHtu{EYT*iAmfZL6(UbP?i&G03@GCg^t zSGTm;rb5J@Ua3L_!`>-oEX7?~r~vsH2i$ZC2(gHtBYjYK1v-oampVOFt&8(qU@ITA z#qv~apB?Q7?s(CcWPxqZG&_RGVUIg&vKoX^MO98Y8rLpAY~x`5o8>?(H-16Cxw~xY z0=SCHD$gJW%Q|f$ezFQT%aGE5JZUJnH0Dw&BV0z`w$NG~ml!K-uMjJlVkR0C!BL|L zn)SP5T#h%EDm$i@X^F%pv$7bmu-%Q&wAw_2@#KDN5DIxQ5)N*1Q+>PpI@gjg@Rym8 zIz&pCPG5@<=KWaJSaP|gy`%dd$kH#dL*Q0uM$;>131c1K1uz40qp|>PezKqwh5Z_e zRmht=vv~SI5fAg|D~zpnJM&SKc(`jbR!M?c3XU1w7@Q2)1LcXdV+|k#%@tzla@#t% z5-Wk`&>(ZdU`?>s1b1c}gS5hl2iBb<%h&e_*(9ZqtWX{!MCJ(wS`>G*03Hh8(hm+$ zvP7zwpM`7WB*3aEsWf*RVt;z0x5o$dc>@5`IDkx4kDOwjfj+Fjje2-66TkVB5}SG< zxDR_{K8z7a+0BxDO$|PIK^sIT6huwOPb$>G+bcbQCY4?X35nK#v&DfKl=A!TbO5xk z(eQN36+|V4Wds2kDOp3{5p?pJUQ#3zhTn0FNjQQ}99XYFbn56)R%5vtyqKy2!BU21 zO(pRg)>@xFn!Lrq3|8B3_o^Qw?`%fA{AJL?-@|W;qAUQLQcb*nu>-H^GrGGt-6@H< zXGPelH=4{i^642#W;i&M=^b3KB@RCbcp7x&dw!8w(6N#5R+2vb#z#HmR`=Rqg=2#B z?k-P^AZW$uCRrR$rI=edy!6{{*qF!aZejJOkJk*WAB<=i=;Y z1>rJM3zzMQ6GlQv>m@r~Lx^NAMe)=GT7E1oz4cjYloMLqkE9c(oj@ ztT|71wJ{LvVyQInMSoSvr8nYEhvD7B=3n|}86x2yzL!gCRYvI3PO1eiz0MUDrPIh7 z*Qe_;q;N?Fuct@97f3>{b-XeIJwx9UFUSevin1$n~wHtp8 z|Ldah_*pm=F$uW8ep4s!+suC?eE1fAQ=;khkA67^(MX%xD10cKawJ@#b}$6qdwKlw z44s)+x%q;VlBJ{*kic+={14~nL2wCI(6{%FIyfbUzUg_|+j3cNOko$S>5+{)#S+xr zesm+SU$?m3*3MzJo$Wqf>j;_F*SGK=YptNaw0KhOLBvbjrJI}ahM-Y>P?vp4vccOO zq5ur&^qG?J>U3RD2l}1Q@CMs4Ev7r}Vu-|QKMY+-ae1DMY%Lf<5p}mYIRG+vZ z?-q$)R-oUT%K0z_w(q)^PW13rRuc*uNnPw`gEGSZ+&{@^WCeYZ+)6t?ER|W3jmox% ziSZjV@Vn_1KJQI3D~xym(f_(S?dLfONZlX8Ea&SDDQ%>a7?Q_I+Z<;u`F~`U!+w~& zGxuH*Mz0ajS7`Ib&UfAxh`un$Es=JLZ@aw<&B(3J6>ObIc*Vw_!y`lWQFJrXlor~y z^2FO>Xqh&S^4r!L<9sjEu zx(mV!e4o-YDYJ)(!%q}?u>b#LihA#4vBSJ*84fi?%Ev!EJi(94oL+MwxyJX z^jtJOLpMj3-1)Q-hQUHOkyKZr&x)5zz*KTXOT!;tYw;V!;!r#&7+(3R;mDWDE06D}+70b5dU~>LX03__bJH zJ}0IzVGFh_zJ?qjE%$(gcr>iT|NvM-DFGUf4*4`Fr;6NGr}19gvF#yfgYfui>9jU47j!`xfyn zV;C%{aeU&IY6Tfff7V@vy3ZI;a)NMiErXo@_2-FFXV)0}+V*5d7v47puVk()rQ`S9 zJ-%UaW1mYeV>1r$w`2}ZaA>QXN9Hb@E~OB;m8=xuJI@^H0at zI;+3DR%7+J7`og`8bBZ7Qlup+tly4s%XdU4y*OM}Wo#{?!!*QkdU1L1$J64F8CY_GpLFTU%)GF`a_>)Eod^y_5Tb_zreaN20$;}K5)Us;Ht zy~YHENI*sB0gdW7RbS1qFfXzsM+70(uZlMCw^x*~vScU9Qo-=W_vB}*REH$Dhj)GU zXdT*JQLA& z8XOz7;>ACeA!k4N&l&p5vQt%a;=mQRcQ}N39L6!HGKI?mz{u=hR`3svYp@Jgp&Yr%4$yV5C zy)ZNU3sAW(YraasdiIINlG(h514UkKC+n8q-wQ^JzTDfuia!Rtx+66%Veg-u3W+cK zmx;2UJn#3`Pu6UwSM1qV|D4%>;EgD@*(44c??sWG1Ca(t-_$j{Vajl{5F!0rAnwg*Xc(i8wvpa1E9O7G_Tq52xS<6AfiQQJQD>`gw3*8)^Fo z1+knsxA3)}(HYb&c{GSzG8w)UsX?$tK+Vk(QdYIecqe|i=MTUtu^fR(`S!vG&K9dE zcTDwO)^nyx`O=C_`n2~9_yQzssKSlJopkkXt5wYt{2kI^%K>eOIx z^y?9b`}Z1AsU<#Qm&^^9@IZSq?2`+K{Tr8nRC{tPHT@ppF|3Rm!EPdDre1j12uufq z%L3||Yu~I(RS>OGQ>U$_)@E;8{{7Yq-C$0~{yn2Je&EDiTzz|$cYYRXlRv^eU@=pb zvLo5h;`2UJp!t<5XWwMz)PV4wRqnj?yDX^3cNd$G({rNC-H`E~^K6cUY@Uh*a6G1G zctxu)?-)GfV0pc=uxF=q<@}&kLCl$|hTD30u)C+?^sbF&>(IWzS5-c*shT=?u=AA} zf-a7UvF_Z)l;oSVvSnbaTE`ouh48AD43_UI?Z0nI+NATk32x;HYgfZ6(^D|UPg}fK z$X!(&SH$z%)gPVvCMRAP&4GS5*pTIRsZ2Jtp9_8Nz4M7NG(0w!!G%hEcUpP7CHzMpE5<=P- zTO1`vfI)nG_tg5hSE;q!bNfBttItW6Sxjca#pQq6j7O5g{F|B$!;n8_`NuL;sT!LP zk7sy(#dWlHF#SGh{gJ(^rya#NysRohqYu7Qato-E#mq=;5(j5q6s1Ml4(3`wD4-Fm zVRXjL@_C*$C6r5qLXJ0?U^x3Is8ZrOM{{Jq<5?UroaCZvkwH zEJ(0murhp$-0k{&J+iw?kf3%o4qO|lQ7W*Ah3gg;PSE1HU<1h_1gXA&Ia;|t8|Qrz z_>WD?%PHX2H;(QdyZp)TP<0POuQ4E301WpDGKQ_~;%6mLlb7`qQuzqkewNu-vefDd zP#1=o1}Q1#{C^_*;@bGC&p*9hxrx1G_gToB{c@8Rc_f=vck>XB&TA9SVccvoX*SM` z$p&uu#nC$WY0?AcG6W3rNCNjm#GcJihIX2cD*>7n2MbPQ$PLGdCh*^qDC)62mqX4s zOR<}@)zbG5R6)PMD4~10J<7N_y6uem7nsyAB1|qrsIS7eIIzN4!)JlX-P+Ypg3WaT zqIsTa9?gPZE0hno=9t}Yp_J43G_tp6TcTJ3LL73$Zbzr|E;;6D6w_H_F2aC+Yvu!(uIZuJv1r?7b4 zb7$NfAuBlMHQ9@M6<;4w<-Jqk$I?%j&9DCPxjxhTHy=h{O|Ab?^J?T<_rGeZsA%Ky z8NnvmPTbLSIFUZP%@s&tiI31cEB0h&rgBOI_q{M5sJ_B-qlFlX=n3}M;^7%lkcOyr zi-M@~e-W~z7LjhWW(PC;nVb}oQkIEm4FuIVhM$U4w(y7M`bPw|rjzQW%wl4ej70df zwrLDgaWiK2hZQO)6*63-zh%^pG4%bunt36PqK}0bPUgDQNk#NAQmQL$9W!m${#a!aG9vmj^ zbjmPrB~1$-$vKyBJVk!+SAB+-7|SxRj|*vYB!cxHZ2I<0+TGfX$LM$UvYVK#S3SmT zfA=lfFtwgTv_M_BaC2*?SH6X`5HSadY?&a1o~D4-{cCYx5kO?<$cs#%n)Y^4)4BKiD>^EI)FNxdgRu?p8h(AS zfsh2LHf43VRqTB^vYX*amdBW5r<)((gTtQEDnn-87DFm?XZ^OI=Ml2uM>MyIo{~C` zapHZna#ajE87CsJc}y1*D8AF%cxTe!LfVe?Ncq$TY(Fh#h7t{pm}qcNz0irxbEfqb zrzX819{ZJW`0Ws~E>aAkcZH@A$fSb#FUiefpLZ!i(~=I2q=Mbz*mDmWUe$@i7|obp zHT=J4Ha+>Jl;J9+&cneFatK9P{=S-C%r%_&2ASznn$ z#BfB`F;B&Er7K7ig_6`36NWWUn>4wv(2Nz(t)|T&Q$5t?Nej_p?h+{3^4g$Sk9#o@ zB~pz8hGp>XYV43)!(A1Wk(FIi6W#bgzuP_I0)h{(a0N`%_o!r2-}n5uUAM~vX2`9- zF3q@G2hCp4= z9`D_uev`=`)S~s>2OA1+;@6J( z1itU%AN_lNEdU$fixbPFqn=Gz`%xHu2~6bk@YCvua~rj22Hb+2)o^tBMg7Y+ZAKOL zi7dqzUPghB!dp3E%&9oJQWXxiHlNv|ge?TemrX)}hBW^Ik5PFSmo$c9(anbbJ`$dTk#HQR80%!noXXR=de8&Sz&>w{%DzAXDYVJ>e_$ca9 z;`O;U1|ER|{YLp<9OcrA<4@Lm?(GVA7VCE69?k43ORCzmqfg4-TOXBomcnS%W%^iL zHV;WuID&b%oO*%CjB1QSB{g!+Oj%Ku=c0Sbw1n><(s{MWZ^o|St;w`3&gW|$7d`O| z>RR+&su_brsW>pR=!ja$2`Z>%SZ?+P-Nd_*M2xUR##2aM6YLgT{UjJBfQ%=pz764h zc$dU^#`4Q{2h=5~L*sox>U;L_jco{6>(vL58ie?vuh4OuJQz@2LKKik^yPwZ!)NW- zxAOe%hv)5H>-5RzM=#QYPO7lJ!;O9L<@0Bjc$}kt>|B!FvplVanfR4kBWfR!r3NQ} zY5lP+B%9|a_UQ}n8iLZlvIX578UpU_0=75bO;tW0ZukQp?u7?hq&z>eDc{OWx4#~W zWWQJc+qM|Rl|WgSi6w}C7Di5;WQ8^2iRv~pJSz8}gIsUaoU%Z_!C*Fkz_}E_~-k zO0ZOrvXKqqBStbR1R3=8v%<5M;PjB7_+Teq7Aq<#Vc@@+(q(lCYJOJA#gf)42=*bG z5ZntiBjm=+Xj(BtXu_3{K}aw&r?YUCfo1PJqDs8Y^-(rPh`nq%h8f`?9^au;lTxTS z=JVJ=$jx$Ry{;$qj}{dHfDW3V*JGvpTVW)jDKXfr!PAmZ`%$(+_@yW=7m=zXf%w!I z>bd-X9{RRve#+f{1S=*pJX7{-Gsd61Bscg8xeo^qj~@dz@})xY`q#jq-@kXzrJ&~- zOK-Dvk8?FG``XlDXq010#2+C)N4AV)+ zKRQ3Xkp^WjqLlFEUdUpUC)Jo~Xm~R8$O&Ep@2MV<{QdIx_0qC{>mr%^IWXC?+)8Jo z&ZDa7O3S_UpukUx5*Ly-cE1jgmEL^`d^&DqH?7D`1N@m>qApu$>lP^KOZ01+m)&tY z2AGBbCxQRYkyBDE5~(Yv}hz8d&g=NG7no0^jZ+0Vu`GwntYL$mmUzwP`C2^@g!Y zI-sw3>$D+SHUJGp^4k_01j-Up7Zx}R?7t}yqE<9>PMV#tO*gHqW=f78t=^EYi}9-9 zc%elNUn{!$=Ov(*CrX8>F%($JVf&C2P+qP=&Sr6?V*eYZ1R6Yx1K=eY_MC_K|CVSC zvJMJ^FFh+Yw+Zh7!NCA(l_}LuCWbVnfZ@T28YRv4RaFkCy-`BXWgQ zI?!U@353t0+`eEEsf-LUCfQbloSn;C+H8B0>xfoTYd;BJ;yb*yOoC^^(Kpcf z4yzuYCc6jVM9jzzr1P%dC!x&E9rA{ z59D!2ll5>NeZzee39z>3b56-WN5A*7optD@#9{rH_qw4FuS<5nY0t9Ebw|~E==0IB z{o}E{oKF1)_)Fzl8$>7XVe2s6VK+LpCMH1ak<;Cmmi`K%Xq;A0bx+l)d&KxmpQ$Gq znLd#F!o+~Wvz5)nEr{N&N;U1Tx><_aUna=oZ#O0XAkeKlsR`ex>azLp@2tC*ZLCng zk6>rE2d&o|PHwwf`ws#z&qo?_!~r_};d?Bo2rnPN zkp&sR#@<&&Z1f~G*UWA-_K_n*(BSh|r26aNF641`=eK+(jmT0p{PN%AOlQt`tYnRf zN8T>dO};nFF9DZ-Iu#T_i4<_aqBYgfh_~-PogosqPGI^fm}wYTUnyM4jnnnb*k<;&9P%-Po_z5>kj^V}ExicE}e zc-an0ZLG-v8m1BJ?Flw1YU<-~buRR_zrXelGl&}SE-H$x!7TVT%GEijQQ6wMR!ykI zT^P*AGUj_4CgJmRg}cis68N!reH5zLCoE{e3Co2ZlwyorV$Yw+P>+~o-RzyHmsd2` zd=(~L<4Rr%?A9A739%2oAiVI{1bi4_FSpVM!7(rPh%VzD)8xTr6yE@w`K zXt7q(T0=ErV)K;ld31-fZ~$k0n_N*fw?f|hv;&d;5 zn5VxX=(is_I6p5D?1!?e3RXGW-8|t|fbBEVi75x;V)yye7BWIuH?1hkC3cDN=e!G0 zlDIvsw(u{Kfek>DWUa|$0f6DlEtwPla1K3=O+lQr=dE%SFDb(O3>_2}Rgbn6{!$Kn zp3h}et_2eF-<-r7AkD2I`PVrHQgTC?U;r^bMD*UuGpGP5`@P8Yy4u&uIf6|tL{YQ3 zR@uI@wzhPhS5~LV;w%JlXpnhx_&j2q7o<@40`or!9;3oEL~8vhUpw=>NQqNbmJIGe zIu2$gdlRdMTtNHk!#v+8kcJt9#LMmqyzUAxP1C~U9Hn!IxZ%ZGgr8=3n7u{!G6|S) zVy-HsZL&fjEvL9t%Qf1}g@;3BWxt@&E@W7w1jSH(9XxUl^TIU)?+pU)C*U6H3inUJ zi?eq`>Z}JA#sIhP5gUW>ii(0=N_AzfBRkOHO_S*1ID;+%B_mwd3#h^i1w%~}&Fw^y z0cCJFl2nE?&uC@_5ew+ly%MIx3nc{pUZoAPNZD4&Ly!o11v9t$;!}|;E@!cpS=x0O zgO=sV;^^4F!qub)lq-P^Hghqlhb=BhWS*$Rwv2F2Z$#2(herZ~&uT|%kdC8h*TnV0 zyfe`RNLCE-cLhDFii+peiifst00{IyHG-ej*!r;&uE@>ezDk_}w!1RJ;aF1}Sg>vU zriFf?8#oE4kW$MbETGJUqk_Ss7754?_E@P1YFB>%9m-Yd%aHnrSI9re`_ZJ{U%bK# zcn|*-3U=MkqT{5dXQo zY)(wt6l<_`Qti?i^h{9T_I;-+rSboa?1i97lz-Np75-OdMa{mGOcp15qUx=;=zEf7 zg!+&oyC8Uu_7@e>)Vy6RA5cSB0&kE3tT2N})7CTPT?K;CJT|-|P!VDUd`j_>WbCwV z$y;KYglItp*X#Wcg(Uos(ks&2%^qAUVD{6kq9U@`_a$>9-*9i&GBEHzp_SNi`O|-+ zVQ$e9oEkhDI`H5D@S7gBV*7M2$EX2+^=diTA7z63**K5?89Ej6czYd|d;7r1!#tL^$R5=IPWMyAmR;^Dd;30YS z@PJOHqQ(AYUxI4mA)_=MpyNgBvSAu$-5KLq47m7>x&UuRgpQ%Y#}yR0qXIu!c;kY3)B*lbVp zlr1i9G-)+7dbXtgg)j3xGS!%t)#4qs{!XBbiP!yt_jK!q(e$Z)?3&Y`S-eKw=?Mv+ z#*;sL&uTsWG6>K9a-e`9N14tOr~WBd+wsN6$0aIX8I%i!`IP?Xor54Cq8sQJ$7{OW zqbhjs-J!o4eeP#okEg5KGJJfq7*duA&-8TF_$A`=367$Uo`;7^MRBeKo62K6?41iK zla>8AwAH`1cFh2OZWE`X5apNLMfvHKvalw4;kkJ@GgElEF|*u>w>fB#^4#t#%I|6S zeh}T&9r%ylN40Rv@bkzb(xphD+vkHz@b5W>!_|yk$Ef;Fb*_ayG?VLk+q0AJ6SZ0a z4xn5*e)s;?l|owyhOc+Ep#r=<0dG+~cH#9)^LX7U!k=|;7GjAQN}t~psPBBWp(3VX*3?LRMdXoAeE z;4swcW)Pv7cVlw6d-)W9m-433X|MJjPIrpPOD^HTG8`LN58yv)au~&yS^uLm){^F7 zG}<3g7jOU(fVBvv#FAFMkDTFsS7axRVX#&{V#}7obx32C@a?NbF05&42w!)ZJ(#!?ZE$YLvjQ3o++R6NY0fxZwiNednolj%- znupX(VLY6fM1p%&g7QoF=)|C}Ki!fvZNOeacbX_+U|h4dUsKgJc0#jJ4dg%C5~lmDTF78F!x;Wxm51*Pnrz|E%PrPJrm0 zV{!%DUPuLMXS$ITQe=owY{~`{ex*7bRAaG|Q=J0VekB>RH()`X4AS6b(ewGKMg#2* z&uotX(>jMf_>U~CCsIe%eWVuTaMM2j#I=Sxpgd^?(V}+7F4P;!xWRD;g4Sw|C|~SethT6(>r*P~RzgADT>O7bvA|`2td&ObZz62J zh9 z_Iaqoi-9F))MiZBU45@wfUwFlhqIh`a#$s`GkjM`rp3g~8QY-k#>*wQ!UoZE?2Ns< zPpR&)+hI~BVZ;X_PLh~JlJ5nk?@1F_6MogxB=YE2G!8Ex^6h_>>=m!-i!!f)2ML{} za;k&u!7 zkl|`kmRUKPIp~2`Sp6w2gCf9?mt`}ooSNDThbss88Zt%(OYP{44Vctqqa3zR=sfDN zK^FB}ui4(>%miAm5g&Me3jIIzAKpFyX|X1>I#oW2BEf8W;f(GM&kBEMk*x`u9DEFE zChNR>=QJB*5yQbkTSc)$Y-P-8snzGhkReYW2j(+HZH^rvrMClhcjIlwDT#V*Ha8@y z^7m1Z5#W~e8%q96lc<@b_+HFOZJ{mFuwu<^s?Ce({(rc&c%d=occZus#8Qu+6Tu}N zVx#o}ZO7_F&+0zLi^Dq=wM@BiwSD~~qV0#$w~I(o6n5|rKws$+2B zR2l(iPqPH^UWvKkiqGS~lCwKEnOO$ij5wi# zV2n_@Q%YbkK#&emQff$-grJn<2Ds5Ny1S9?oODWem!#k6`}6xf?mye>bzk>9Pwbwv zd(Z3Kc@58bnqO3L^Fp-5Dm5l<5SPEEa+r)ZmEliN%=SQXd_3Y_xA_ku8Y>7wCwADo zD4crc=FcD`PJ%NTyAiJ_S1o!`R<&cp9TdefOEM(m5jVdnFcVqGu!AdlUqf(^r+@~oAfI{M-&F5m@D?1AlFN13~iU5 zEFDuNzec7*tg^s%-RN&OSwKoM2{*Ol(Jx*{1xl=1TpLDfF~ zj;CG+!cy*sPqAyRcH$EVP2KD9G(77}7E|wDG_?LT+?u$r_@GLF%GQK-o{@sM zU=&_t(#|SuIE@v(u+Vy=lCgY1H|h{U?)IVSr67bS@xRMj8OuAPqHZ7NZ=38B(90rN z8X>_%k?0kn+ZG%sIs|F`nUNvC8Y#Fv^|cbI`Z(3sAEjEJa*-6N+d1!OP&2gH<6YkK zXW`%jda1#e1{g0Lpvtrz_}O|T=*OR|UrW(8{9!V=E1QecYn*mp7EhL=`0n+UM{CkL z620+DvGoN&=tUUb?Ty2)*bpj0NxP3_udZSmyYC}Gtf7l#_H>``-UH8%T&^_F+^o~l z!PJ=!969;Gmz&zHH1M`Ohgknd%cUt0ZI#x|vHo7v5k=JZ|5>_IP>HL3s?ZtErg>R4*; zJVZm3UXnIvb%ISEi#VHVsI%FYuM%ROOhjt0%2zx3;RiCgPJHs_-G#mO8}KVL6ip|$Rb8&Jmvyz9R#*|^ zh$M+tYR4IVOU#SL(ew|8I`HoH;osj=dE_Qx;cH5zFh)OwNDvV->7k&rjD_v{K@HI zW_cg7!V}Nb`G|h(u$$JF|dg^XOTW3VQx(Nj- zT84+!>hjxsL7o_01f}3II)nb>_!|g?!wuKI?)8cmf}Od?gGqNW{QmrQ9|G?@!)E(6 z!r;u|;Q|9$@>PQHfFQnFlzclp@>{uqRnTfHHqhx0(zeb56(s1Gglb)AZ;4F++a`cX zi{rsN>>71@*qj0!9P_$dN09Pbgqc=Po&XDaQ875$SODax*N^;D?xDyP#cV`xs{ig# z+I0FWz5vfFp-rv;cDZK{pU7(SehHvl)8SQkefH{v^(3}5x9AO@jlkKUeXW3m4lgWh zBl%5A{%ymHKUY&}{#>7t{>dPvYE>hs%9{g(AoCgySno;FH4~e%EJFO5aNl_5jmhx~>BZD(X?qU(K}Wv% z`2PLhLqmld0D!O5r53(3bW)1rtm?vEZei=Y4laG&i-4p^uZVKa-%P>Xd04+c0-CX5 zqYvqnh#MTHZWU4|I{|Lq{Q&+AAn@xGq(ubRVcY}2r$Xzear*rE*a3q-hv&}Nc`Bc! z*1Nx%Ie;r9EEO>FyI_rO%>I+Bm{0(9>4N=(M`D4iKgvV;qm#WqecsWtCY4}_ZlS(U zjL(Fu==FUlIjMSI3l!qxct@+aqs4JbaxkW5ob`?tNJN)ur< zcj!s1*qD26uKToDf{AA0Be%QT+-aU!M4Z(gQ0Gm7h$01zQ3h{hLFAviSFJZSEq(J4 zIka)5Qe?_=%5W~a%5}><;a4Kq)F>_qI}Erz=d2(RNnx+CO&xc!vw%YDxI_f+Z+)2n z%{OvZXAC|%NnZ!kzJq>W1-~!jYhub#fOj{^3nxS@|0gS zWDiX+vqJxnhgxJI9l#tzWwF|k>iOZlugG&WtDO|F4A{~q`N){&B0(c~V@wp!C>-NR zW*+0>GrcPcb5r|-t@4hkbe*S;g=buHo7u1rfCeLqu0#9<>0zzqE2PW^_U-~)#)msJ zg}15wSvi)YK>+B<3(7fLdvy8p6Vm3%o#OI%DEm|kG-nl61SG*)LR!uF;ja5Rg?;LT zAqz|*UEnYFB0w(g|y5=i0bSZN9Y<_cz+n_@4MP2Gy0m;)$4DBXOx$2Kq+m-3ywn?E_HP(;R zh&N8(Ce3+@euaDn0i2A`e>0D#0+4@ht>6~3rESQA>hMTD$J=K;E8R#1zf5AaXKv=s zse<7P1$tmw$LJUo_$@z2exmp*iZD<^q6JEY8?*}#i*bNN-ju|&2xVcOFwwAT7fyS* zMtv)2ndlT}?Huy7*3Wb3{FvJ5KIi?86f{psN1H|^4}A}PkHQ3VR)hE7+E4Or6z$T% zY{MQ?n!odgo+4vG{Cj7KAfzB{Or=1v_w_$=MaC%mdup-!TeyuFQCP&aJ80a;*9vB0z&}h zC_Q$4qM74C3HKzn`ldeB61cWVhWsy06@g^e7;UxZPCemSILNv!+v(e_CZ6=klNvOe z?Z_)R5n$v4EfYChOV#xEJ{Y?fWKrODw=<^7Rcn|RX$b<`aHVE^^tX=~fCfkdi&gknj!0r zSzbIHTA(z*=9|%@7oKxU#R;b4)?ygK#|bgmNs#(zO2Nt6mbuIARIh+j$C;R5sGI5Z zX(=WzTKW87?>`yG=qz@I5cl8Sp`v~#(!K;X$yHwjJA*VF?2d zrdO>*-clRAIP-e#a1e+B;$~9*02sNDFg@Qr_&NpQyojimqeWCbz4j-@7J|jadMrjL zY4D7aCuf`WCJQ&w$iQxzw!2gHoai5Ym6~;a{%8NqDz7_*``q@26YB4aYt!AshA7T? zv=-2MVA%l8nqRkR&pp}Y4+^h-aMm-e2j1b9cQF_s<}mKty~dOjdr)PGHO!FC=`^+S zfR4;#j~S&Z`>Y?&Oqg&dAgy8Fz2kOE`!S(}?5Ivm{gzH)(|lk=*!L5`h7~hs%e7`Y zYUjWRH=o9v6C6RTYlXsW2sPXm=81d3i7sg%o{o(4a`D=Q?fn)S)15UK;RbW zcJ|~Hu+Mk*u7K;3UcGK%<+LEJge?W$HJ3NFF-;}-%8ofYWw|Z{ld#5|65w$#?M+x9 zbCdD#1;yIC*CB81nc|{eDrg%43FB`Q&*h9JGFem(%lI?UMLKU*OxLJt9E{!9XQCWL>G$rvycDfRb-zqWfe?VH_ z{$>H*$vH;lnmbf{5ux*rLym*xkifqCv8#P~QnMAQ^u_0pglZPpkvR9)k5%iy5l2l2 zRYXK)pLuePYUVI45a9(yu=!5^PaB7x&3H+vOt7X*U9ifq{6LCTGj0a+Ukt}*@HUE< z+hiX0lkyY?PD7F*zdaX)_fkR3#uM+>KZ+ZRc?iLDZLw9os=IfaDLNGSKr~*~F-Evm zNdyf{Lkp5NdE{0yyP!Kx;xBAmAFDS#FqNw?2%US-w#o+lD+~g3lU7etVE7rGGrQHd zaKH-FCsUiM3wpSzN>L&P`itTmBgV@!%YG$lC^>xf_%plZ!%;(GFl}IFoS0Cn@MnF2 zFrhZJ*f_|8OlT?xi=!Rk=xtiFi>ZT-&FWDXLBdR#th53ym$Gt@AizxFACfcAHRgIMDIg8TvdH3V;-s=a-NK7~@F~wnhM#6nh z)1u(&*sq?D6-$*ne8$|T`P(XMBNk6_YQIr<)6;MJOJ+V!#Zp=G7V5^(0iIjxUqvY% zSCc;$9Gb21{cp_L0KB;11{qt?H1$Y@vzk?w%rp;8)Do=rT4wE_SP zT3GoHfK07`hwv|*@++S~%7nbQ=nF#Vad5-1|9wD5M6S-Z?CZi#FY~t!9LU|wv5imD zV`7VA2$k)h^7LQz`~9dosW0dI52&v#y8|8H5|Q^{6p@}G$IZ&*tLNN%o^-dF2BvSh zokiUzehn#QL8bSfyktt~TZ~97`G3#7NKirmxJl8Q5k^KAPk=)rjJ#pfuX3;6j!0kc zdFZyd{$5;spQDNm#)P#Z>9V`+yOkuYJJmOFZ(I!MG;UAkTA+ zk+~tRk#Le_8HMop-%Bbp)4<_%+KI-UYmV3 zgY;uH<6>r;W#m?77o0ULE0+AFCQ)iFp>lW+k#MT33FhhUE_0q1&qun%(%Q&xFi^y| z!k_o{agK=}2kJeC8SnJ}$IxqAz&MkgVGL_EH4oU3ibBunGJYPGtUDl9{8sN|r!pl{ z*VVYnRaP>Myozgb-bn5%L6ShgwFP?VcLxD!jGTD;wcCgyA1K%H{GPLUd~@NI>Y_|i zw!(I$gwumqX{>eP?;2kfjqqxwX0Z-5BF{f5Z7(c2uo3CmDOEw7cYc%*ntlS8y6RL_ zkvP5;T%K${$Z=aSG*Hg=2T`Dq60yH_ z$h$p0P7S1+^+c-wzGB_`39D6s3?uDTdO%e_Vnydd{Bo;+DFjAUetC1iw|u!5Zy9UH zJx`hMLi2DtBCZQ}H41{WSR*BTFZ`tP_fl;mYsl^Gi0ek~9RF#pTGY5y>h1Y8Z0*J- zuNWbiPMCi>Ji5S_ujv0z5WGSsPH(K^F6}B$Ri|@B9OLIRqb0cY;@G7`+IrLO+IydPb z<5R&TstYC#jbjNQi$t;&q#v6XdC`Y_6G^068pv?9I9!`7n5H@Uz^7I`tp##E^Tw3D zy3rez05Z(tdNenS#l4N{|BTzX0iea-CC~w{W&{br7ksIVlrz{bp2*v~xa^ zkmKD30AEdp6cPwi_MUbi)@WVT4Hv81)q5TdWv#~**4?Na;5t-5hq3WlX68PGfGD^I z!wu;girJo|owS%Q3M^Vf$2|*~CpvemV$g>RYBLg*u6*!yzvD$n?oYueoy!8p|60RI zfNFz+;dXQFoH~8UWa>{&&gq{h{Ue6sSPioy^Xzlt&~_ugpL;JrpOtmV^KO40>YowM|3M^K zw)s5*LC!}iywjlqk-%SCn!Y;|RV+s1fv7Idg%mut@vVV9Wo(|F`f%vk>Jhityl@cO zv9I5J)iN|Z^TuU2_Mm_CDKW3rNrX2lW?3qa)$Jaa(uqXj%V3cIIy2>n5l~8M!0ChA zYq8-M*gk$_uyK$_D)dHcS#b4iPcDkH`3EP_rbEkN3hiY#@*_nNwHqZ2=vnnW^IE{0 zz)V-8|MU~8l+A1P_;);DWQ1oIlVRMn7by@wPh}28pB2EpmWR42TscA&;=oy|6MGxCPKj=(cDvwDuh%aeW4?qq7f&pvk6FneVt!w+A<696H$|O-DLwlAO z&!rI1&y&_cO`$fve?D@t_C0CFqbNS~oXKMWboV{HN$X;EFJkwfggRM)}7S+k*e~m{N}{RnC3# z?}ohXGjvda4l^*&B370}KGwnl%iVqeiYZ4X2K! z+Dm}$g$aww4YqIULWCLBn@GqBe3Or*`9}%uv2--~HQzKqI<557&>;od3;6=;nL@OX zdw|e^kf%!YpbY%KfKb@rmDkSbOw$+qB1yO_wB<8!&F+{^Y_>?8e~1tH!ibYxY2wTj zvwIbcnKDD4a4;MV0~VA1v4gq+o4wZ3CXHR*%zP0^WQ{G|Pp4<|pY5n}zl3(*>4`3{Jt&TvIc@OPCBK@@Mlr{>8bp+=d z$REJe-)r4&%;Q(SJbn~xbKBQ`=RBkuXXp3b`7i-gBKg@~>Z+aRe(rup@#W6<+i9ZV zIE_}M0D?Sm^0R7}$?p7eOUOX=54-xTH~r10HAzpIxoL#|gw)KQN?hdz&&xW->!ywS%PxPIxU zek~@b`luT-xeFlTUD{|tp2mrB!?nJ+3-6mwFy(lAgg>xEF`Qu#1>c?K>jtL|11?z{ zyTaJ6N;xjoOS#&*AX9{NQwhm~$h{7p!W9_7yB|68H%*VJl&_(BF!}^u?+Lv^`=Zr4w*mhYRUO0h8o`fAg1g^j#=t43&K@6O%2e}o` z7giUxAWG$)3%>L>lpRPm!nha^I{p54+pT|*)LJlUP0ZUZzwqk4hVLy4CW>5*2ScwS z7GC&Wr9Ob|a1P5&GH4&Q}@_FNgt;tu!!yeqkn_xGw&n-iL?!M9u6*U^iRgtr0Pg-Im zzV>R00`dJv?kyPa&z4ebr+pa6J+L4#1_Dk#6VSJ2zxFv; zi)?)2iyIcOueeept^4-MB3}|MGpteHIM5}!&I6jXcp4bOeV+^-{-JJIoL=Vxz+$3#c7#_}TTnNNi~rJDU9Cb&v>^ z2)Q9w)bQjc4oufA7WAPPIS+qW2=?}OJD;pEH8C-HHH)c0#x_a_KO4nQNkwx|%xw+r z+rK|KI8!ZqVx?thG7L!HOa?tu6IFC%24h-~((-SE5U%F6hBdu&m`!~_pi1xBK1ou| zTrmOA)kiO{8@f3X7i*}i%W?gqGg`!#VDNR6SFOm!{NiS;N76*2+U8Ii+rU1oVr}78 zQ}N||%RO^qCc=Et0bM?LuH`W``cvhYW?4EDtU{b2^7$&!3FJp*k8yw9<~|H~&s_li zKI_CbMwrHf)=*GZC^4tQwZA`-Yc)sJa^3}~=6N`mez!LAx|=I4&hO6Ju^Q%7yj4n$ zpT5`KTs}sSP&AqMCG`xu7hsQ1!nqoi@1bf`D{YQ)ZBJ9S9`IGIsCraK!QwvPJ~r5Z z4;%{sx!%61dc-d3|5Duh>SRkfk6QqQPf8LX%W-hBz5h-Y$OPjT014KXdI*q3+&@93 zOf*KaVsL8YJilAe+C6cfXKdjWu%8p2zILl}1gR`2ts-?6Hd~Rhu~^%WZX~>~R;bh> z_-&MlBv>NJi}5)6Uu~AH^uELGe3L%)C=Vacs56K^nVhw(p^(lvyZEWQWg#-KM7~&= z$Ry8QDq~8F#C3LSH&FNpvBq|a5R@d5BrCco`vPunVJ+W>p)MxiUqRMWQ`SFUeS;(# zh-|I7-@KFcuia~i?gIHN)dD7k?$Hh`j9ijl>VnBs7PS&Q8DVV3I7M2KqHoQqT_xC)H^U9^a{)j z$uh-MI;=)$v1RZ5y#C-{8`p4RS!6kxQmtVIhN6vM6+C+*T~}U`L6pd&pMLf{ z1jTdLVHgME6zr8e1j1;e(JN=@0_XMaKUR4rNa;NjF^(EC^&R-R-eaI-paqz& zKK8X(AE&oT$kKBKfJ0Cx9qe}s3QBXE`Kd9W`bZs8GPZ1n8y3GJQZ)0aAZII+;f!NX z?_)PfY3sS4$GyU` zNQUUPY2~9H*MT+KN5cS0cCTo@{0F~u$>q7f-?kr?P`pTYlX~;9z=;myt_BchNsW_; zMTm-}Lu-OUhLIo5&#?$ED3+Wa8}Y?_se7_9n^D!>B{|Lyyg(A#?nJ7V>(jI_U0Xi2 zjNk>Gts=9&i{Bou`kSG1nl%{&SrKu7?9wStW^EzrtlNp*IT10{a*h@LP0S3BeK|y#?Xcn4O@1;QD71 zNB@v^@}+3}L)`nDV{-x+lG!Hi5i?nIOiSKH=(A)Q7yaku`{qoxYg>LltaTK>RFtWz%#wQ!@8T*IWn0q(P>W7c!gg zPSmEY2h9iWH8R1phQ#UlM%D0P=+-hDe|9t3Tlu}oFVt7FVlT{t7wxD~4Dx#z+>(nIcIYlSq z;BgGv^K1?R76CF5xLDeJUY5RRswG8ZTow~TsoGPE$ba5tzx6oJK%e1j7KFt^+1{b#zmh)5v>?iG?683Bk>~OIp<2^Iv+xVYf=}Z6!RLK;3@nj_{bJHT zk*Uuh;xV%Qps&R&hovEjyNtER7LxV(~yqoFYKjA&?Eu5uk6S=)NM*_u_oKUNM)4yzl{R ze(_i953_&f(Bgy&MHo|ELz47=K{Iw^gXdo~P;R%!2x3#x1ga%MzHjlGHa?`4a~!~= zLu9ox^LuuFC3@mkx)%e%o+kHu$Q;KbY3z`+CJFR`I8E!IjXXijE#i#r2g-Nuk<=JEvb3ZyvR3zp zkJ42-uVz^--`Bb6=|zQ)neL^ug|7^g^)Tl2YKfrJ8K`kk$}*Yh%B>Hb;}wAETdZr3 zoaeFo#*(NG@s4BfbyCz7tv#$<|47`-IW>N)70Tg!T6g7|K5|OmzkRIt#fVY zz5cyu=Y*7LG6hyA`RuSOIM5ErC3@DoOz-#`M!#qMwSWp(j3M^|oUEV$tm2~#?po6V zb7DL$#I}G=$6MZ=jx2DEVD!`?$m1rKKZ+)--?M=WAQn)Q`1nG^*~l&cp7Tv;Lzp|> zOutwc!0)X7%`CaMeyU@$Ms2J$>G`-8o2>uaUz4j{OSp_&F#CutnQqQCse}5nV7h=B z0mp4X_b5A_lT1kKdWyKsiy*k#P*WsRw5T?i->;}vDpb8-!2IeIZ&eAb($6B&cyjv& zx$wJGRb{sbappJ$d)PX7L^|e4C&&j-XbiRj~p;{nXvfBnm*3PQj^FN4SWRTq31Z!Wgr$8je{h`h{%RhquXorPc`plO3H^5!M|}JB!7GEl26Zn0V7e220=95+cQcsi zL{{Xy#MO)b;Ia;6by#u}?*m5d^@WVRnJ8)qea2>>vBR7?a6L$;5w|yCN$5}rkBf_s z0cXn7k9>M@A_-$-tPdPE*+qs}X?=)tTD5{-@C)X4=ZF9l*5Q|OJfuufE1UPUv%;?e zuGEAQ)!FJFt3TUhCRSTxf|%j9PlX z`n6f}Pl91LHQR`VdJl3mj@f3h8^?*y=Yr9gO;+awxfGN~{k(@igAKPeUoxnFfe2kS z{Fc&Dn$_|fw$5tKcR79#2@K8zanlCdG7Ik&aC#qL(3{TYq-uv@eSHT^5#?u8_3mHs zFp=9VL7cUM`O{)h?DK{!ogeT?yhD>{)-)AL`=rjdt7odP-thOGr|wC(lCFt<)J$uR!{;mYdWyQYFv;d1l72f3!zHM$CEJ*_TdmFCIUAPuL)6aP zp2Iz=OJ2Z6V;f$%YQV#Gmp{*@<`w#j8R2T>_wZpv!ECIi)>M3F5ITZ4z)j(H)AC($ z-5msLJ<(YIEE&CiFLSNDVK#m+7f7~msb|>=cE*xma&xzp+|}xJ*8R&u?~+I55Jvz9 zhOy3qYg`4d&J255$=s4<&6U4s;qb#@%O?|26wkTNJMkdTOIvDT;Y{G0QaaBc9v?z0 zQR3WKuXdj5*q5ko+0M6f5X*c~Q)$0piSbr6;vg5D&k zQL=_M!vdZpM^}TL2l_XXOIjvDV1|G%(9$SSq&g`jQ|LDt`mT7rwH=h0@fIMzsk7dB z*ANJim{7?80ATc9)#_Guxl`bs`Sa0HFzVt+JRj1DH&tzZ~tqZb~oxtB? zMm3NX;gJ+TOmW@(hqPhR%RegUcbosp1pP+3h5(3|@b3%v|6RJCgInx= zzEr|aEk+>K&{@gu?JHSZ%i~$IS5yL7`+txeSjDmJv#fE z-c#@-N0xpU-Q`785J#lM5Wpc3bSMy}n2Ijit_bXNLca{AhJJti^Ax~Qc0Ot6{c)f0 zek>TesxAOZvO-CKwdC@1<58Q2qq`Q+e?%`4Yw9#Am8~fGRoEb&RRl7>@ZS(vK)yKp zZ|USC-mmz^FKBehn+?FqD@m}27@ur_=d?a|ZoZ9|_k^rDUb|;|%tP9;Z47=}yBB*m z)bISNM!!=mo|vBPTW2F&C15hsHL9=iLRBhLYc$GcB0|YUjls5lmwT!L`3A4?OxLg5 zKUs_YaKJ0;cT7&nv#tVnu}X4z=1ecpRlN&3*v;navW zl4srwSFZ^o+yus^f}g1ACCwiRVE7zd)sq{zS3RO8#AxqrI$Kjd?!Vk5gz+{3^dT>bwEs-aMLs#FyxSFc zq|6dDqMI6Qh3WxgGCh0~Rt9h=*uO+vgBOC83vf=m+_YpVJE+slgY=#YC(QXRSwdg% zs2$g)v*FAS{s7PzyD$E-^=(A4K@YcUk@HM|p1z&e% zWQbR&X5umqo?y2s^2pBO_d|2z>_;n}+r*(R8r$>&G=5)B_eIQUpVzWVJ1Bj4QFGt= zp6Vv4-1s{6DYIL&h_ZS?kE9!7z1I^H&#*6DX)WFIMPe{mT3kZOCVcJu(l{?ptF>}f86}-)oKSRf=j@N9q=J$nxpGo9+6RLcp!U`GN z{V`H3Fb89-r**?Ac)sxwtVaw?@i)``E}ZDUVf3+XTwy2fI{qexO#b?{B2>#tCOkAE zZ=5->|F=#VyC8djCz(s12I)i&Pvy@PNZP_apkrJ;u`}7`bahSeDuf}<;qsr){8T&TB#8R?_4OH5Lo=sS{LkR`Cb{WB=0THWh`ayesDdHHNqZJ!e* z0hsn+d4gkeh+HF)e^chk{jxYX3SeQQk;?X0WUG5^yyE&Wc+u>Z65$(&qRVa^-yH3cyL>QoTphftroi^r+X4eAucc@=%VLl(~=nQEPf|kSJS+juIY3b?c=p%%1;bHASt&BdFeRHf1K2xz4;(wvoM_Sd`6`;j5u}0{gU>nWkrlXANe4_3{d2RAJiS z`U}@htJXmOP4ycf;pMZ!42sEMnNhgcZ|IMXjhN;xZoK1Ukjo!dbU|1n+eg#3T)=cV z65U*Dg9iIM6R{lC+=YXySQ-Rt#`$T=SegjIlx< zUYngCgMdD*N}0}fZLk))HT?0brF`!;{Vrp`hf8oZWyt^gaO}8-yA8d&so{72C5=LP z#Quf##0&jm>A@-$er<-z^GjivvA1l+_$WcOl@=d}Z`1YpRLe8ii8-l1V8qw(K!18xzvYJKlg0y|E}dYW-+6OeoXnvj5E~Os z-KNGlRz@1bdrcm_ShQJKYL#%be&Y0XEjeQ0?-N4}-sekR+SGH3QN$LH)_(~(4Gf8U z#>||remY<`>6%G}B@;T8jIEYmM_=?nE2GwU;Qmi%(o> zOIkz8(apaalfIrR>R73Kq1mRn@2%Ud{_$Zq64AVmlfhkPJ@5kgYs$>VeB0&}Z46(<2s?<}$BjYn3f5wyO-4!Abn zs$pt#M&q+l8hl!UfkPQ#eT@wE)&8j6m>OSBw&&xq=~fx1r8euld}J-xI#>BV4##v^ zx~N{bxQ+m9i|@z^xA191WmI^IDEF_lS9V~?wIh;94DNj@jL&)<(_j6VXAy(q=z$x) zg1QUw9GA$q{Dc@_AgLTmr%!p629}0-ZzRGrw50jX1DokH9&jbz}flGzKx5Z4Vm(~ z`YCJs*HjPEsPC~LqmfN8{~l@j+YZ?#A7A8)Vc3F9dB}?gjWCCm1fxOcF_vzmwQfhI z66ta{NQ4jcjY1EiQht$sjC4bp?_#J~{gdD`Zi0Z}#~FeH29;d+;VFDt^3eWZ(jjiA zev)$pD5&h5B%+NQ)I<=lhr#S>9c!Qe3+XQP+?;Le!gu1G;1!4o>f8AA7Aafg6NgjF zZsmrzYH2~*^uFs3zyhYV07E+)EiIj#*vv$^#>YE#Yi#~W?s27ogfmSSk`8XsQs4%v zWnG>41O`&LtGXCxN)&?ep9q9Jq{eWK#QFGR9j@5PVK}=z9aX+;z)5EYZtmyVks1W= z5P?s|oRWnaWDGI7kp=)GPSJdmk^X6UNt=Ol8|)!_eIWT%IeLy3QzKor+V-Jt^`t>O z^w}hIfswvEno`32SW)pi0DN$*eXIFDR9|LvRipNF!4025<<2mOd)!Ko1!XNA6%?^7a5XdLkE>58!-$8(Z!Kct*OQJ)|joy=` z%JoboKP)>&3~^^|MZWFFK(2{ks1d9lEgr-}`K>azcFt}r=s{;^Fj1rBjzz*dt%66> zaG`_&S|PM_Fp(rowt*lCXbAvV-SL@4dQ1s8#608Nra9$fr4{&Ir<#VFZD_A~wZst2 z`qCy2$Ad5IJ3{KM70^Ip4LsZB_@&4KsMfy{Ce{6gcCD^_Cd>Y(#Z@Qr;at0=dbmqqoR^TxpyLgYu~UZ64APNOmH8=k;C`wI*NB>G9pYXC28pg5ba(r&i-`EUus z>¨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

JdEF$abtcR54u5r}_Vsh}P<`X}; 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 ['onPluginsInitialized', 0] + ]; + } + + /** + * Initialize the plugin + */ + public function onPluginsInitialized() + { + + // Don't proceed if php ext-json is not available + if (!function_exists('json_encode')) { + return; + } + + spl_autoload_register(function ($class) { + if (Utils::startsWith($class, 'Grav\Plugin\Aura\\')) { + require_once __DIR__ .'/classes/' . strtolower(basename(str_replace("\\", '/', $class))) . '.php'; + } + }); + + // Admin only events + if ($this->isAdmin()) { + $this->enable([ + 'onGetPageBlueprints' => ['onGetPageBlueprints', 0], + 'onAdminSave' => ['onAdminSave', 0], + ]); + return; + } + + // Frontend events + $this->enable([ + 'onPageInitialized' => ['onPageInitialized', 0] + ]); + } + + /** + * Extend page blueprints with additional configuration options. + * + * @param Event $event + */ + public function onGetPageBlueprints($event) + { + $types = $event->types; + $types->scanBlueprints('plugins://' . $this->name . '/blueprints'); + } + + public function onAdminSave(Event $event) + { + // Don't proceed if Admin is not saving a Page + if (!$event['object'] instanceof Page) { + return; + } + + // Don't proceed if required params not set + $requiredParams = array( + 'org-name', + 'org-url', + ); + foreach ($requiredParams as $param) { + $key = 'plugins.aura.' . $param; + if (!$this->grav['config']->get($key)) { + return; + } + } + + $page = $event['object']; + $aura = new Aura($page); + + // Meta Description + if ($aura->webpage->description) { + // Append description to page metadata + $aura->webpage->metadata['description'] = array( + 'name' => 'description', + 'content' => htmlentities($aura->webpage->description), + ); + } + + // Open Graph + if ($this->grav['config']->get('plugins.aura.output-og')) { + $aura->generateOpenGraphMeta(); + } + + // Twitter + if ($this->grav['config']->get('plugins.aura.output-twitter')) { + $aura->generateTwitterMeta(); + } + + // LinkedIn + if ($this->grav['config']->get('plugins.aura.output-linkedin')) { + $aura->generateLinkedInMeta(); + } + + // Generate Aura metadata + $metadata = []; + foreach ($aura->webpage->metadata as $tag) { + if (array_key_exists('property', $tag)) { + $metadata[$tag['property']] = $tag['content']; + } else if (array_key_exists('name', $tag)) { + $metadata[$tag['name']] = $tag['content']; + } + } + + // Check for existing metadata that may have been set prior to installation of Aura v2.0.0. + if (isset($page->header()->metadata) && is_array($page->header()->metadata)) { + foreach ($page->header()->metadata as $key => $val) { + $exists = false; + if (array_key_exists($key, $metadata)) { + $exists = true; + } else { + if (isset($page->header()->aura['metadata']) && is_array($page->header()->aura['metadata'])) { + if (array_key_exists($key, $page->header()->aura['metadata'])) { + $exists = true; + } + } + } + if (!$exists) { + $metadata[$key] = $val; + $page->header()->aura['metadata'] = array($key => $val); + } + } + } + + $page->header()->metadata = array_merge($metadata, isset($page->header()->aura['metadata']) ? $page->header()->aura['metadata'] : []); + + } + + /** + * Insert meta tags and structured data to head of each page + * + * @param Event $e + */ + public function onPageInitialized() + { + // Structured Data + if ($this->grav['config']->get('plugins.aura.output-sd')) { + + // Don't proceed if required params not set + $requiredParams = array( + 'org-name', + 'org-url', + ); + foreach ($requiredParams as $param) { + $key = 'plugins.aura.' . $param; + if (!$this->grav['config']->get($key)) { + return; + } + } + + $page = $this->grav['page']; + $assets = $this->grav['assets']; + + $aura = new Aura($page); + + // Generate structured data block + $sd = $aura->generateStructuredData(); + // Drop into JS pipeline + $type = array('type' => 'application/ld+json'); + if (version_compare(GRAV_VERSION, '1.6.0', '<')) { + $type = 'application/ld+json'; + } + $assets->addInlineJs($sd, null, null, $type); + } + + } + +} \ No newline at end of file diff --git a/user/plugins/aura/aura.yaml b/user/plugins/aura/aura.yaml new file mode 100644 index 0000000..d4ca941 --- /dev/null +++ b/user/plugins/aura/aura.yaml @@ -0,0 +1 @@ +enabled: true diff --git a/user/plugins/aura/blueprints.yaml b/user/plugins/aura/blueprints.yaml new file mode 100644 index 0000000..2dc58bb --- /dev/null +++ b/user/plugins/aura/blueprints.yaml @@ -0,0 +1,137 @@ +name: Aura +version: 2.0.1 +description: Automatically add meta tags and structured data to your pages for visually appealing and informative search results and social media sharing. +icon: code +author: + name: Matt Mulhall + email: matt@theskylab.net + url: https://www.theskylab.net +homepage: https://github.com/matt-j-m/grav-plugin-aura +keywords: seo, structured data, open graph +bugs: https://github.com/matt-j-m/grav-plugin-aura/issues +license: MIT + +form: + validation: strict + fields: + enabled: + type: toggle + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + output-heading: + type: section + title: Output Options + text: 'Enable any or all of the following options to include the relevant tags and metadata in each page.' + underline: true + output-sd: + type: toggle + label: schema.org Structured Data (Google, Microsoft, Yahoo!, Yandex) + highlight: 1 + default: 1 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + output-og: + type: toggle + label: Open Graph (Facebook, Pinterest) + highlight: 1 + default: 1 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + output-twitter: + type: toggle + label: Twitter Card + highlight: 1 + default: 1 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + output-linkedin: + type: toggle + label: LinkedIn Article + highlight: 1 + default: 1 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + org-heading: + type: section + title: Organization Information + underline: true + org-name: + type: text + size: large + label: Name + validate: + required: true + org-url: + type: text + size: large + label: URL + validate: + required: true + org-logo: + type: file + size: large + label: Logo (minimum 112x112px .jpg, .png or .gif format only) + multiple: false + destination: 'user/images' + accept: + - .jpg + - .png + - .gif + org-facebook-url: + type: text + size: large + label: Facebook URL + placeholder: 'https://www.facebook.com/username' + org-facebook-appid: + type: text + size: large + label: Facebook App ID + placeholder: '1234567890' + org-twitter-user: + type: text + size: large + label: Twitter Username + placeholder: 'username' + org-instagram-url: + type: text + size: large + label: Instagram URL + placeholder: 'https://www.instagram.com/username/' + org-linkedin-url: + type: text + size: large + label: LinkedIn URL + placeholder: 'https://www.linkedin.com/company/company-name/' + org-pinterest-url: + type: text + size: large + label: Pinterest URL + placeholder: 'https://www.pinterest.com/username/' + org-youtube-url: + type: text + size: large + label: YouTube URL + placeholder: 'https://www.youtube.com/username' + org-wikipedia-url: + type: text + size: large + label: Wikipedia URL + placeholder: "https://en.wikipedia.org/wiki/Company" diff --git a/user/plugins/aura/blueprints/default.yaml b/user/plugins/aura/blueprints/default.yaml new file mode 100644 index 0000000..37e5b68 --- /dev/null +++ b/user/plugins/aura/blueprints/default.yaml @@ -0,0 +1,61 @@ +title: Aura +'@extends': + type: default + context: blueprints://pages + +form: + fields: + tabs: + type: tabs + active: 1 + + fields: + + options: + type: tab + title: PLUGIN_ADMIN.OPTIONS + + fields: + publishing: + type: section + title: PLUGIN_ADMIN.PUBLISHING + underline: true + + fields: + header.metadata: + unset@: true + + aura: + type: tab + title: Aura + + fields: + + header.aura.pagetype: + type: select + label: Page Type + size: medium + options: + website: Website (default) + article: Article + validate: + required: true + + header.aura.description: + type: text + size: long + label: 'Page Description' + description: 'Recommended length 140-160 characters.' + + header.aura.image: + type: filepicker + label: Preview Image + preview_images: true + description: 'Recommended size 1920x1080px in .jpg, .png or .gif format only. If not specified, will default to the first image found in the page''s folder.' + + header.aura.metadata: + toggleable: true + type: array + label: Additional Metadata + placeholder_key: PLUGIN_ADMIN.METADATA_KEY + placeholder_value: PLUGIN_ADMIN.METADATA_VALUE diff --git a/user/plugins/aura/classes/aura.php b/user/plugins/aura/classes/aura.php new file mode 100644 index 0000000..22afa9f --- /dev/null +++ b/user/plugins/aura/classes/aura.php @@ -0,0 +1,443 @@ +grav = $cache = Grav::instance(); + + /* + * Organization + */ + $this->org = new Organization(); + + $this->org->url = (string)$this->grav['config']->get('plugins.aura.org-url'); + $this->org->id = $this->org->url . '#organization'; + $this->org->name = $this->grav['config']->get('plugins.aura.org-name'); + + // Org SameAs + $sameAs = array(); + foreach ($this->otherPresence as $platform) { + $key = 'plugins.aura.org-' . $platform; + if ($this->grav['config']->get($key)) { + $sameAs[] = $this->grav['config']->get($key); + } + } + $key = 'plugins.aura.' . 'org-twitter-user'; + if ($this->grav['config']->get($key)) { + $sameAs[] = 'https://twitter.com/' . $this->grav['config']->get($key); + } + if (!empty($sameAs)) { + $this->org->sameAs = $sameAs; + } + + // Org Logo + if ($this->grav['config']->get('plugins.aura.org-logo')) { + $imageArray = $this->grav['config']->get('plugins.aura.org-logo'); + $firstImage = reset($imageArray); + $imagePath = ROOT_DIR . $firstImage['path']; + if (file_exists($imagePath)) { + $size = getimagesize($imagePath); + $this->org->logo = new Image(); + $this->org->logo->url = $this->grav['base_url_absolute'] . '/' . $firstImage['path']; + $this->org->logo->id = $this->org->url . '#logo'; + $this->org->logo->width = $size[0]; + $this->org->logo->height = $size[1]; + $this->org->logo->caption = $this->org->name; + $this->org->logo->type = $size['mime']; + } + } + + /* + * Website + */ + $this->website = new WebSite; + + $this->website->url = $this->grav['base_url_absolute']; + $this->website->id = $this->website->url . '#website'; + $this->website->name = $this->grav['config']->get('site.title'); + + + /* + * Webpage + */ + $this->webpage = new WebPage; + + $this->webpage->url = $page->url(true); + $this->webpage->id = $this->webpage->url . '#webpage'; + $this->webpage->title = $page->title() . ' | ' . $this->grav['config']->get('site.title'); + $header = $page->header(); + if ((isset($header->aura['description'])) && ($header->aura['description'] != '')) { + $this->webpage->description = (string)$header->aura['description']; + } + if ((isset($header->language)) and ($header->language != '')) { + $this->webpage->language = $header->language; + } else { + $this->webpage->language = $this->grav['language']->getActive(); + if (!$this->webpage->language) { + $this->webpage->language = $this->grav['config']->get('site.default_lang'); + } + } + + $datePublishedRaw = time(); + if ($page->publishDate()) { + $datePublishedRaw = $page->publishDate(); + } else if ($page->date()) { + $datePublishedRaw = $page->date(); + } else if ($page->modified()) { + $datePublishedRaw = $page->modified(); + } + $dateModifiedRaw = $page->modified() ? $page->modified() : time(); + $this->webpage->datePublished = date("c", $datePublishedRaw); + $this->webpage->dateModified = date("c", $dateModifiedRaw); + + // Webpage Image + $filename = false; + if ((isset($header->aura['image'])) && ($header->aura['image'] != '')) { + $filename = $header->aura['image']; + } else if (isset($header->media_order) && ($header->media_order != '')) { + $images = explode(',', $header->media_order); + if ((is_array($images)) && (!empty($images))) { + $filename = $images[0]; + } + } + + if ($filename) { + $imagePath = $page->path() . '/' . $filename; + if (file_exists($imagePath)) { + $size = getimagesize($imagePath); + $this->webpage->image = new Image(); + $this->webpage->image->url = preg_replace('/' . preg_quote($page->urlExtension()) . '$/u', '', $page->url(true)) . '/' . $filename; + $this->webpage->image->id = $this->webpage->url . '#primaryimage'; + $this->webpage->image->width = $size[0]; + $this->webpage->image->height = $size[1]; + $this->webpage->image->caption = $this->webpage->title; + $this->webpage->image->type = $size['mime']; + } + } + + if ((isset($header->aura['pagetype'])) && ($header->aura['pagetype'] != '')) { + $this->webpage->type = $header->aura['pagetype']; + } + + // Author + if (($this->grav['config']->get('plugins.aura-authors.enabled')) && isset($page->header()->aura['author'])) { + $authors = $this->grav['config']->get('plugins.aura-authors.authors'); + $key = array_search($page->header()->aura['author'], array_column($authors, 'label')); + if ($key !== false) { + + $author = $authors[$key]; + + $this->person = new Person(); + + $this->person->id = $this->org->url . '#person/' . $author['label']; + $this->person->name = $author['name']; + $this->person->description = $author['description']; + + // Person SameAs + $sameAs = array(); + foreach ($this->otherPresence as $platform) { + $key = 'person-' . $platform; + if (isset($author[$key]) && $author[$key] != '') { + $sameAs[] = $author[$key]; + } + } + $key = 'person-twitter-user'; + if (isset($author[$key]) && $author[$key] != '') { + $this->person->twitterUser = $author[$key]; + $sameAs[] = 'https://twitter.com/' . $author[$key]; + } + if (!empty($sameAs)) { + $this->person->sameAs = $sameAs; + } + + // Person Image + if ((isset($author['image'])) && (!empty($author['image']))) { + $firstImage = reset($author['image']); + $imagePath = ROOT_DIR . $firstImage['path']; + if (file_exists($imagePath)) { + $size = getimagesize($imagePath); + $this->person->image = new Image(); + $this->person->image->url = $this->grav['base_url_absolute'] . '/' . $firstImage['path']; + $this->person->image->id = $this->org->url . '#personimage/' . $author['label']; + $this->person->image->width = $size[0]; + $this->person->image->height = $size[1]; + $this->person->image->caption = $author['name']; + $this->person->image->type = $size['mime']; + } + } + + } + } + + } + + public function generateOpenGraphMeta() { + $data = array( + 'og:url' => $this->webpage->url, + 'og:type' => $this->webpage->type, + 'og:title' => $this->webpage->title, + ); + if ($this->webpage->description) { + $data['og:description'] = $this->webpage->description; + } + if ($this->webpage->image) { + $data['og:image'] = $this->webpage->image->url; + $data['og:image:type'] = $this->webpage->image->type; + $data['og:image:width'] = $this->webpage->image->width; + $data['og:image:height'] = $this->webpage->image->height; + } + if ($this->grav['config']->get('plugins.aura.org-facebook-appid')) { + $data['fb:app_id'] = $this->grav['config']->get('plugins.aura.org-facebook-appid'); + } + if ($this->person) { + $data['og:author'] = $this->person->name; + } else { + $data['og:author'] = $this->org->name; + } + foreach ($data as $property => $content) { + $this->webpage->metadata[$property] = array( + 'property' => $property, + 'content' => htmlentities($content), + ); + } + } + + public function generateTwitterMeta() { + $data = array( + 'twitter:card' => 'summary_large_image', + 'twitter:title' => $this->webpage->title, + ); + if ($this->webpage->description) { + $data['twitter:description'] = $this->webpage->description; + } + if ($this->grav['config']->get('plugins.aura.org-twitter-user')) { + $data['twitter:site'] = '@' . $this->grav['config']->get('plugins.aura.org-twitter-user'); + } + if ($this->person && $this->person->twitterUser) { + $data['twitter:creator'] = '@' . $this->person->twitterUser; + } else { + if ($this->grav['config']->get('plugins.aura.org-twitter-user')) { + $data['twitter:creator'] = '@' . $this->grav['config']->get('plugins.aura.org-twitter-user'); + } + } + if ($this->webpage->image) { + $data['twitter:image'] = $this->webpage->image->url; + } + foreach ($data as $name => $content) { + $this->webpage->metadata[$name] = array( + 'name' => $name, + 'content' => htmlentities($content), + ); + } + } + + public function generateLinkedInMeta() { + $data = array( + 'article:published_time' => $this->webpage->datePublished, + 'article:modified_time' => $this->webpage->dateModified, + ); + if ($this->person) { + $data['article:author'] = $this->person->name; + } else { + $data['article:author'] = $this->org->name; + } + foreach ($data as $property => $content) { + $this->webpage->metadata[$property] = array( + 'property' => $property, + 'content' => htmlentities($content), + ); + } + } + + public function generateStructuredData() { + $organization = array( + '@type' => 'Organization', + '@id' => $this->org->id, + 'name' => $this->org->name, + 'url' => $this->org->url, + ); + + $website = array( + '@type' => 'WebSite', + '@id' => $this->website->id, + 'url' => $this->website->url, + 'name' => $this->website->name, + 'publisher' => array( + '@id' => $this->org->id, + ), + ); + + $webpage = array( + '@type' => 'WebPage', + '@id' => $this->webpage->id, + 'url' => $this->webpage->url, + 'inLanguage' => $this->webpage->language, + 'name' => $this->webpage->title, + 'isPartOf' => array( + '@id' => $this->website->id, + ), + 'datePublished' => $this->webpage->datePublished, + 'dateModified' => $this->webpage->dateModified, + ); + + // Add Organization sameAs (if defined) + if ($this->org->sameAs) { + $organization['sameAs'] = $this->org->sameAs; + } + + // Add logo (if defined) + if ($this->org->logo) { + $organization['logo'] = array( + '@type' => 'ImageObject', + '@id' => $this->org->logo->id, + 'url' => $this->org->logo->url, + 'width' => $this->org->logo->width, + 'height' => $this->org->logo->height, + 'caption' => $this->org->logo->caption, + ); + $organization['image'] = array( + '@id' => $this->org->logo->id, + ); + } + + // Add page description (if defined) + if ($this->webpage->description) { + $webpage['description'] = $this->webpage->description; + } + + // Add page image (if defined) + if ($this->webpage->image) { + $webpageImage = array( + '@type' => 'ImageObject', + '@id' => $this->webpage->image->id, + 'url' => $this->webpage->image->url, + 'width' => $this->webpage->image->width, + 'height' => $this->webpage->image->height, + 'caption' => $this->webpage->image->caption, + ); + $webpage['primaryImageOfPage'] = array( + '@id' => $this->webpage->image->id, + ); + } + + // Additional based on page type i.e. article + if ($this->webpage->type == 'article') { + $article = array( + '@type' => 'Article', + '@id' => $this->webpage->url . '#article', + 'isPartOf' => array( + '@id' => $this->webpage->id, + ), + 'headline' => $this->webpage->title, + 'datePublished' => $this->webpage->datePublished, + 'dateModified' => $this->webpage->dateModified, + 'mainEntityOfPage' => array( + '@id' => $this->webpage->id, + ), + 'publisher' => array( + '@id' => $this->org->id, + ), + ); + + // Add Image + if ($this->webpage->image) { + $article['image'] = array( + '@id' => $this->webpage->image->id, + ); + } + + // Add Author + if ($this->person) { + // Use Person (if defined) + $person = array( + '@type' => 'Person', + '@id' => $this->person->id, + 'name' => $this->person->name, + ); + + // Add Person description (if defined) + if ($this->person->description) { + $person['description'] = $this->person->description; + } + + // Add Person sameAs (if defined) + if ($this->person->sameAs) { + $person['sameAs'] = $this->person->sameAs; + } + + // Add Person image (if defined) + if ($this->person->image) { + $person['image'] = array( + '@type' => 'ImageObject', + '@id' => $this->person->image->id, + 'url' => $this->person->image->url, + 'width' => $this->person->image->width, + 'height' => $this->person->image->height, + 'caption' => $this->person->image->caption, + ); + } + $article['author'] = array( + '@id' => $this->person->id, + ); + } else { + // Use Organization + $article['author'] = array( + '@id' => $this->org->id, + ); + } + } + + // Build the empty structured data block + $data = array( + '@context' => 'https://schema.org', + '@graph' => array(), + ); + + // Add the elements in order + $data['@graph'][] = $organization; + $data['@graph'][] = $website; + if (isset($webpageImage)) { + $data['@graph'][] = $webpageImage; + } + $data['@graph'][] = $webpage; + if (isset($article)) { + $data['@graph'][] = $article; + } + if (isset($person)) { + $data['@graph'][] = $person; + } + + return json_encode($data, JSON_UNESCAPED_SLASHES); + + } + +} \ No newline at end of file diff --git a/user/plugins/aura/classes/image.php b/user/plugins/aura/classes/image.php new file mode 100644 index 0000000..c85dcba --- /dev/null +++ b/user/plugins/aura/classes/image.php @@ -0,0 +1,12 @@ +