From d4d885a87400443f29717f513743745b2eed183c Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 7 May 2018 05:40:25 -0600 Subject: [PATCH] Initial commit of LDAP plugin --- CHANGELOG.md | 5 + LICENSE | 21 + README.md | 57 +- blueprints.yaml | 135 ++ composer.json | 5 + composer.lock | 236 +++ login-ldap.php | 141 ++ login-ldap.yaml | 14 + vendor/autoload.php | 7 + vendor/composer/ClassLoader.php | 445 +++++ vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 9 + vendor/composer/autoload_files.php | 10 + vendor/composer/autoload_namespaces.php | 9 + vendor/composer/autoload_psr4.php | 13 + vendor/composer/autoload_real.php | 70 + vendor/composer/autoload_static.php | 50 + vendor/composer/installed.json | 228 +++ vendor/symfony/ldap/.gitignore | 3 + .../ldap/Adapter/AbstractConnection.php | 63 + vendor/symfony/ldap/Adapter/AbstractQuery.php | 51 + .../symfony/ldap/Adapter/AdapterInterface.php | 54 + .../ldap/Adapter/CollectionInterface.php | 25 + .../ldap/Adapter/ConnectionInterface.php | 33 + .../ldap/Adapter/EntryManagerInterface.php | 55 + .../symfony/ldap/Adapter/ExtLdap/Adapter.php | 87 + .../ldap/Adapter/ExtLdap/Collection.php | 134 ++ .../ldap/Adapter/ExtLdap/Connection.php | 154 ++ .../Adapter/ExtLdap/ConnectionOptions.php | 78 + .../ldap/Adapter/ExtLdap/EntryManager.php | 95 + vendor/symfony/ldap/Adapter/ExtLdap/Query.php | 109 ++ .../symfony/ldap/Adapter/QueryInterface.php | 42 + .../ldap/Adapter/RenameEntryInterface.php | 22 + vendor/symfony/ldap/CHANGELOG.md | 12 + vendor/symfony/ldap/Entry.php | 95 + .../ldap/Exception/ConnectionException.php | 21 + .../Exception/DriverNotFoundException.php | 21 + .../ldap/Exception/ExceptionInterface.php | 21 + .../symfony/ldap/Exception/LdapException.php | 21 + .../ldap/Exception/NotBoundException.php | 21 + vendor/symfony/ldap/LICENSE | 19 + vendor/symfony/ldap/Ldap.php | 87 + vendor/symfony/ldap/LdapClient.php | 127 ++ vendor/symfony/ldap/LdapClientInterface.php | 36 + vendor/symfony/ldap/LdapInterface.php | 64 + vendor/symfony/ldap/README.md | 21 + .../Tests/Adapter/ExtLdap/AdapterTest.php | 114 ++ .../Tests/Adapter/ExtLdap/LdapManagerTest.php | 195 ++ .../ldap/Tests/Fixtures/conf/slapd.conf | 17 + .../ldap/Tests/Fixtures/data/base.ldif | 4 + .../ldap/Tests/Fixtures/data/fixtures.ldif | 26 + vendor/symfony/ldap/Tests/LdapClientTest.php | 229 +++ vendor/symfony/ldap/Tests/LdapTest.php | 84 + vendor/symfony/ldap/Tests/LdapTestCase.php | 16 + vendor/symfony/ldap/composer.json | 36 + vendor/symfony/ldap/phpunit.xml.dist | 32 + vendor/symfony/options-resolver/.gitignore | 3 + vendor/symfony/options-resolver/CHANGELOG.md | 52 + .../Debug/OptionsResolverIntrospector.php | 102 + .../Exception/AccessException.php | 22 + .../Exception/ExceptionInterface.php | 21 + .../Exception/InvalidArgumentException.php | 21 + .../Exception/InvalidOptionsException.php | 23 + .../Exception/MissingOptionsException.php | 23 + .../Exception/NoConfigurationException.php | 26 + .../Exception/NoSuchOptionException.php | 26 + .../Exception/OptionDefinitionException.php | 21 + .../Exception/UndefinedOptionsException.php | 24 + vendor/symfony/options-resolver/LICENSE | 19 + vendor/symfony/options-resolver/Options.php | 22 + .../options-resolver/OptionsResolver.php | 1081 +++++++++++ vendor/symfony/options-resolver/README.md | 15 + .../Debug/OptionsResolverIntrospectorTest.php | 203 ++ .../Tests/OptionsResolverTest.php | 1653 +++++++++++++++++ vendor/symfony/options-resolver/composer.json | 33 + .../symfony/options-resolver/phpunit.xml.dist | 31 + vendor/symfony/polyfill-php56/LICENSE | 19 + vendor/symfony/polyfill-php56/Php56.php | 138 ++ vendor/symfony/polyfill-php56/README.md | 15 + vendor/symfony/polyfill-php56/bootstrap.php | 38 + vendor/symfony/polyfill-php56/composer.json | 32 + vendor/symfony/polyfill-util/Binary.php | 18 + .../polyfill-util/BinaryNoFuncOverload.php | 65 + .../polyfill-util/BinaryOnFuncOverload.php | 67 + vendor/symfony/polyfill-util/LICENSE | 19 + .../polyfill-util/LegacyTestListener.php | 84 + vendor/symfony/polyfill-util/README.md | 13 + vendor/symfony/polyfill-util/TestListener.php | 96 + .../polyfill-util/TestListenerTrait.php | 107 ++ vendor/symfony/polyfill-util/composer.json | 30 + 90 files changed, 7961 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 blueprints.yaml create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 login-ldap.php create mode 100644 login-ldap.yaml create mode 100644 vendor/autoload.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/LICENSE create mode 100644 vendor/composer/autoload_classmap.php create mode 100644 vendor/composer/autoload_files.php create mode 100644 vendor/composer/autoload_namespaces.php create mode 100644 vendor/composer/autoload_psr4.php create mode 100644 vendor/composer/autoload_real.php create mode 100644 vendor/composer/autoload_static.php create mode 100644 vendor/composer/installed.json create mode 100644 vendor/symfony/ldap/.gitignore create mode 100644 vendor/symfony/ldap/Adapter/AbstractConnection.php create mode 100644 vendor/symfony/ldap/Adapter/AbstractQuery.php create mode 100644 vendor/symfony/ldap/Adapter/AdapterInterface.php create mode 100644 vendor/symfony/ldap/Adapter/CollectionInterface.php create mode 100644 vendor/symfony/ldap/Adapter/ConnectionInterface.php create mode 100644 vendor/symfony/ldap/Adapter/EntryManagerInterface.php create mode 100644 vendor/symfony/ldap/Adapter/ExtLdap/Adapter.php create mode 100644 vendor/symfony/ldap/Adapter/ExtLdap/Collection.php create mode 100644 vendor/symfony/ldap/Adapter/ExtLdap/Connection.php create mode 100644 vendor/symfony/ldap/Adapter/ExtLdap/ConnectionOptions.php create mode 100644 vendor/symfony/ldap/Adapter/ExtLdap/EntryManager.php create mode 100644 vendor/symfony/ldap/Adapter/ExtLdap/Query.php create mode 100644 vendor/symfony/ldap/Adapter/QueryInterface.php create mode 100644 vendor/symfony/ldap/Adapter/RenameEntryInterface.php create mode 100644 vendor/symfony/ldap/CHANGELOG.md create mode 100644 vendor/symfony/ldap/Entry.php create mode 100644 vendor/symfony/ldap/Exception/ConnectionException.php create mode 100644 vendor/symfony/ldap/Exception/DriverNotFoundException.php create mode 100644 vendor/symfony/ldap/Exception/ExceptionInterface.php create mode 100644 vendor/symfony/ldap/Exception/LdapException.php create mode 100644 vendor/symfony/ldap/Exception/NotBoundException.php create mode 100644 vendor/symfony/ldap/LICENSE create mode 100644 vendor/symfony/ldap/Ldap.php create mode 100644 vendor/symfony/ldap/LdapClient.php create mode 100644 vendor/symfony/ldap/LdapClientInterface.php create mode 100644 vendor/symfony/ldap/LdapInterface.php create mode 100644 vendor/symfony/ldap/README.md create mode 100644 vendor/symfony/ldap/Tests/Adapter/ExtLdap/AdapterTest.php create mode 100644 vendor/symfony/ldap/Tests/Adapter/ExtLdap/LdapManagerTest.php create mode 100644 vendor/symfony/ldap/Tests/Fixtures/conf/slapd.conf create mode 100644 vendor/symfony/ldap/Tests/Fixtures/data/base.ldif create mode 100644 vendor/symfony/ldap/Tests/Fixtures/data/fixtures.ldif create mode 100644 vendor/symfony/ldap/Tests/LdapClientTest.php create mode 100644 vendor/symfony/ldap/Tests/LdapTest.php create mode 100644 vendor/symfony/ldap/Tests/LdapTestCase.php create mode 100644 vendor/symfony/ldap/composer.json create mode 100644 vendor/symfony/ldap/phpunit.xml.dist create mode 100644 vendor/symfony/options-resolver/.gitignore create mode 100644 vendor/symfony/options-resolver/CHANGELOG.md create mode 100644 vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php create mode 100644 vendor/symfony/options-resolver/Exception/AccessException.php create mode 100644 vendor/symfony/options-resolver/Exception/ExceptionInterface.php create mode 100644 vendor/symfony/options-resolver/Exception/InvalidArgumentException.php create mode 100644 vendor/symfony/options-resolver/Exception/InvalidOptionsException.php create mode 100644 vendor/symfony/options-resolver/Exception/MissingOptionsException.php create mode 100644 vendor/symfony/options-resolver/Exception/NoConfigurationException.php create mode 100644 vendor/symfony/options-resolver/Exception/NoSuchOptionException.php create mode 100644 vendor/symfony/options-resolver/Exception/OptionDefinitionException.php create mode 100644 vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php create mode 100644 vendor/symfony/options-resolver/LICENSE create mode 100644 vendor/symfony/options-resolver/Options.php create mode 100644 vendor/symfony/options-resolver/OptionsResolver.php create mode 100644 vendor/symfony/options-resolver/README.md create mode 100644 vendor/symfony/options-resolver/Tests/Debug/OptionsResolverIntrospectorTest.php create mode 100644 vendor/symfony/options-resolver/Tests/OptionsResolverTest.php create mode 100644 vendor/symfony/options-resolver/composer.json create mode 100644 vendor/symfony/options-resolver/phpunit.xml.dist create mode 100644 vendor/symfony/polyfill-php56/LICENSE create mode 100644 vendor/symfony/polyfill-php56/Php56.php create mode 100644 vendor/symfony/polyfill-php56/README.md create mode 100644 vendor/symfony/polyfill-php56/bootstrap.php create mode 100644 vendor/symfony/polyfill-php56/composer.json create mode 100644 vendor/symfony/polyfill-util/Binary.php create mode 100644 vendor/symfony/polyfill-util/BinaryNoFuncOverload.php create mode 100644 vendor/symfony/polyfill-util/BinaryOnFuncOverload.php create mode 100644 vendor/symfony/polyfill-util/LICENSE create mode 100644 vendor/symfony/polyfill-util/LegacyTestListener.php create mode 100644 vendor/symfony/polyfill-util/README.md create mode 100644 vendor/symfony/polyfill-util/TestListener.php create mode 100644 vendor/symfony/polyfill-util/TestListenerTrait.php create mode 100644 vendor/symfony/polyfill-util/composer.json diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e6d4c29 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# v0.1.0 +## 05/07/2018 + +1. [](#new) + * ChangeLog started... diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b5e7990 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Trilby Media + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index cfb122d..6518833 100644 --- a/README.md +++ b/README.md @@ -1 +1,56 @@ -# grav-plugin-login-ldap \ No newline at end of file +# Login Ldap Plugin + +**This README.md file should be modified to describe the features, installation, configuration, and general usage of this plugin.** + +The **Login Ldap** Plugin is for [Grav CMS](http://github.com/getgrav/grav). Allows authentication against an LDAP Server + +## Installation + +Installing the Login Ldap 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 login-ldap + +This will install the Login Ldap plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/login-ldap`. + +### 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 `login-ldap`. You can find these files on [GitHub](https://github.com/trilbymedia/grav-plugin-login-ldap) or via [GetGrav.org](http://getgrav.org/downloads/plugins#extras). + +You should now have all the plugin files under + + /your/site/grav/user/plugins/login-ldap + +> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav) and the [Error](https://github.com/getgrav/grav-plugin-error) and [Problems](https://github.com/getgrav/grav-plugin-problems) to operate. + +### Admin Plugin + +If you use the admin plugin, you can install directly through the admin plugin by browsing the `Plugins` tab and clicking on the `Add` button. + +## Configuration + +Before configuring this plugin, you should copy the `user/plugins/login-ldap/login-ldap.yaml` to `user/config/plugins/login-ldap.yaml` and only edit that copy. + +Here is the default configuration and an explanation of available options: + +```yaml +enabled: true +``` + +Note that if you use the admin plugin, a file with your configuration, and named login-ldap.yaml will be saved in the `user/config/plugins/` folder once the configuration is saved in the admin. + +## Usage + +**Describe how to use the plugin.** + +## Credits + +**Did you incorporate third-party code? Want to thank somebody?** + +## To Do + +- [ ] Future plans, if any + diff --git a/blueprints.yaml b/blueprints.yaml new file mode 100644 index 0000000..279851b --- /dev/null +++ b/blueprints.yaml @@ -0,0 +1,135 @@ +name: Login Ldap +version: 0.1.0 +description: Allows authentication against an LDAP Server +icon: plug +author: + name: Trilby Media + email: hello@trilby.media +homepage: https://github.com/trilbymedia/grav-plugin-login-ldap +demo: http://demo.yoursite.com +keywords: grav, plugin, etc +bugs: https://github.com/trilbymedia/grav-plugin-login-ldap/issues +docs: https://github.com/trilbymedia/grav-plugin-login-ldap/blob/develop/README.md +license: MIT + +form: + validation: strict + fields: + enabled: + type: toggle + label: Plugin status + highlight: 1 + default: 0 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + + server_section: + type: section + title: LDAP Server + + fields: + + host: + type: text + label: Host + help: Host name of the LDAP server + validate: + required: true + + port: + type: number + label: Port + default: 389 + help: Port to connect to host + validate: + required: true + + version: + type: number + label: Version + default: 3 + help: LDAP Version 3 is most popular, only change this if you know what you are doing + validate: + required: true + + ssl: + type: toggle + label: Use SSL + default: 0 + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + start_tls: + type: toggle + label: Negotiate TLS + help: Negotiate TLS encryption with the LDAP server (requires all traffic to be encrypted) + default: 0 + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + opt_referrals: + type: toggle + label: Follow Referrals + help: Sets the value of LDAP_OPT_REFERRALS (Set to "off" for Windows 2003 servers) + default: 0 + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + config_section: + type: section + title: LDAP Configuration + + fields: + + user_dn: + type: text + label: User Search DN + placeholder: uid=[username],dc=company,dc=com + help: String used to authenticate a user, where [username] is directly replaced by user value entered via login + validate: + required: true + + data_dn: + type: text + label: User Data DN + placeholder: dc=company,dc=com + help: String used to retrieve user data. If not provided, extra LDAP user data will not be stored in Grav user account file + + map_username: + type: text + label: Username Mapping + help: LDAP Attribute(s) that contains the user's username + placeholder: uid + validate: + required: true + + map_fullname: + type: text + label: User Fullname Mapping + help: LDAP Attribute(s) that contains the user's full name + placeholder: givenName lastName + validate: + required: true + + map_email: + type: text + label: User Email Mapping + help: LDAP Attribute that contains the user's email + placeholder: mail + validate: + required: true diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7402f00 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "symfony/ldap": "^3.4" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..33e7cbc --- /dev/null +++ b/composer.lock @@ -0,0 +1,236 @@ +{ + "_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": "6197ace98de56caac9e1b8de60b2a2d7", + "packages": [ + { + "name": "symfony/ldap", + "version": "v3.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/ldap.git", + "reference": "be38e9977e072a66b5c1f6c86f6ac1f7c8752736" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ldap/zipball/be38e9977e072a66b5c1f6c86f6ac1f7c8752736", + "reference": "be38e9977e072a66b5c1f6c86f6ac1f7c8752736", + "shasum": "" + }, + "require": { + "ext-ldap": "*", + "php": "^5.5.9|>=7.0.8", + "symfony/options-resolver": "~2.8|~3.0|~4.0", + "symfony/polyfill-php56": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Ldap\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Charles Sarrazin", + "email": "charles@sarraz.in" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "An abstraction in front of PHP's LDAP functions, compatible with PHP 5.3.9 onwards.", + "homepage": "https://symfony.com", + "keywords": [ + "active directory", + "ldap" + ], + "time": "2018-01-03T07:37:34+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v3.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "f3109a6aedd20e35c3a33190e932c2b063b7b50e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/f3109a6aedd20e35c3a33190e932c2b063b7b50e", + "reference": "f3109a6aedd20e35c3a33190e932c2b063b7b50e", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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 OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2018-01-11T07:56:07+00:00" + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "af98553c84912459db3f636329567809d639a8f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/af98553c84912459db3f636329567809d639a8f6", + "reference": "af98553c84912459db3f636329567809d639a8f6", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2018-04-26T10:06:28+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "1a5ad95d9436cbff3296034fe9f8d586dce3fb3a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/1a5ad95d9436cbff3296034fe9f8d586dce3fb3a", + "reference": "1a5ad95d9436cbff3296034fe9f8d586dce3fb3a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "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 utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2018-04-26T10:06:28+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/login-ldap.php b/login-ldap.php new file mode 100644 index 0000000..ab13699 --- /dev/null +++ b/login-ldap.php @@ -0,0 +1,141 @@ + [ + ['autoload', 100000], + ['onPluginsInitialized', 0] + ], + 'onUserLoginAuthenticate' => ['userLoginAuthenticate', 1000], + 'onUserLoginFailure' => ['userLoginFailure', 0], + 'onUserLogin' => ['userLogin', 0], + 'onUserLogout' => ['userLogout', 0], + ]; + } + + /** + * [onPluginsInitialized:100000] Composer autoload. + * + * @return ClassLoader + */ + public function autoload() + { + return require __DIR__ . '/vendor/autoload.php'; + } + + /** + * Initialize the plugin + */ + public function onPluginsInitialized() + { + // Check for PHP LDAP + if (!function_exists('ldap_connect')) { + throw new \RuntimeException('The PHP LDAP module needs to be installed and enabled'); + } + + // Check to ensure login plugin is enabled. + if (!$this->grav['config']->get('plugins.login.enabled')) { + throw new \RuntimeException('The Login plugin needs to be installed and enabled'); + } + } + + public function userLoginAuthenticate(UserLoginEvent $event) + { + $username = $event->getUser(); + $credentials = $event->getCredentials(); + + // This gets fired for user authentication. + $username="cn=amiller,ou=users,dc=trilbymedia,dc=com"; + $credentials="gom8Jabar"; + + try { + + $ldap = Ldap::create('ext_ldap', array( + 'host' => 'ldap.trilbymedia.com', + )); + + + $ldap->bind($username, $credentials); + + + + $query = $ldap->query('dc=trilbymedia,dc=com', 'uid=amiller'); + $results = $query->execute(); + + $userdata = ['ldap' => $results[0]->getAttributes()]; + unset($userdata['ldap']['userPassword']); + + // Create Grav User + $grav_user = User::load($username); + + $current_access = $grav_user->get('access'); + if (!$current_access) { + $access = $this->config->get('plugins.login.user_registration.access.site', []); + if (count($access) > 0) { + $data['access']['site'] = $access; + $grav_user->merge($data); + } + } + + $grav_user->merge($userdata); + $grav_user->save(); + + $event->setUser($grav_user); + + $event->setStatus($event::AUTHENTICATION_SUCCESS); + $event->stopPropagation(); + + return; + + } catch (ConnectionException $e) { + print $e->getMessage(); + + $event->setStatus($event::AUTHENTICATION_FAILURE); + $event->stopPropagation(); + + return; + } + + } + + public function userLoginFailure(UserLoginEvent $event) + { + // This gets fired if user fails to log in. + } + + public function userLogin(UserLoginEvent $event) + { + // This gets fired if user successfully logs in. + } + + public function userLogout(UserLoginEvent $event) + { + // This gets fired on user logout. + } + +} diff --git a/login-ldap.yaml b/login-ldap.yaml new file mode 100644 index 0000000..9f7bbcf --- /dev/null +++ b/login-ldap.yaml @@ -0,0 +1,14 @@ +enabled: true +host: +port: 389 +version: 3 +ssl: false +start_tls: false +opt_referrals: false +user_dn: uid=[username],dc=company,dc=com +search_dn: dc=company,dc=com +map_username: uid +map_fullname: givenName lastName +map_email: mail + + diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..fdc04e8 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $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/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + $vendorDir . '/symfony/polyfill-php56/bootstrap.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/symfony/polyfill-util'), + 'Symfony\\Polyfill\\Php56\\' => array($vendorDir . '/symfony/polyfill-php56'), + 'Symfony\\Component\\OptionsResolver\\' => array($vendorDir . '/symfony/options-resolver'), + 'Symfony\\Component\\Ldap\\' => array($vendorDir . '/symfony/ldap'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..8ef31ba --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit80d9e84728650f554dd1ba63313920f5::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\ComposerStaticInit80d9e84728650f554dd1ba63313920f5::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire80d9e84728650f554dd1ba63313920f5($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire80d9e84728650f554dd1ba63313920f5($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..17cd296 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,50 @@ + __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Polyfill\\Util\\' => 22, + 'Symfony\\Polyfill\\Php56\\' => 23, + 'Symfony\\Component\\OptionsResolver\\' => 34, + 'Symfony\\Component\\Ldap\\' => 23, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Util\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-util', + ), + 'Symfony\\Polyfill\\Php56\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php56', + ), + 'Symfony\\Component\\OptionsResolver\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/options-resolver', + ), + 'Symfony\\Component\\Ldap\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/ldap', + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit80d9e84728650f554dd1ba63313920f5::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit80d9e84728650f554dd1ba63313920f5::$prefixDirsPsr4; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..5913fe6 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,228 @@ +[ + { + "name": "symfony/ldap", + "version": "v3.4.9", + "version_normalized": "3.4.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/ldap.git", + "reference": "be38e9977e072a66b5c1f6c86f6ac1f7c8752736" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ldap/zipball/be38e9977e072a66b5c1f6c86f6ac1f7c8752736", + "reference": "be38e9977e072a66b5c1f6c86f6ac1f7c8752736", + "shasum": "" + }, + "require": { + "ext-ldap": "*", + "php": "^5.5.9|>=7.0.8", + "symfony/options-resolver": "~2.8|~3.0|~4.0", + "symfony/polyfill-php56": "~1.0" + }, + "time": "2018-01-03T07:37:34+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Ldap\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Charles Sarrazin", + "email": "charles@sarraz.in" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "An abstraction in front of PHP's LDAP functions, compatible with PHP 5.3.9 onwards.", + "homepage": "https://symfony.com", + "keywords": [ + "active directory", + "ldap" + ] + }, + { + "name": "symfony/options-resolver", + "version": "v3.4.9", + "version_normalized": "3.4.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "f3109a6aedd20e35c3a33190e932c2b063b7b50e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/f3109a6aedd20e35c3a33190e932c2b063b7b50e", + "reference": "f3109a6aedd20e35c3a33190e932c2b063b7b50e", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "time": "2018-01-11T07:56:07+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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 OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ] + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.8.0", + "version_normalized": "1.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "af98553c84912459db3f636329567809d639a8f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/af98553c84912459db3f636329567809d639a8f6", + "reference": "af98553c84912459db3f636329567809d639a8f6", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "time": "2018-04-26T10:06:28+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/polyfill-util", + "version": "v1.8.0", + "version_normalized": "1.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "1a5ad95d9436cbff3296034fe9f8d586dce3fb3a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/1a5ad95d9436cbff3296034fe9f8d586dce3fb3a", + "reference": "1a5ad95d9436cbff3296034fe9f8d586dce3fb3a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2018-04-26T10:06:28+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "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 utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ] + } +] diff --git a/vendor/symfony/ldap/.gitignore b/vendor/symfony/ldap/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/ldap/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/ldap/Adapter/AbstractConnection.php b/vendor/symfony/ldap/Adapter/AbstractConnection.php new file mode 100644 index 0000000..67529b6 --- /dev/null +++ b/vendor/symfony/ldap/Adapter/AbstractConnection.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\Ldap\Adapter; + +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Charles Sarrazin + */ +abstract class AbstractConnection implements ConnectionInterface +{ + protected $config; + + public function __construct(array $config = array()) + { + $resolver = new OptionsResolver(); + + $this->configureOptions($resolver); + + $this->config = $resolver->resolve($config); + } + + /** + * Configures the adapter's options. + * + * @param OptionsResolver $resolver An OptionsResolver instance + */ + protected function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults(array( + 'host' => 'localhost', + 'version' => 3, + 'connection_string' => null, + 'encryption' => 'none', + 'options' => array(), + )); + + $resolver->setDefault('port', function (Options $options) { + return 'ssl' === $options['encryption'] ? 636 : 389; + }); + + $resolver->setDefault('connection_string', function (Options $options) { + return sprintf('ldap%s://%s:%s', 'ssl' === $options['encryption'] ? 's' : '', $options['host'], $options['port']); + }); + + $resolver->setAllowedTypes('host', 'string'); + $resolver->setAllowedTypes('port', 'numeric'); + $resolver->setAllowedTypes('connection_string', 'string'); + $resolver->setAllowedTypes('version', 'numeric'); + $resolver->setAllowedValues('encryption', array('none', 'ssl', 'tls')); + $resolver->setAllowedTypes('options', 'array'); + } +} diff --git a/vendor/symfony/ldap/Adapter/AbstractQuery.php b/vendor/symfony/ldap/Adapter/AbstractQuery.php new file mode 100644 index 0000000..5cacad7 --- /dev/null +++ b/vendor/symfony/ldap/Adapter/AbstractQuery.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Adapter; + +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Charles Sarrazin + */ +abstract class AbstractQuery implements QueryInterface +{ + protected $connection; + protected $dn; + protected $query; + protected $options; + + public function __construct(ConnectionInterface $connection, $dn, $query, array $options = array()) + { + $resolver = new OptionsResolver(); + $resolver->setDefaults(array( + 'filter' => '*', + 'maxItems' => 0, + 'sizeLimit' => 0, + 'timeout' => 0, + 'deref' => static::DEREF_NEVER, + 'attrsOnly' => 0, + 'scope' => static::SCOPE_SUB, + )); + $resolver->setAllowedValues('deref', array(static::DEREF_ALWAYS, static::DEREF_NEVER, static::DEREF_FINDING, static::DEREF_SEARCHING)); + $resolver->setAllowedValues('scope', array(static::SCOPE_BASE, static::SCOPE_ONE, static::SCOPE_SUB)); + + $resolver->setNormalizer('filter', function (Options $options, $value) { + return is_array($value) ? $value : array($value); + }); + + $this->connection = $connection; + $this->dn = $dn; + $this->query = $query; + $this->options = $resolver->resolve($options); + } +} diff --git a/vendor/symfony/ldap/Adapter/AdapterInterface.php b/vendor/symfony/ldap/Adapter/AdapterInterface.php new file mode 100644 index 0000000..f01e352 --- /dev/null +++ b/vendor/symfony/ldap/Adapter/AdapterInterface.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\Ldap\Adapter; + +/** + * @author Charles Sarrazin + */ +interface AdapterInterface +{ + /** + * Returns the current connection. + * + * @return ConnectionInterface + */ + public function getConnection(); + + /** + * Creates a new Query. + * + * @param string $dn + * @param string $query + * @param array $options + * + * @return QueryInterface + */ + public function createQuery($dn, $query, array $options = array()); + + /** + * Fetches the entry manager instance. + * + * @return EntryManagerInterface + */ + public function getEntryManager(); + + /** + * Escape a string for use in an LDAP filter or DN. + * + * @param string $subject + * @param string $ignore + * @param int $flags + * + * @return string + */ + public function escape($subject, $ignore = '', $flags = 0); +} diff --git a/vendor/symfony/ldap/Adapter/CollectionInterface.php b/vendor/symfony/ldap/Adapter/CollectionInterface.php new file mode 100644 index 0000000..2db4d2b --- /dev/null +++ b/vendor/symfony/ldap/Adapter/CollectionInterface.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\Ldap\Adapter; + +use Symfony\Component\Ldap\Entry; + +/** + * @author Charles Sarrazin + */ +interface CollectionInterface extends \Countable, \IteratorAggregate, \ArrayAccess +{ + /** + * @return Entry[] + */ + public function toArray(); +} diff --git a/vendor/symfony/ldap/Adapter/ConnectionInterface.php b/vendor/symfony/ldap/Adapter/ConnectionInterface.php new file mode 100644 index 0000000..347a852 --- /dev/null +++ b/vendor/symfony/ldap/Adapter/ConnectionInterface.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\Ldap\Adapter; + +/** + * @author Charles Sarrazin + */ +interface ConnectionInterface +{ + /** + * Checks whether the connection was already bound or not. + * + * @return bool + */ + public function isBound(); + + /** + * Binds the connection against a DN and password. + * + * @param string $dn The user's DN + * @param string $password The associated password + */ + public function bind($dn = null, $password = null); +} diff --git a/vendor/symfony/ldap/Adapter/EntryManagerInterface.php b/vendor/symfony/ldap/Adapter/EntryManagerInterface.php new file mode 100644 index 0000000..9538abf --- /dev/null +++ b/vendor/symfony/ldap/Adapter/EntryManagerInterface.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Adapter; + +use Symfony\Component\Ldap\Entry; +use Symfony\Component\Ldap\Exception\LdapException; +use Symfony\Component\Ldap\Exception\NotBoundException; + +/** + * Entry manager interface. + * + * @author Charles Sarrazin + * @author Bob van de Vijver + */ +interface EntryManagerInterface +{ + /** + * Adds a new entry in the Ldap server. + * + * @param Entry $entry + * + * @throws NotBoundException + * @throws LdapException + */ + public function add(Entry $entry); + + /** + * Updates an entry from the Ldap server. + * + * @param Entry $entry + * + * @throws NotBoundException + * @throws LdapException + */ + public function update(Entry $entry); + + /** + * Removes an entry from the Ldap server. + * + * @param Entry $entry + * + * @throws NotBoundException + * @throws LdapException + */ + public function remove(Entry $entry); +} diff --git a/vendor/symfony/ldap/Adapter/ExtLdap/Adapter.php b/vendor/symfony/ldap/Adapter/ExtLdap/Adapter.php new file mode 100644 index 0000000..05fbce0 --- /dev/null +++ b/vendor/symfony/ldap/Adapter/ExtLdap/Adapter.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Adapter\ExtLdap; + +use Symfony\Component\Ldap\Adapter\AdapterInterface; +use Symfony\Component\Ldap\Exception\LdapException; + +/** + * @author Charles Sarrazin + */ +class Adapter implements AdapterInterface +{ + private $config; + private $connection; + private $entryManager; + + public function __construct(array $config = array()) + { + if (!extension_loaded('ldap')) { + throw new LdapException('The LDAP PHP extension is not enabled.'); + } + + $this->config = $config; + } + + /** + * {@inheritdoc} + */ + public function getConnection() + { + if (null === $this->connection) { + $this->connection = new Connection($this->config); + } + + return $this->connection; + } + + /** + * {@inheritdoc} + */ + public function getEntryManager() + { + if (null === $this->entryManager) { + $this->entryManager = new EntryManager($this->getConnection()); + } + + return $this->entryManager; + } + + /** + * {@inheritdoc} + */ + public function createQuery($dn, $query, array $options = array()) + { + return new Query($this->getConnection(), $dn, $query, $options); + } + + /** + * {@inheritdoc} + */ + public function escape($subject, $ignore = '', $flags = 0) + { + $value = ldap_escape($subject, $ignore, $flags); + + // Per RFC 4514, leading/trailing spaces should be encoded in DNs, as well as carriage returns. + if ((int) $flags & LDAP_ESCAPE_DN) { + if (!empty($value) && ' ' === $value[0]) { + $value = '\\20'.substr($value, 1); + } + if (!empty($value) && ' ' === $value[strlen($value) - 1]) { + $value = substr($value, 0, -1).'\\20'; + } + $value = str_replace("\r", '\0d', $value); + } + + return $value; + } +} diff --git a/vendor/symfony/ldap/Adapter/ExtLdap/Collection.php b/vendor/symfony/ldap/Adapter/ExtLdap/Collection.php new file mode 100644 index 0000000..581cf15 --- /dev/null +++ b/vendor/symfony/ldap/Adapter/ExtLdap/Collection.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Adapter\ExtLdap; + +use Symfony\Component\Ldap\Adapter\CollectionInterface; +use Symfony\Component\Ldap\Entry; +use Symfony\Component\Ldap\Exception\LdapException; + +/** + * @author Charles Sarrazin + */ +class Collection implements CollectionInterface +{ + private $connection; + private $search; + private $entries; + + public function __construct(Connection $connection, Query $search) + { + $this->connection = $connection; + $this->search = $search; + } + + /** + * {@inheritdoc} + */ + public function toArray() + { + if (null === $this->entries) { + $this->entries = iterator_to_array($this->getIterator(), false); + } + + return $this->entries; + } + + public function count() + { + if (false !== $count = ldap_count_entries($this->connection->getResource(), $this->search->getResource())) { + return $count; + } + + throw new LdapException(sprintf('Error while retrieving entry count: %s.', ldap_error($this->connection->getResource()))); + } + + public function getIterator() + { + $con = $this->connection->getResource(); + $search = $this->search->getResource(); + $current = ldap_first_entry($con, $search); + + if (0 === $this->count()) { + return; + } + + if (false === $current) { + throw new LdapException(sprintf('Could not rewind entries array: %s.', ldap_error($con))); + } + + yield $this->getSingleEntry($con, $current); + + while (false !== $current = ldap_next_entry($con, $current)) { + yield $this->getSingleEntry($con, $current); + } + } + + public function offsetExists($offset) + { + $this->toArray(); + + return isset($this->entries[$offset]); + } + + public function offsetGet($offset) + { + $this->toArray(); + + return isset($this->entries[$offset]) ? $this->entries[$offset] : null; + } + + public function offsetSet($offset, $value) + { + $this->toArray(); + + $this->entries[$offset] = $value; + } + + public function offsetUnset($offset) + { + $this->toArray(); + + unset($this->entries[$offset]); + } + + private function getSingleEntry($con, $current) + { + $attributes = ldap_get_attributes($con, $current); + + if (false === $attributes) { + throw new LdapException(sprintf('Could not fetch attributes: %s.', ldap_error($con))); + } + + $attributes = $this->cleanupAttributes($attributes); + + $dn = ldap_get_dn($con, $current); + + if (false === $dn) { + throw new LdapException(sprintf('Could not fetch DN: %s.', ldap_error($con))); + } + + return new Entry($dn, $attributes); + } + + private function cleanupAttributes(array $entry) + { + $attributes = array_diff_key($entry, array_flip(range(0, $entry['count'] - 1)) + array( + 'count' => null, + 'dn' => null, + )); + array_walk($attributes, function (&$value) { + unset($value['count']); + }); + + return $attributes; + } +} diff --git a/vendor/symfony/ldap/Adapter/ExtLdap/Connection.php b/vendor/symfony/ldap/Adapter/ExtLdap/Connection.php new file mode 100644 index 0000000..1a6ea28 --- /dev/null +++ b/vendor/symfony/ldap/Adapter/ExtLdap/Connection.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\Ldap\Adapter\ExtLdap; + +use Symfony\Component\Ldap\Adapter\AbstractConnection; +use Symfony\Component\Ldap\Exception\ConnectionException; +use Symfony\Component\Ldap\Exception\LdapException; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Charles Sarrazin + */ +class Connection extends AbstractConnection +{ + /** @var bool */ + private $bound = false; + + /** @var resource */ + private $connection; + + public function __destruct() + { + $this->disconnect(); + } + + /** + * {@inheritdoc} + */ + public function isBound() + { + return $this->bound; + } + + /** + * {@inheritdoc} + */ + public function bind($dn = null, $password = null) + { + if (!$this->connection) { + $this->connect(); + } + + if (false === @ldap_bind($this->connection, $dn, $password)) { + throw new ConnectionException(ldap_error($this->connection)); + } + + $this->bound = true; + } + + /** + * Returns a link resource. + * + * @return resource + * + * @internal + */ + public function getResource() + { + return $this->connection; + } + + public function setOption($name, $value) + { + if (!@ldap_set_option($this->connection, ConnectionOptions::getOption($name), $value)) { + throw new LdapException(sprintf('Could not set value "%s" for option "%s".', $value, $name)); + } + } + + public function getOption($name) + { + if (!@ldap_get_option($this->connection, ConnectionOptions::getOption($name), $ret)) { + throw new LdapException(sprintf('Could not retrieve value for option "%s".', $name)); + } + + return $ret; + } + + protected function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + + $resolver->setDefault('debug', false); + $resolver->setAllowedTypes('debug', 'bool'); + $resolver->setDefault('referrals', false); + $resolver->setAllowedTypes('referrals', 'bool'); + + $resolver->setNormalizer('options', function (Options $options, $value) { + if (true === $options['debug']) { + $value['debug_level'] = 7; + } + + if (!isset($value['protocol_version'])) { + $value['protocol_version'] = $options['version']; + } + + if (!isset($value['referrals'])) { + $value['referrals'] = $options['referrals']; + } + + return $value; + }); + + $resolver->setAllowedValues('options', function (array $values) { + foreach ($values as $name => $value) { + if (!ConnectionOptions::isOption($name)) { + return false; + } + } + + return true; + }); + } + + private function connect() + { + if ($this->connection) { + return; + } + + $this->connection = ldap_connect($this->config['connection_string']); + + foreach ($this->config['options'] as $name => $value) { + $this->setOption($name, $value); + } + + if (false === $this->connection) { + throw new LdapException(sprintf('Could not connect to Ldap server: %s.', ldap_error($this->connection))); + } + + if ('tls' === $this->config['encryption'] && false === ldap_start_tls($this->connection)) { + throw new LdapException(sprintf('Could not initiate TLS connection: %s.', ldap_error($this->connection))); + } + } + + private function disconnect() + { + if ($this->connection && is_resource($this->connection)) { + ldap_close($this->connection); + } + + $this->connection = null; + $this->bound = false; + } +} diff --git a/vendor/symfony/ldap/Adapter/ExtLdap/ConnectionOptions.php b/vendor/symfony/ldap/Adapter/ExtLdap/ConnectionOptions.php new file mode 100644 index 0000000..6d878aa --- /dev/null +++ b/vendor/symfony/ldap/Adapter/ExtLdap/ConnectionOptions.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\Ldap\Adapter\ExtLdap; + +use Symfony\Component\Ldap\Exception\LdapException; + +/** + * A class representing the Ldap extension's options, which can be used with + * ldap_set_option or ldap_get_option. + * + * @author Charles Sarrazin + * + * @internal + */ +final class ConnectionOptions +{ + const API_INFO = 0x00; + const DEREF = 0x02; + const SIZELIMIT = 0x03; + const TIMELIMIT = 0x04; + const REFERRALS = 0x08; + const RESTART = 0x09; + const PROTOCOL_VERSION = 0x11; + const SERVER_CONTROLS = 0x12; + const CLIENT_CONTROLS = 0x13; + const API_FEATURE_INFO = 0x15; + const HOST_NAME = 0x30; + const ERROR_NUMBER = 0x31; + const ERROR_STRING = 0x32; + const MATCHED_DN = 0x33; + const DEBUG_LEVEL = 0x5001; + const NETWORK_TIMEOUT = 0x5005; + const X_SASL_MECH = 0x6100; + const X_SASL_REALM = 0x6101; + const X_SASL_AUTHCID = 0x6102; + const X_SASL_AUTHZID = 0x6103; + + public static function getOptionName($name) + { + return sprintf('%s::%s', self::class, strtoupper($name)); + } + + /** + * Fetches an option's corresponding constant value from an option name. + * The option name can either be in snake or camel case. + * + * @param string $name + * + * @return int + * + * @throws LdapException + */ + public static function getOption($name) + { + // Convert + $constantName = self::getOptionName($name); + + if (!defined($constantName)) { + throw new LdapException(sprintf('Unknown option "%s".', $name)); + } + + return constant($constantName); + } + + public static function isOption($name) + { + return defined(self::getOptionName($name)); + } +} diff --git a/vendor/symfony/ldap/Adapter/ExtLdap/EntryManager.php b/vendor/symfony/ldap/Adapter/ExtLdap/EntryManager.php new file mode 100644 index 0000000..871c4b0 --- /dev/null +++ b/vendor/symfony/ldap/Adapter/ExtLdap/EntryManager.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Adapter\ExtLdap; + +use Symfony\Component\Ldap\Adapter\EntryManagerInterface; +use Symfony\Component\Ldap\Adapter\RenameEntryInterface; +use Symfony\Component\Ldap\Entry; +use Symfony\Component\Ldap\Exception\LdapException; +use Symfony\Component\Ldap\Exception\NotBoundException; + +/** + * @author Charles Sarrazin + * @author Bob van de Vijver + */ +class EntryManager implements EntryManagerInterface, RenameEntryInterface +{ + private $connection; + + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * {@inheritdoc} + */ + public function add(Entry $entry) + { + $con = $this->getConnectionResource(); + + if (!@ldap_add($con, $entry->getDn(), $entry->getAttributes())) { + throw new LdapException(sprintf('Could not add entry "%s": %s.', $entry->getDn(), ldap_error($con))); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function update(Entry $entry) + { + $con = $this->getConnectionResource(); + + if (!@ldap_modify($con, $entry->getDn(), $entry->getAttributes())) { + throw new LdapException(sprintf('Could not update entry "%s": %s.', $entry->getDn(), ldap_error($con))); + } + } + + /** + * {@inheritdoc} + */ + public function remove(Entry $entry) + { + $con = $this->getConnectionResource(); + + if (!@ldap_delete($con, $entry->getDn())) { + throw new LdapException(sprintf('Could not remove entry "%s": %s.', $entry->getDn(), ldap_error($con))); + } + } + + /** + * {@inheritdoc} + */ + public function rename(Entry $entry, $newRdn, $removeOldRdn = true) + { + $con = $this->getConnectionResource(); + + if (!@ldap_rename($con, $entry->getDn(), $newRdn, null, $removeOldRdn)) { + throw new LdapException(sprintf('Could not rename entry "%s" to "%s": %s.', $entry->getDn(), $newRdn, ldap_error($con))); + } + } + + /** + * Get the connection resource, but first check if the connection is bound. + */ + private function getConnectionResource() + { + // If the connection is not bound, throw an exception. Users should use an explicit bind call first. + if (!$this->connection->isBound()) { + throw new NotBoundException('Query execution is not possible without binding the connection first.'); + } + + return $this->connection->getResource(); + } +} diff --git a/vendor/symfony/ldap/Adapter/ExtLdap/Query.php b/vendor/symfony/ldap/Adapter/ExtLdap/Query.php new file mode 100644 index 0000000..09cb42c --- /dev/null +++ b/vendor/symfony/ldap/Adapter/ExtLdap/Query.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Adapter\ExtLdap; + +use Symfony\Component\Ldap\Adapter\AbstractQuery; +use Symfony\Component\Ldap\Exception\LdapException; +use Symfony\Component\Ldap\Exception\NotBoundException; + +/** + * @author Charles Sarrazin + * @author Bob van de Vijver + */ +class Query extends AbstractQuery +{ + /** @var Connection */ + protected $connection; + + /** @var resource */ + private $search; + + public function __construct(Connection $connection, $dn, $query, array $options = array()) + { + parent::__construct($connection, $dn, $query, $options); + } + + public function __destruct() + { + $con = $this->connection->getResource(); + $this->connection = null; + + if (null === $this->search || false === $this->search) { + return; + } + + $success = ldap_free_result($this->search); + $this->search = null; + + if (!$success) { + throw new LdapException(sprintf('Could not free results: %s.', ldap_error($con))); + } + } + + /** + * {@inheritdoc} + */ + public function execute() + { + if (null === $this->search) { + // If the connection is not bound, throw an exception. Users should use an explicit bind call first. + if (!$this->connection->isBound()) { + throw new NotBoundException('Query execution is not possible without binding the connection first.'); + } + + $con = $this->connection->getResource(); + + switch ($this->options['scope']) { + case static::SCOPE_BASE: + $func = 'ldap_read'; + break; + case static::SCOPE_ONE: + $func = 'ldap_list'; + break; + case static::SCOPE_SUB: + $func = 'ldap_search'; + break; + default: + throw new LdapException(sprintf('Could not search in scope "%s".', $this->options['scope'])); + } + + $this->search = @$func( + $con, + $this->dn, + $this->query, + $this->options['filter'], + $this->options['attrsOnly'], + $this->options['maxItems'], + $this->options['timeout'], + $this->options['deref'] + ); + } + + if (false === $this->search) { + throw new LdapException(sprintf('Could not complete search with dn "%s", query "%s" and filters "%s".', $this->dn, $this->query, implode(',', $this->options['filter']))); + } + + return new Collection($this->connection, $this); + } + + /** + * Returns a LDAP search resource. + * + * @return resource + * + * @internal + */ + public function getResource() + { + return $this->search; + } +} diff --git a/vendor/symfony/ldap/Adapter/QueryInterface.php b/vendor/symfony/ldap/Adapter/QueryInterface.php new file mode 100644 index 0000000..afeff7f --- /dev/null +++ b/vendor/symfony/ldap/Adapter/QueryInterface.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\Ldap\Adapter; + +use Symfony\Component\Ldap\Entry; +use Symfony\Component\Ldap\Exception\LdapException; +use Symfony\Component\Ldap\Exception\NotBoundException; + +/** + * @author Charles Sarrazin + * @author Bob van de Vijver + */ +interface QueryInterface +{ + const DEREF_NEVER = 0x00; + const DEREF_SEARCHING = 0x01; + const DEREF_FINDING = 0x02; + const DEREF_ALWAYS = 0x03; + + const SCOPE_BASE = 'base'; + const SCOPE_ONE = 'one'; + const SCOPE_SUB = 'sub'; + + /** + * Executes a query and returns the list of Ldap entries. + * + * @return CollectionInterface|Entry[] + * + * @throws NotBoundException + * @throws LdapException + */ + public function execute(); +} diff --git a/vendor/symfony/ldap/Adapter/RenameEntryInterface.php b/vendor/symfony/ldap/Adapter/RenameEntryInterface.php new file mode 100644 index 0000000..c15cb16 --- /dev/null +++ b/vendor/symfony/ldap/Adapter/RenameEntryInterface.php @@ -0,0 +1,22 @@ + + * + * @deprecated since version 3.3, will be merged with {@link EntryManagerInterface} in 4.0. + */ +interface RenameEntryInterface +{ + /** + * Renames an entry on the Ldap server. + * + * @param Entry $entry + * @param string $newRdn + * @param bool $removeOldRdn + */ + public function rename(Entry $entry, $newRdn, $removeOldRdn = true); +} diff --git a/vendor/symfony/ldap/CHANGELOG.md b/vendor/symfony/ldap/CHANGELOG.md new file mode 100644 index 0000000..5d90f27 --- /dev/null +++ b/vendor/symfony/ldap/CHANGELOG.md @@ -0,0 +1,12 @@ +CHANGELOG +========= + +3.3.0 +----- + +* The `RenameEntryInterface` inferface is deprecated, and will be merged with `EntryManagerInterface` in 4.0. + +3.1.0 +----- + + * The `LdapClient` class is deprecated. Use the `Ldap` class instead. diff --git a/vendor/symfony/ldap/Entry.php b/vendor/symfony/ldap/Entry.php new file mode 100644 index 0000000..42745c2 --- /dev/null +++ b/vendor/symfony/ldap/Entry.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap; + +/** + * @author Charles Sarrazin + */ +class Entry +{ + private $dn; + private $attributes; + + public function __construct($dn, array $attributes = array()) + { + $this->dn = $dn; + $this->attributes = $attributes; + } + + /** + * Returns the entry's DN. + * + * @return string + */ + public function getDn() + { + return $this->dn; + } + + /** + * Returns whether an attribute exists. + * + * @param $name string The name of the attribute + * + * @return bool + */ + public function hasAttribute($name) + { + return isset($this->attributes[$name]); + } + + /** + * Returns a specific attribute's value. + * + * As LDAP can return multiple values for a single attribute, + * this value is returned as an array. + * + * @param $name string The name of the attribute + * + * @return null|array + */ + public function getAttribute($name) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : null; + } + + /** + * Returns the complete list of attributes. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Sets a value for the given attribute. + * + * @param string $name + * @param array $value + */ + public function setAttribute($name, array $value) + { + $this->attributes[$name] = $value; + } + + /** + * Removes a given attribute. + * + * @param string $name + */ + public function removeAttribute($name) + { + unset($this->attributes[$name]); + } +} diff --git a/vendor/symfony/ldap/Exception/ConnectionException.php b/vendor/symfony/ldap/Exception/ConnectionException.php new file mode 100644 index 0000000..cded4cf --- /dev/null +++ b/vendor/symfony/ldap/Exception/ConnectionException.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\Ldap\Exception; + +/** + * ConnectionException is throw if binding to ldap can not be established. + * + * @author Grégoire Pineau + */ +class ConnectionException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/ldap/Exception/DriverNotFoundException.php b/vendor/symfony/ldap/Exception/DriverNotFoundException.php new file mode 100644 index 0000000..4025843 --- /dev/null +++ b/vendor/symfony/ldap/Exception/DriverNotFoundException.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\Ldap\Exception; + +/** + * LdapException is throw if php ldap module is not loaded. + * + * @author Charles Sarrazin + */ +class DriverNotFoundException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/ldap/Exception/ExceptionInterface.php b/vendor/symfony/ldap/Exception/ExceptionInterface.php new file mode 100644 index 0000000..b861a3f --- /dev/null +++ b/vendor/symfony/ldap/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Exception; + +/** + * Base ExceptionInterface for the Ldap component. + * + * @author Charles Sarrazin + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/ldap/Exception/LdapException.php b/vendor/symfony/ldap/Exception/LdapException.php new file mode 100644 index 0000000..4045f32 --- /dev/null +++ b/vendor/symfony/ldap/Exception/LdapException.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\Ldap\Exception; + +/** + * LdapException is throw if php ldap module is not loaded. + * + * @author Grégoire Pineau + */ +class LdapException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/ldap/Exception/NotBoundException.php b/vendor/symfony/ldap/Exception/NotBoundException.php new file mode 100644 index 0000000..6eb904e --- /dev/null +++ b/vendor/symfony/ldap/Exception/NotBoundException.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\Ldap\Exception; + +/** + * NotBoundException is thrown if the connection with the LDAP server is not yet bound. + * + * @author Bob van de Vijver + */ +class NotBoundException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/ldap/LICENSE b/vendor/symfony/ldap/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/ldap/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/ldap/Ldap.php b/vendor/symfony/ldap/Ldap.php new file mode 100644 index 0000000..514f51d --- /dev/null +++ b/vendor/symfony/ldap/Ldap.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap; + +use Symfony\Component\Ldap\Adapter\AdapterInterface; +use Symfony\Component\Ldap\Exception\DriverNotFoundException; + +/** + * @author Charles Sarrazin + */ +final class Ldap implements LdapInterface +{ + private $adapter; + + private static $adapterMap = array( + 'ext_ldap' => 'Symfony\Component\Ldap\Adapter\ExtLdap\Adapter', + ); + + public function __construct(AdapterInterface $adapter) + { + $this->adapter = $adapter; + } + + /** + * {@inheritdoc} + */ + public function bind($dn = null, $password = null) + { + $this->adapter->getConnection()->bind($dn, $password); + } + + /** + * {@inheritdoc} + */ + public function query($dn, $query, array $options = array()) + { + return $this->adapter->createQuery($dn, $query, $options); + } + + /** + * {@inheritdoc} + */ + public function getEntryManager() + { + return $this->adapter->getEntryManager(); + } + + /** + * {@inheritdoc} + */ + public function escape($subject, $ignore = '', $flags = 0) + { + return $this->adapter->escape($subject, $ignore, $flags); + } + + /** + * Creates a new Ldap instance. + * + * @param string $adapter The adapter name + * @param array $config The adapter's configuration + * + * @return static + */ + public static function create($adapter, array $config = array()) + { + if (!isset(self::$adapterMap[$adapter])) { + throw new DriverNotFoundException(sprintf( + 'Adapter "%s" not found. You should use one of: %s', + $adapter, + implode(', ', self::$adapterMap) + )); + } + + $class = self::$adapterMap[$adapter]; + + return new self(new $class($config)); + } +} diff --git a/vendor/symfony/ldap/LdapClient.php b/vendor/symfony/ldap/LdapClient.php new file mode 100644 index 0000000..4776879 --- /dev/null +++ b/vendor/symfony/ldap/LdapClient.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap; + +@trigger_error('The '.__NAMESPACE__.'\LdapClient class is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Ldap class directly instead.', E_USER_DEPRECATED); + +/** + * @author Grégoire Pineau + * @author Francis Besset + * @author Charles Sarrazin + * + * @deprecated since version 3.1, to be removed in 4.0. Use the Ldap class instead. + */ +final class LdapClient implements LdapClientInterface +{ + private $ldap; + + public function __construct($host = null, $port = 389, $version = 3, $useSsl = false, $useStartTls = false, $optReferrals = false, LdapInterface $ldap = null) + { + $config = $this->normalizeConfig($host, $port, $version, $useSsl, $useStartTls, $optReferrals); + + $this->ldap = null !== $ldap ? $ldap : Ldap::create('ext_ldap', $config); + } + + /** + * {@inheritdoc} + */ + public function bind($dn = null, $password = null) + { + $this->ldap->bind($dn, $password); + } + + /** + * {@inheritdoc} + */ + public function query($dn, $query, array $options = array()) + { + return $this->ldap->query($dn, $query, $options); + } + + /** + * {@inheritdoc} + */ + public function getEntryManager() + { + return $this->ldap->getEntryManager(); + } + + /** + * {@inheritdoc} + */ + public function find($dn, $query, $filter = '*') + { + @trigger_error('The "find" method is deprecated since Symfony 3.1 and will be removed in 4.0. Use the "query" method instead.', E_USER_DEPRECATED); + + $query = $this->ldap->query($dn, $query, array('filter' => $filter)); + $entries = $query->execute(); + $result = array( + 'count' => 0, + ); + + foreach ($entries as $entry) { + $resultEntry = array(); + + foreach ($entry->getAttributes() as $attribute => $values) { + $resultAttribute = array( + 'count' => count($values), + ); + + foreach ($values as $val) { + $resultAttribute[] = $val; + } + $attributeName = strtolower($attribute); + + $resultAttribute['count'] = count($values); + $resultEntry[$attributeName] = $resultAttribute; + $resultEntry[] = $attributeName; + } + + $resultEntry['count'] = count($resultEntry) / 2; + $resultEntry['dn'] = $entry->getDn(); + $result[] = $resultEntry; + } + + $result['count'] = count($result) - 1; + + return $result; + } + + /** + * {@inheritdoc} + */ + public function escape($subject, $ignore = '', $flags = 0) + { + return $this->ldap->escape($subject, $ignore, $flags); + } + + private function normalizeConfig($host, $port, $version, $useSsl, $useStartTls, $optReferrals) + { + if ((bool) $useSsl) { + $encryption = 'ssl'; + } elseif ((bool) $useStartTls) { + $encryption = 'tls'; + } else { + $encryption = 'none'; + } + + return array( + 'host' => $host, + 'port' => $port, + 'encryption' => $encryption, + 'options' => array( + 'protocol_version' => $version, + 'referrals' => (bool) $optReferrals, + ), + ); + } +} diff --git a/vendor/symfony/ldap/LdapClientInterface.php b/vendor/symfony/ldap/LdapClientInterface.php new file mode 100644 index 0000000..0872ee0 --- /dev/null +++ b/vendor/symfony/ldap/LdapClientInterface.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\Ldap; + +/** + * Ldap interface. + * + * This interface is used for the BC layer with branch 2.8 and 3.0. + * + * @author Grégoire Pineau + * @author Charles Sarrazin + * + * @deprecated since version 3.1, to be removed in 4.0. Use the LdapInterface instead. + */ +interface LdapClientInterface extends LdapInterface +{ + /** + * Find a username into ldap connection. + * + * @param string $dn + * @param string $query + * @param mixed $filter + * + * @return array|null + */ + public function find($dn, $query, $filter = '*'); +} diff --git a/vendor/symfony/ldap/LdapInterface.php b/vendor/symfony/ldap/LdapInterface.php new file mode 100644 index 0000000..9f24276 --- /dev/null +++ b/vendor/symfony/ldap/LdapInterface.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap; + +use Symfony\Component\Ldap\Adapter\EntryManagerInterface; +use Symfony\Component\Ldap\Adapter\QueryInterface; +use Symfony\Component\Ldap\Exception\ConnectionException; + +/** + * Ldap interface. + * + * @author Charles Sarrazin + */ +interface LdapInterface +{ + const ESCAPE_FILTER = 0x01; + const ESCAPE_DN = 0x02; + + /** + * Return a connection bound to the ldap. + * + * @param string $dn A LDAP dn + * @param string $password A password + * + * @throws ConnectionException if dn / password could not be bound + */ + public function bind($dn = null, $password = null); + + /** + * Queries a ldap server for entries matching the given criteria. + * + * @param string $dn + * @param string $query + * @param array $options + * + * @return QueryInterface + */ + public function query($dn, $query, array $options = array()); + + /** + * @return EntryManagerInterface + */ + public function getEntryManager(); + + /** + * Escape a string for use in an LDAP filter or DN. + * + * @param string $subject + * @param string $ignore + * @param int $flags + * + * @return string + */ + public function escape($subject, $ignore = '', $flags = 0); +} diff --git a/vendor/symfony/ldap/README.md b/vendor/symfony/ldap/README.md new file mode 100644 index 0000000..b79d60b --- /dev/null +++ b/vendor/symfony/ldap/README.md @@ -0,0 +1,21 @@ +Ldap Component +============== + +A Ldap client for PHP on top of PHP's ldap extension. + +Disclaimer +---------- + +This component is only stable since Symfony 3.1. Earlier versions +have been marked as internal as they still needed some work. +Breaking changes were introduced in Symfony 3.1, so code relying on +previous version of the component will break with this version. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/ldap) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/ldap/Tests/Adapter/ExtLdap/AdapterTest.php b/vendor/symfony/ldap/Tests/Adapter/ExtLdap/AdapterTest.php new file mode 100644 index 0000000..f238361 --- /dev/null +++ b/vendor/symfony/ldap/Tests/Adapter/ExtLdap/AdapterTest.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\Ldap\Tests; + +use Symfony\Component\Ldap\Adapter\ExtLdap\Adapter; +use Symfony\Component\Ldap\Adapter\ExtLdap\Collection; +use Symfony\Component\Ldap\Adapter\ExtLdap\Query; +use Symfony\Component\Ldap\Entry; +use Symfony\Component\Ldap\Exception\NotBoundException; +use Symfony\Component\Ldap\LdapInterface; + +/** + * @requires extension ldap + */ +class AdapterTest extends LdapTestCase +{ + public function testLdapEscape() + { + $ldap = new Adapter(); + + $this->assertEquals('\20foo\3dbar\0d(baz)*\20', $ldap->escape(" foo=bar\r(baz)* ", null, LdapInterface::ESCAPE_DN)); + } + + /** + * @group functional + */ + public function testLdapQuery() + { + $ldap = new Adapter($this->getLdapConfig()); + + $ldap->getConnection()->bind('cn=admin,dc=symfony,dc=com', 'symfony'); + $query = $ldap->createQuery('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))', array()); + $result = $query->execute(); + + $this->assertInstanceOf(Collection::class, $result); + $this->assertCount(1, $result); + + $entry = $result[0]; + $this->assertInstanceOf(Entry::class, $entry); + $this->assertEquals(array('Fabien Potencier'), $entry->getAttribute('cn')); + $this->assertEquals(array('fabpot@symfony.com', 'fabien@potencier.com'), $entry->getAttribute('mail')); + } + + /** + * @group functional + */ + public function testLdapQueryIterator() + { + $ldap = new Adapter($this->getLdapConfig()); + + $ldap->getConnection()->bind('cn=admin,dc=symfony,dc=com', 'symfony'); + $query = $ldap->createQuery('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))', array()); + $result = $query->execute(); + $iterator = $result->getIterator(); + $iterator->rewind(); + $entry = $iterator->current(); + $this->assertInstanceOf(Entry::class, $entry); + $this->assertEquals(array('Fabien Potencier'), $entry->getAttribute('cn')); + $this->assertEquals(array('fabpot@symfony.com', 'fabien@potencier.com'), $entry->getAttribute('mail')); + } + + /** + * @group functional + */ + public function testLdapQueryWithoutBind() + { + $ldap = new Adapter($this->getLdapConfig()); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(NotBoundException::class); + $query = $ldap->createQuery('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))', array()); + $query->execute(); + } + + public function testLdapQueryScopeBase() + { + $ldap = new Adapter($this->getLdapConfig()); + + $ldap->getConnection()->bind('cn=admin,dc=symfony,dc=com', 'symfony'); + + $query = $ldap->createQuery('cn=Fabien Potencier,dc=symfony,dc=com', '(objectclass=*)', array( + 'scope' => Query::SCOPE_BASE, + )); + $result = $query->execute(); + + $entry = $result[0]; + $this->assertEquals($result->count(), 1); + $this->assertEquals(array('Fabien Potencier'), $entry->getAttribute('cn')); + } + + public function testLdapQueryScopeOneLevel() + { + $ldap = new Adapter($this->getLdapConfig()); + + $ldap->getConnection()->bind('cn=admin,dc=symfony,dc=com', 'symfony'); + + $one_level_result = $ldap->createQuery('ou=Components,dc=symfony,dc=com', '(objectclass=*)', array( + 'scope' => Query::SCOPE_ONE, + ))->execute(); + + $subtree_count = $ldap->createQuery('ou=Components,dc=symfony,dc=com', '(objectclass=*)')->execute()->count(); + + $this->assertNotEquals($one_level_result->count(), $subtree_count); + $this->assertEquals($one_level_result->count(), 1); + $this->assertEquals($one_level_result[0]->getAttribute('ou'), array('Ldap')); + } +} diff --git a/vendor/symfony/ldap/Tests/Adapter/ExtLdap/LdapManagerTest.php b/vendor/symfony/ldap/Tests/Adapter/ExtLdap/LdapManagerTest.php new file mode 100644 index 0000000..855a575 --- /dev/null +++ b/vendor/symfony/ldap/Tests/Adapter/ExtLdap/LdapManagerTest.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Tests; + +use Symfony\Component\Ldap\Adapter\ExtLdap\Adapter; +use Symfony\Component\Ldap\Adapter\ExtLdap\Collection; +use Symfony\Component\Ldap\Entry; +use Symfony\Component\Ldap\Exception\LdapException; +use Symfony\Component\Ldap\Exception\NotBoundException; + +/** + * @requires extension ldap + */ +class LdapManagerTest extends LdapTestCase +{ + /** @var Adapter */ + private $adapter; + + protected function setUp() + { + $this->adapter = new Adapter($this->getLdapConfig()); + $this->adapter->getConnection()->bind('cn=admin,dc=symfony,dc=com', 'symfony'); + } + + /** + * @group functional + */ + public function testLdapAddAndRemove() + { + $this->executeSearchQuery(1); + + $entry = new Entry('cn=Charles Sarrazin,dc=symfony,dc=com', array( + 'sn' => array('csarrazi'), + 'objectclass' => array( + 'inetOrgPerson', + ), + )); + + $em = $this->adapter->getEntryManager(); + $em->add($entry); + + $this->executeSearchQuery(2); + + $em->remove($entry); + $this->executeSearchQuery(1); + } + + /** + * @group functional + */ + public function testLdapAddInvalidEntry() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(LdapException::class); + $this->executeSearchQuery(1); + + // The entry is missing a subject name + $entry = new Entry('cn=Charles Sarrazin,dc=symfony,dc=com', array( + 'objectclass' => array( + 'inetOrgPerson', + ), + )); + + $em = $this->adapter->getEntryManager(); + $em->add($entry); + } + + /** + * @group functional + */ + public function testLdapUpdate() + { + $result = $this->executeSearchQuery(1); + + $entry = $result[0]; + $this->assertNull($entry->getAttribute('email')); + + $em = $this->adapter->getEntryManager(); + $em->update($entry); + + $result = $this->executeSearchQuery(1); + + $entry = $result[0]; + $this->assertNull($entry->getAttribute('email')); + + $entry->removeAttribute('email'); + $em->update($entry); + + $result = $this->executeSearchQuery(1); + $entry = $result[0]; + $this->assertNull($entry->getAttribute('email')); + } + + /** + * @group functional + */ + public function testLdapUnboundAdd() + { + $this->adapter = new Adapter($this->getLdapConfig()); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(NotBoundException::class); + $em = $this->adapter->getEntryManager(); + $em->add(new Entry('')); + } + + /** + * @group functional + */ + public function testLdapUnboundRemove() + { + $this->adapter = new Adapter($this->getLdapConfig()); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(NotBoundException::class); + $em = $this->adapter->getEntryManager(); + $em->remove(new Entry('')); + } + + /** + * @group functional + */ + public function testLdapUnboundUpdate() + { + $this->adapter = new Adapter($this->getLdapConfig()); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(NotBoundException::class); + $em = $this->adapter->getEntryManager(); + $em->update(new Entry('')); + } + + /** + * @return Collection|Entry[] + */ + private function executeSearchQuery($expectedResults = 1) + { + $results = $this + ->adapter + ->createQuery('dc=symfony,dc=com', '(objectclass=person)') + ->execute() + ; + + $this->assertCount($expectedResults, $results); + + return $results; + } + + /** + * @group functional + */ + public function testLdapRename() + { + $result = $this->executeSearchQuery(1); + + $entry = $result[0]; + + $entryManager = $this->adapter->getEntryManager(); + $entryManager->rename($entry, 'cn=Kevin'); + + $result = $this->executeSearchQuery(1); + $renamedEntry = $result[0]; + $this->assertEquals($renamedEntry->getAttribute('cn')[0], 'Kevin'); + + $oldRdn = $entry->getAttribute('cn')[0]; + $entryManager->rename($renamedEntry, 'cn='.$oldRdn); + $this->executeSearchQuery(1); + } + + /** + * @group functional + */ + public function testLdapRenameWithoutRemovingOldRdn() + { + $result = $this->executeSearchQuery(1); + + $entry = $result[0]; + + $entryManager = $this->adapter->getEntryManager(); + $entryManager->rename($entry, 'cn=Kevin', false); + + $result = $this->executeSearchQuery(1); + + $newEntry = $result[0]; + $originalCN = $entry->getAttribute('cn')[0]; + + $this->assertContains($originalCN, $newEntry->getAttribute('cn')); + + $entryManager->rename($newEntry, 'cn='.$originalCN); + + $this->executeSearchQuery(1); + } +} diff --git a/vendor/symfony/ldap/Tests/Fixtures/conf/slapd.conf b/vendor/symfony/ldap/Tests/Fixtures/conf/slapd.conf new file mode 100644 index 0000000..35f7fe5 --- /dev/null +++ b/vendor/symfony/ldap/Tests/Fixtures/conf/slapd.conf @@ -0,0 +1,17 @@ +# See slapd.conf(5) for details on configuration options. +include /etc/ldap/schema/core.schema +include /etc/ldap/schema/cosine.schema +include /etc/ldap/schema/inetorgperson.schema +include /etc/ldap/schema/nis.schema + +pidfile /tmp/slapd/slapd.pid +argsfile /tmp/slapd/slapd.args + +modulepath /usr/lib/openldap + +database ldif +directory /tmp/slapd + +suffix "dc=symfony,dc=com" +rootdn "cn=admin,dc=symfony,dc=com" +rootpw {SSHA}btWUi971ytYpVMbZLkaQ2A6ETh3VA0lL diff --git a/vendor/symfony/ldap/Tests/Fixtures/data/base.ldif b/vendor/symfony/ldap/Tests/Fixtures/data/base.ldif new file mode 100644 index 0000000..25abb29 --- /dev/null +++ b/vendor/symfony/ldap/Tests/Fixtures/data/base.ldif @@ -0,0 +1,4 @@ +dn: dc=symfony,dc=com +objectClass: dcObject +objectClass: organizationalUnit +ou: Organization diff --git a/vendor/symfony/ldap/Tests/Fixtures/data/fixtures.ldif b/vendor/symfony/ldap/Tests/Fixtures/data/fixtures.ldif new file mode 100644 index 0000000..4384214 --- /dev/null +++ b/vendor/symfony/ldap/Tests/Fixtures/data/fixtures.ldif @@ -0,0 +1,26 @@ +dn: cn=Fabien Potencier,dc=symfony,dc=com +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Fabien Potencier +sn: fabpot +mail: fabpot@symfony.com +mail: fabien@potencier.com +ou: People +ou: Maintainers +ou: Founder +givenName: Fabien Potencier +description: Founder and project lead @Symfony + +dn: ou=Components,dc=symfony,dc=com +objectclass: organizationalunit +ou: Components + +dn: ou=Ldap,ou=Components,dc=symfony,dc=com +objectclass: organizationalunit +ou: Ldap + +dn: ou=Ldap scoping,ou=Ldap,ou=Components,dc=symfony,dc=com +objectclass: organizationalunit +ou: Ldap scoping diff --git a/vendor/symfony/ldap/Tests/LdapClientTest.php b/vendor/symfony/ldap/Tests/LdapClientTest.php new file mode 100644 index 0000000..176c8f1 --- /dev/null +++ b/vendor/symfony/ldap/Tests/LdapClientTest.php @@ -0,0 +1,229 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Tests; + +use Symfony\Component\Ldap\Adapter\CollectionInterface; +use Symfony\Component\Ldap\Adapter\QueryInterface; +use Symfony\Component\Ldap\Entry; +use Symfony\Component\Ldap\LdapClient; +use Symfony\Component\Ldap\LdapInterface; + +/** + * @group legacy + */ +class LdapClientTest extends LdapTestCase +{ + /** @var LdapClient */ + private $client; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $ldap; + + protected function setUp() + { + $this->ldap = $this->getMockBuilder(LdapInterface::class)->getMock(); + + $this->client = new LdapClient(null, 389, 3, false, false, false, $this->ldap); + } + + public function testLdapBind() + { + $this->ldap + ->expects($this->once()) + ->method('bind') + ->with('foo', 'bar') + ; + $this->client->bind('foo', 'bar'); + } + + public function testLdapEscape() + { + $this->ldap + ->expects($this->once()) + ->method('escape') + ->with('foo', 'bar', 'baz') + ; + $this->client->escape('foo', 'bar', 'baz'); + } + + public function testLdapQuery() + { + $this->ldap + ->expects($this->once()) + ->method('query') + ->with('foo', 'bar', array('baz')) + ; + $this->client->query('foo', 'bar', array('baz')); + } + + public function testLdapFind() + { + $collection = $this->getMockBuilder(CollectionInterface::class)->getMock(); + $collection + ->expects($this->once()) + ->method('getIterator') + ->will($this->returnValue(new \ArrayIterator(array( + new Entry('cn=qux,dc=foo,dc=com', array( + 'cn' => array('qux'), + 'dc' => array('com', 'foo'), + 'givenName' => array('Qux'), + )), + new Entry('cn=baz,dc=foo,dc=com', array( + 'cn' => array('baz'), + 'dc' => array('com', 'foo'), + 'givenName' => array('Baz'), + )), + )))) + ; + $query = $this->getMockBuilder(QueryInterface::class)->getMock(); + $query + ->expects($this->once()) + ->method('execute') + ->will($this->returnValue($collection)) + ; + $this->ldap + ->expects($this->once()) + ->method('query') + ->with('dc=foo,dc=com', 'bar', array('filter' => 'baz')) + ->willReturn($query) + ; + + $expected = array( + 'count' => 2, + 0 => array( + 'count' => 3, + 0 => 'cn', + 'cn' => array( + 'count' => 1, + 0 => 'qux', + ), + 1 => 'dc', + 'dc' => array( + 'count' => 2, + 0 => 'com', + 1 => 'foo', + ), + 2 => 'givenname', + 'givenname' => array( + 'count' => 1, + 0 => 'Qux', + ), + 'dn' => 'cn=qux,dc=foo,dc=com', + ), + 1 => array( + 'count' => 3, + 0 => 'cn', + 'cn' => array( + 'count' => 1, + 0 => 'baz', + ), + 1 => 'dc', + 'dc' => array( + 'count' => 2, + 0 => 'com', + 1 => 'foo', + ), + 2 => 'givenname', + 'givenname' => array( + 'count' => 1, + 0 => 'Baz', + ), + 'dn' => 'cn=baz,dc=foo,dc=com', + ), + ); + $this->assertEquals($expected, $this->client->find('dc=foo,dc=com', 'bar', 'baz')); + } + + /** + * @dataProvider provideConfig + */ + public function testLdapClientConfig($args, $expected) + { + $reflObj = new \ReflectionObject($this->client); + $reflMethod = $reflObj->getMethod('normalizeConfig'); + $reflMethod->setAccessible(true); + array_unshift($args, $this->client); + $this->assertEquals($expected, call_user_func_array(array($reflMethod, 'invoke'), $args)); + } + + /** + * @group functional + * @requires extension ldap + */ + public function testLdapClientFunctional() + { + $config = $this->getLdapConfig(); + $ldap = new LdapClient($config['host'], $config['port']); + $ldap->bind('cn=admin,dc=symfony,dc=com', 'symfony'); + $result = $ldap->find('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))'); + + $con = @ldap_connect($config['host'], $config['port']); + @ldap_bind($con, 'cn=admin,dc=symfony,dc=com', 'symfony'); + $search = @ldap_search($con, 'dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))', array('*')); + $expected = @ldap_get_entries($con, $search); + + $this->assertSame($expected, $result); + } + + public function provideConfig() + { + return array( + array( + array('localhost', 389, 3, true, false, false), + array( + 'host' => 'localhost', + 'port' => 389, + 'encryption' => 'ssl', + 'options' => array( + 'protocol_version' => 3, + 'referrals' => false, + ), + ), + ), + array( + array('localhost', 389, 3, false, true, false), + array( + 'host' => 'localhost', + 'port' => 389, + 'encryption' => 'tls', + 'options' => array( + 'protocol_version' => 3, + 'referrals' => false, + ), + ), + ), + array( + array('localhost', 389, 3, false, false, false), + array( + 'host' => 'localhost', + 'port' => 389, + 'encryption' => 'none', + 'options' => array( + 'protocol_version' => 3, + 'referrals' => false, + ), + ), + ), + array( + array('localhost', 389, 3, false, false, false), + array( + 'host' => 'localhost', + 'port' => 389, + 'encryption' => 'none', + 'options' => array( + 'protocol_version' => 3, + 'referrals' => false, + ), + ), + ), + ); + } +} diff --git a/vendor/symfony/ldap/Tests/LdapTest.php b/vendor/symfony/ldap/Tests/LdapTest.php new file mode 100644 index 0000000..1718432 --- /dev/null +++ b/vendor/symfony/ldap/Tests/LdapTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Ldap\Adapter\AdapterInterface; +use Symfony\Component\Ldap\Adapter\ConnectionInterface; +use Symfony\Component\Ldap\Exception\DriverNotFoundException; +use Symfony\Component\Ldap\Ldap; + +class LdapTest extends TestCase +{ + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $adapter; + + /** @var Ldap */ + private $ldap; + + protected function setUp() + { + $this->adapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); + $this->ldap = new Ldap($this->adapter); + } + + public function testLdapBind() + { + $connection = $this->getMockBuilder(ConnectionInterface::class)->getMock(); + $connection + ->expects($this->once()) + ->method('bind') + ->with('foo', 'bar') + ; + $this->adapter + ->expects($this->once()) + ->method('getConnection') + ->will($this->returnValue($connection)) + ; + $this->ldap->bind('foo', 'bar'); + } + + public function testLdapEscape() + { + $this->adapter + ->expects($this->once()) + ->method('escape') + ->with('foo', 'bar', 'baz') + ; + $this->ldap->escape('foo', 'bar', 'baz'); + } + + public function testLdapQuery() + { + $this->adapter + ->expects($this->once()) + ->method('createQuery') + ->with('foo', 'bar', array('baz')) + ; + $this->ldap->query('foo', 'bar', array('baz')); + } + + /** + * @requires extension ldap + */ + public function testLdapCreate() + { + $ldap = Ldap::create('ext_ldap'); + $this->assertInstanceOf(Ldap::class, $ldap); + } + + public function testCreateWithInvalidAdapterName() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(DriverNotFoundException::class); + Ldap::create('foo'); + } +} diff --git a/vendor/symfony/ldap/Tests/LdapTestCase.php b/vendor/symfony/ldap/Tests/LdapTestCase.php new file mode 100644 index 0000000..33afd6d --- /dev/null +++ b/vendor/symfony/ldap/Tests/LdapTestCase.php @@ -0,0 +1,16 @@ + getenv('LDAP_HOST'), + 'port' => getenv('LDAP_PORT'), + ); + } +} diff --git a/vendor/symfony/ldap/composer.json b/vendor/symfony/ldap/composer.json new file mode 100644 index 0000000..6e4e266 --- /dev/null +++ b/vendor/symfony/ldap/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/ldap", + "type": "library", + "description": "An abstraction in front of PHP's LDAP functions, compatible with PHP 5.3.9 onwards.", + "keywords": ["ldap", "active directory"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Charles Sarrazin", + "email": "charles@sarraz.in" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-php56": "~1.0", + "symfony/options-resolver": "~2.8|~3.0|~4.0", + "ext-ldap": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Ldap\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + } +} diff --git a/vendor/symfony/ldap/phpunit.xml.dist b/vendor/symfony/ldap/phpunit.xml.dist new file mode 100644 index 0000000..fabf90c --- /dev/null +++ b/vendor/symfony/ldap/phpunit.xml.dist @@ -0,0 +1,32 @@ + + + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/options-resolver/.gitignore b/vendor/symfony/options-resolver/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/options-resolver/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/options-resolver/CHANGELOG.md b/vendor/symfony/options-resolver/CHANGELOG.md new file mode 100644 index 0000000..6e9d49f --- /dev/null +++ b/vendor/symfony/options-resolver/CHANGELOG.md @@ -0,0 +1,52 @@ +CHANGELOG +========= + +3.4.0 +----- + + * added `OptionsResolverIntrospector` to inspect options definitions inside an `OptionsResolver` instance + * added array of types support in allowed types (e.g int[]) + +2.6.0 +----- + + * deprecated OptionsResolverInterface + * [BC BREAK] removed "array" type hint from OptionsResolverInterface methods + setRequired(), setAllowedValues(), addAllowedValues(), setAllowedTypes() and + addAllowedTypes() + * added OptionsResolver::setDefault() + * added OptionsResolver::hasDefault() + * added OptionsResolver::setNormalizer() + * added OptionsResolver::isRequired() + * added OptionsResolver::getRequiredOptions() + * added OptionsResolver::isMissing() + * added OptionsResolver::getMissingOptions() + * added OptionsResolver::setDefined() + * added OptionsResolver::isDefined() + * added OptionsResolver::getDefinedOptions() + * added OptionsResolver::remove() + * added OptionsResolver::clear() + * deprecated OptionsResolver::replaceDefaults() + * deprecated OptionsResolver::setOptional() in favor of setDefined() + * deprecated OptionsResolver::isKnown() in favor of isDefined() + * [BC BREAK] OptionsResolver::isRequired() returns true now if a required + option has a default value set + * [BC BREAK] merged Options into OptionsResolver and turned Options into an + interface + * deprecated Options::overload() (now in OptionsResolver) + * deprecated Options::set() (now in OptionsResolver) + * deprecated Options::get() (now in OptionsResolver) + * deprecated Options::has() (now in OptionsResolver) + * deprecated Options::replace() (now in OptionsResolver) + * [BC BREAK] Options::get() (now in OptionsResolver) can only be used within + lazy option/normalizer closures now + * [BC BREAK] removed Traversable interface from Options since using within + lazy option/normalizer closures resulted in exceptions + * [BC BREAK] removed Options::all() since using within lazy option/normalizer + closures resulted in exceptions + * [BC BREAK] OptionDefinitionException now extends LogicException instead of + RuntimeException + * [BC BREAK] normalizers are not executed anymore for unset options + * normalizers are executed after validating the options now + * [BC BREAK] an UndefinedOptionsException is now thrown instead of an + InvalidOptionsException when non-existing options are passed diff --git a/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php b/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php new file mode 100644 index 0000000..6031724 --- /dev/null +++ b/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Debug; + +use Symfony\Component\OptionsResolver\Exception\NoConfigurationException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Maxime Steinhausser + * + * @final + */ +class OptionsResolverIntrospector +{ + private $get; + + public function __construct(OptionsResolver $optionsResolver) + { + $this->get = \Closure::bind(function ($property, $option, $message) { + /** @var OptionsResolver $this */ + if (!$this->isDefined($option)) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist.', $option)); + } + + if (!array_key_exists($option, $this->{$property})) { + throw new NoConfigurationException($message); + } + + return $this->{$property}[$option]; + }, $optionsResolver, $optionsResolver); + } + + /** + * @param string $option + * + * @return mixed + * + * @throws NoConfigurationException on no configured value + */ + public function getDefault($option) + { + return call_user_func($this->get, 'defaults', $option, sprintf('No default value was set for the "%s" option.', $option)); + } + + /** + * @param string $option + * + * @return \Closure[] + * + * @throws NoConfigurationException on no configured closures + */ + public function getLazyClosures($option) + { + return call_user_func($this->get, 'lazy', $option, sprintf('No lazy closures were set for the "%s" option.', $option)); + } + + /** + * @param string $option + * + * @return string[] + * + * @throws NoConfigurationException on no configured types + */ + public function getAllowedTypes($option) + { + return call_user_func($this->get, 'allowedTypes', $option, sprintf('No allowed types were set for the "%s" option.', $option)); + } + + /** + * @param string $option + * + * @return mixed[] + * + * @throws NoConfigurationException on no configured values + */ + public function getAllowedValues($option) + { + return call_user_func($this->get, 'allowedValues', $option, sprintf('No allowed values were set for the "%s" option.', $option)); + } + + /** + * @param string $option + * + * @return \Closure + * + * @throws NoConfigurationException on no configured normalizer + */ + public function getNormalizer($option) + { + return call_user_func($this->get, 'normalizers', $option, sprintf('No normalizer was set for the "%s" option.', $option)); + } +} diff --git a/vendor/symfony/options-resolver/Exception/AccessException.php b/vendor/symfony/options-resolver/Exception/AccessException.php new file mode 100644 index 0000000..c12b680 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/AccessException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when trying to read an option outside of or write it inside of + * {@link \Symfony\Component\OptionsResolver\Options::resolve()}. + * + * @author Bernhard Schussek + */ +class AccessException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/ExceptionInterface.php b/vendor/symfony/options-resolver/Exception/ExceptionInterface.php new file mode 100644 index 0000000..b62bb51 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Marker interface for all exceptions thrown by the OptionsResolver component. + * + * @author Bernhard Schussek + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php b/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..6d421d6 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/InvalidArgumentException.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\OptionsResolver\Exception; + +/** + * Thrown when an argument is invalid. + * + * @author Bernhard Schussek + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php b/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php new file mode 100644 index 0000000..6fd4f12 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/InvalidOptionsException.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\OptionsResolver\Exception; + +/** + * Thrown when the value of an option does not match its validation rules. + * + * You should make sure a valid value is passed to the option. + * + * @author Bernhard Schussek + */ +class InvalidOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/Exception/MissingOptionsException.php b/vendor/symfony/options-resolver/Exception/MissingOptionsException.php new file mode 100644 index 0000000..faa487f --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/MissingOptionsException.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\OptionsResolver\Exception; + +/** + * Exception thrown when a required option is missing. + * + * Add the option to the passed options array. + * + * @author Bernhard Schussek + */ +class MissingOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/Exception/NoConfigurationException.php b/vendor/symfony/options-resolver/Exception/NoConfigurationException.php new file mode 100644 index 0000000..6693ec1 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/NoConfigurationException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +use Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector; + +/** + * Thrown when trying to introspect an option definition property + * for which no value was configured inside the OptionsResolver instance. + * + * @see OptionsResolverIntrospector + * + * @author Maxime Steinhausser + */ +class NoConfigurationException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php b/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php new file mode 100644 index 0000000..4c3280f --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when trying to read an option that has no value set. + * + * When accessing optional options from within a lazy option or normalizer you should first + * check whether the optional option is set. You can do this with `isset($options['optional'])`. + * In contrast to the {@link UndefinedOptionsException}, this is a runtime exception that can + * occur when evaluating lazy options. + * + * @author Tobias Schultze + */ +class NoSuchOptionException extends \OutOfBoundsException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php b/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php new file mode 100644 index 0000000..e8e339d --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/OptionDefinitionException.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\OptionsResolver\Exception; + +/** + * Thrown when two lazy options have a cyclic dependency. + * + * @author Bernhard Schussek + */ +class OptionDefinitionException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php b/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php new file mode 100644 index 0000000..6ca3fce --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Exception thrown when an undefined option is passed. + * + * You should remove the options in question from your code or define them + * beforehand. + * + * @author Bernhard Schussek + */ +class UndefinedOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/LICENSE b/vendor/symfony/options-resolver/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/options-resolver/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/options-resolver/Options.php b/vendor/symfony/options-resolver/Options.php new file mode 100644 index 0000000..d444ec4 --- /dev/null +++ b/vendor/symfony/options-resolver/Options.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver; + +/** + * Contains resolved option values. + * + * @author Bernhard Schussek + * @author Tobias Schultze + */ +interface Options extends \ArrayAccess, \Countable +{ +} diff --git a/vendor/symfony/options-resolver/OptionsResolver.php b/vendor/symfony/options-resolver/OptionsResolver.php new file mode 100644 index 0000000..95a492d --- /dev/null +++ b/vendor/symfony/options-resolver/OptionsResolver.php @@ -0,0 +1,1081 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver; + +use Symfony\Component\OptionsResolver\Exception\AccessException; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; +use Symfony\Component\OptionsResolver\Exception\NoSuchOptionException; +use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; + +/** + * Validates options and merges them with default values. + * + * @author Bernhard Schussek + * @author Tobias Schultze + */ +class OptionsResolver implements Options +{ + /** + * The names of all defined options. + */ + private $defined = array(); + + /** + * The default option values. + */ + private $defaults = array(); + + /** + * The names of required options. + */ + private $required = array(); + + /** + * The resolved option values. + */ + private $resolved = array(); + + /** + * A list of normalizer closures. + * + * @var \Closure[] + */ + private $normalizers = array(); + + /** + * A list of accepted values for each option. + */ + private $allowedValues = array(); + + /** + * A list of accepted types for each option. + */ + private $allowedTypes = array(); + + /** + * A list of closures for evaluating lazy options. + */ + private $lazy = array(); + + /** + * A list of lazy options whose closure is currently being called. + * + * This list helps detecting circular dependencies between lazy options. + */ + private $calling = array(); + + /** + * Whether the instance is locked for reading. + * + * Once locked, the options cannot be changed anymore. This is + * necessary in order to avoid inconsistencies during the resolving + * process. If any option is changed after being read, all evaluated + * lazy options that depend on this option would become invalid. + */ + private $locked = false; + + private static $typeAliases = array( + 'boolean' => 'bool', + 'integer' => 'int', + 'double' => 'float', + ); + + /** + * Sets the default value of a given option. + * + * If the default value should be set based on other options, you can pass + * a closure with the following signature: + * + * function (Options $options) { + * // ... + * } + * + * The closure will be evaluated when {@link resolve()} is called. The + * closure has access to the resolved values of other options through the + * passed {@link Options} instance: + * + * function (Options $options) { + * if (isset($options['port'])) { + * // ... + * } + * } + * + * If you want to access the previously set default value, add a second + * argument to the closure's signature: + * + * $options->setDefault('name', 'Default Name'); + * + * $options->setDefault('name', function (Options $options, $previousValue) { + * // 'Default Name' === $previousValue + * }); + * + * This is mostly useful if the configuration of the {@link Options} object + * is spread across different locations of your code, such as base and + * sub-classes. + * + * @param string $option The name of the option + * @param mixed $value The default value of the option + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefault($option, $value) + { + // Setting is not possible once resolving starts, because then lazy + // options could manipulate the state of the object, leading to + // inconsistent results. + if ($this->locked) { + throw new AccessException('Default values cannot be set from a lazy option or normalizer.'); + } + + // If an option is a closure that should be evaluated lazily, store it + // in the "lazy" property. + if ($value instanceof \Closure) { + $reflClosure = new \ReflectionFunction($value); + $params = $reflClosure->getParameters(); + + if (isset($params[0]) && null !== ($class = $params[0]->getClass()) && Options::class === $class->name) { + // Initialize the option if no previous value exists + if (!isset($this->defaults[$option])) { + $this->defaults[$option] = null; + } + + // Ignore previous lazy options if the closure has no second parameter + if (!isset($this->lazy[$option]) || !isset($params[1])) { + $this->lazy[$option] = array(); + } + + // Store closure for later evaluation + $this->lazy[$option][] = $value; + $this->defined[$option] = true; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + } + + // This option is not lazy anymore + unset($this->lazy[$option]); + + // Yet undefined options can be marked as resolved, because we only need + // to resolve options with lazy closures, normalizers or validation + // rules, none of which can exist for undefined options + // If the option was resolved before, update the resolved value + if (!isset($this->defined[$option]) || array_key_exists($option, $this->resolved)) { + $this->resolved[$option] = $value; + } + + $this->defaults[$option] = $value; + $this->defined[$option] = true; + + return $this; + } + + /** + * Sets a list of default values. + * + * @param array $defaults The default values to set + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefaults(array $defaults) + { + foreach ($defaults as $option => $value) { + $this->setDefault($option, $value); + } + + return $this; + } + + /** + * Returns whether a default value is set for an option. + * + * Returns true if {@link setDefault()} was called for this option. + * An option is also considered set if it was set to null. + * + * @param string $option The option name + * + * @return bool Whether a default value is set + */ + public function hasDefault($option) + { + return array_key_exists($option, $this->defaults); + } + + /** + * Marks one or more options as required. + * + * @param string|string[] $optionNames One or more option names + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setRequired($optionNames) + { + if ($this->locked) { + throw new AccessException('Options cannot be made required from a lazy option or normalizer.'); + } + + foreach ((array) $optionNames as $option) { + $this->defined[$option] = true; + $this->required[$option] = true; + } + + return $this; + } + + /** + * Returns whether an option is required. + * + * An option is required if it was passed to {@link setRequired()}. + * + * @param string $option The name of the option + * + * @return bool Whether the option is required + */ + public function isRequired($option) + { + return isset($this->required[$option]); + } + + /** + * Returns the names of all required options. + * + * @return string[] The names of the required options + * + * @see isRequired() + */ + public function getRequiredOptions() + { + return array_keys($this->required); + } + + /** + * Returns whether an option is missing a default value. + * + * An option is missing if it was passed to {@link setRequired()}, but not + * to {@link setDefault()}. This option must be passed explicitly to + * {@link resolve()}, otherwise an exception will be thrown. + * + * @param string $option The name of the option + * + * @return bool Whether the option is missing + */ + public function isMissing($option) + { + return isset($this->required[$option]) && !array_key_exists($option, $this->defaults); + } + + /** + * Returns the names of all options missing a default value. + * + * @return string[] The names of the missing options + * + * @see isMissing() + */ + public function getMissingOptions() + { + return array_keys(array_diff_key($this->required, $this->defaults)); + } + + /** + * Defines a valid option name. + * + * Defines an option name without setting a default value. The option will + * be accepted when passed to {@link resolve()}. When not passed, the + * option will not be included in the resolved options. + * + * @param string|string[] $optionNames One or more option names + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefined($optionNames) + { + if ($this->locked) { + throw new AccessException('Options cannot be defined from a lazy option or normalizer.'); + } + + foreach ((array) $optionNames as $option) { + $this->defined[$option] = true; + } + + return $this; + } + + /** + * Returns whether an option is defined. + * + * Returns true for any option passed to {@link setDefault()}, + * {@link setRequired()} or {@link setDefined()}. + * + * @param string $option The option name + * + * @return bool Whether the option is defined + */ + public function isDefined($option) + { + return isset($this->defined[$option]); + } + + /** + * Returns the names of all defined options. + * + * @return string[] The names of the defined options + * + * @see isDefined() + */ + public function getDefinedOptions() + { + return array_keys($this->defined); + } + + /** + * Sets the normalizer for an option. + * + * The normalizer should be a closure with the following signature: + * + * ```php + * function (Options $options, $value) { + * // ... + * } + * ``` + * + * The closure is invoked when {@link resolve()} is called. The closure + * has access to the resolved values of other options through the passed + * {@link Options} instance. + * + * The second parameter passed to the closure is the value of + * the option. + * + * The resolved option value is set to the return value of the closure. + * + * @param string $option The option name + * @param \Closure $normalizer The normalizer + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setNormalizer($option, \Closure $normalizer) + { + if ($this->locked) { + throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf( + 'The option "%s" does not exist. Defined options are: "%s".', + $option, + implode('", "', array_keys($this->defined)) + )); + } + + $this->normalizers[$option] = $normalizer; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Sets allowed values for an option. + * + * Instead of passing values, you may also pass a closures with the + * following signature: + * + * function ($value) { + * // return true or false + * } + * + * The closure receives the value as argument and should return true to + * accept the value and false to reject the value. + * + * @param string $option The option name + * @param mixed $allowedValues One or more acceptable values/closures + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setAllowedValues($option, $allowedValues) + { + if ($this->locked) { + throw new AccessException('Allowed values cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf( + 'The option "%s" does not exist. Defined options are: "%s".', + $option, + implode('", "', array_keys($this->defined)) + )); + } + + $this->allowedValues[$option] = is_array($allowedValues) ? $allowedValues : array($allowedValues); + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Adds allowed values for an option. + * + * The values are merged with the allowed values defined previously. + * + * Instead of passing values, you may also pass a closures with the + * following signature: + * + * function ($value) { + * // return true or false + * } + * + * The closure receives the value as argument and should return true to + * accept the value and false to reject the value. + * + * @param string $option The option name + * @param mixed $allowedValues One or more acceptable values/closures + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function addAllowedValues($option, $allowedValues) + { + if ($this->locked) { + throw new AccessException('Allowed values cannot be added from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf( + 'The option "%s" does not exist. Defined options are: "%s".', + $option, + implode('", "', array_keys($this->defined)) + )); + } + + if (!is_array($allowedValues)) { + $allowedValues = array($allowedValues); + } + + if (!isset($this->allowedValues[$option])) { + $this->allowedValues[$option] = $allowedValues; + } else { + $this->allowedValues[$option] = array_merge($this->allowedValues[$option], $allowedValues); + } + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Sets allowed types for an option. + * + * Any type for which a corresponding is_() function exists is + * acceptable. Additionally, fully-qualified class or interface names may + * be passed. + * + * @param string $option The option name + * @param string|string[] $allowedTypes One or more accepted types + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setAllowedTypes($option, $allowedTypes) + { + if ($this->locked) { + throw new AccessException('Allowed types cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf( + 'The option "%s" does not exist. Defined options are: "%s".', + $option, + implode('", "', array_keys($this->defined)) + )); + } + + $this->allowedTypes[$option] = (array) $allowedTypes; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Adds allowed types for an option. + * + * The types are merged with the allowed types defined previously. + * + * Any type for which a corresponding is_() function exists is + * acceptable. Additionally, fully-qualified class or interface names may + * be passed. + * + * @param string $option The option name + * @param string|string[] $allowedTypes One or more accepted types + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function addAllowedTypes($option, $allowedTypes) + { + if ($this->locked) { + throw new AccessException('Allowed types cannot be added from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf( + 'The option "%s" does not exist. Defined options are: "%s".', + $option, + implode('", "', array_keys($this->defined)) + )); + } + + if (!isset($this->allowedTypes[$option])) { + $this->allowedTypes[$option] = (array) $allowedTypes; + } else { + $this->allowedTypes[$option] = array_merge($this->allowedTypes[$option], (array) $allowedTypes); + } + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Removes the option with the given name. + * + * Undefined options are ignored. + * + * @param string|string[] $optionNames One or more option names + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function remove($optionNames) + { + if ($this->locked) { + throw new AccessException('Options cannot be removed from a lazy option or normalizer.'); + } + + foreach ((array) $optionNames as $option) { + unset($this->defined[$option], $this->defaults[$option], $this->required[$option], $this->resolved[$option]); + unset($this->lazy[$option], $this->normalizers[$option], $this->allowedTypes[$option], $this->allowedValues[$option]); + } + + return $this; + } + + /** + * Removes all options. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function clear() + { + if ($this->locked) { + throw new AccessException('Options cannot be cleared from a lazy option or normalizer.'); + } + + $this->defined = array(); + $this->defaults = array(); + $this->required = array(); + $this->resolved = array(); + $this->lazy = array(); + $this->normalizers = array(); + $this->allowedTypes = array(); + $this->allowedValues = array(); + + return $this; + } + + /** + * Merges options with the default values stored in the container and + * validates them. + * + * Exceptions are thrown if: + * + * - Undefined options are passed; + * - Required options are missing; + * - Options have invalid types; + * - Options have invalid values. + * + * @param array $options A map of option names to values + * + * @return array The merged and validated options + * + * @throws UndefinedOptionsException If an option name is undefined + * @throws InvalidOptionsException If an option doesn't fulfill the + * specified validation rules + * @throws MissingOptionsException If a required option is missing + * @throws OptionDefinitionException If there is a cyclic dependency between + * lazy options and/or normalizers + * @throws NoSuchOptionException If a lazy option reads an unavailable option + * @throws AccessException If called from a lazy option or normalizer + */ + public function resolve(array $options = array()) + { + if ($this->locked) { + throw new AccessException('Options cannot be resolved from a lazy option or normalizer.'); + } + + // Allow this method to be called multiple times + $clone = clone $this; + + // Make sure that no unknown options are passed + $diff = array_diff_key($options, $clone->defined); + + if (count($diff) > 0) { + ksort($clone->defined); + ksort($diff); + + throw new UndefinedOptionsException(sprintf( + (count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Defined options are: "%s".', + implode('", "', array_keys($diff)), + implode('", "', array_keys($clone->defined)) + )); + } + + // Override options set by the user + foreach ($options as $option => $value) { + $clone->defaults[$option] = $value; + unset($clone->resolved[$option], $clone->lazy[$option]); + } + + // Check whether any required option is missing + $diff = array_diff_key($clone->required, $clone->defaults); + + if (count($diff) > 0) { + ksort($diff); + + throw new MissingOptionsException(sprintf( + count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', + implode('", "', array_keys($diff)) + )); + } + + // Lock the container + $clone->locked = true; + + // Now process the individual options. Use offsetGet(), which resolves + // the option itself and any options that the option depends on + foreach ($clone->defaults as $option => $_) { + $clone->offsetGet($option); + } + + return $clone->resolved; + } + + /** + * Returns the resolved value of an option. + * + * @param string $option The option name + * + * @return mixed The option value + * + * @throws AccessException If accessing this method outside of + * {@link resolve()} + * @throws NoSuchOptionException If the option is not set + * @throws InvalidOptionsException If the option doesn't fulfill the + * specified validation rules + * @throws OptionDefinitionException If there is a cyclic dependency between + * lazy options and/or normalizers + */ + public function offsetGet($option) + { + if (!$this->locked) { + throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); + } + + // Shortcut for resolved options + if (array_key_exists($option, $this->resolved)) { + return $this->resolved[$option]; + } + + // Check whether the option is set at all + if (!array_key_exists($option, $this->defaults)) { + if (!isset($this->defined[$option])) { + throw new NoSuchOptionException(sprintf( + 'The option "%s" does not exist. Defined options are: "%s".', + $option, + implode('", "', array_keys($this->defined)) + )); + } + + throw new NoSuchOptionException(sprintf( + 'The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.', + $option + )); + } + + $value = $this->defaults[$option]; + + // Resolve the option if the default value is lazily evaluated + if (isset($this->lazy[$option])) { + // If the closure is already being called, we have a cyclic + // dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf( + 'The options "%s" have a cyclic dependency.', + implode('", "', array_keys($this->calling)) + )); + } + + // The following section must be protected from cyclic + // calls. Set $calling for the current $option to detect a cyclic + // dependency + // BEGIN + $this->calling[$option] = true; + try { + foreach ($this->lazy[$option] as $closure) { + $value = $closure($this, $value); + } + } finally { + unset($this->calling[$option]); + } + // END + } + + // Validate the type of the resolved option + if (isset($this->allowedTypes[$option])) { + $valid = false; + $invalidTypes = array(); + + foreach ($this->allowedTypes[$option] as $type) { + $type = isset(self::$typeAliases[$type]) ? self::$typeAliases[$type] : $type; + + if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) { + break; + } + } + + if (!$valid) { + throw new InvalidOptionsException(sprintf( + 'The option "%s" with value %s is expected to be of type '. + '"%s", but is of type "%s".', + $option, + $this->formatValue($value), + implode('" or "', $this->allowedTypes[$option]), + implode('|', array_keys($invalidTypes)) + )); + } + } + + // Validate the value of the resolved option + if (isset($this->allowedValues[$option])) { + $success = false; + $printableAllowedValues = array(); + + foreach ($this->allowedValues[$option] as $allowedValue) { + if ($allowedValue instanceof \Closure) { + if ($allowedValue($value)) { + $success = true; + break; + } + + // Don't include closures in the exception message + continue; + } elseif ($value === $allowedValue) { + $success = true; + break; + } + + $printableAllowedValues[] = $allowedValue; + } + + if (!$success) { + $message = sprintf( + 'The option "%s" with value %s is invalid.', + $option, + $this->formatValue($value) + ); + + if (count($printableAllowedValues) > 0) { + $message .= sprintf( + ' Accepted values are: %s.', + $this->formatValues($printableAllowedValues) + ); + } + + throw new InvalidOptionsException($message); + } + } + + // Normalize the validated option + if (isset($this->normalizers[$option])) { + // If the closure is already being called, we have a cyclic + // dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf( + 'The options "%s" have a cyclic dependency.', + implode('", "', array_keys($this->calling)) + )); + } + + $normalizer = $this->normalizers[$option]; + + // The following section must be protected from cyclic + // calls. Set $calling for the current $option to detect a cyclic + // dependency + // BEGIN + $this->calling[$option] = true; + try { + $value = $normalizer($this, $value); + } finally { + unset($this->calling[$option]); + } + // END + } + + // Mark as resolved + $this->resolved[$option] = $value; + + return $value; + } + + /** + * @param string $type + * @param mixed $value + * @param array &$invalidTypes + * + * @return bool + */ + private function verifyTypes($type, $value, array &$invalidTypes) + { + if ('[]' === substr($type, -2) && is_array($value)) { + $originalType = $type; + $type = substr($type, 0, -2); + $invalidValues = array_filter( // Filter out valid values, keeping invalid values in the resulting array + $value, + function ($value) use ($type) { + return !self::isValueValidType($type, $value); + } + ); + + if (!$invalidValues) { + return true; + } + + $invalidTypes[$this->formatTypeOf($value, $originalType)] = true; + + return false; + } + + if (self::isValueValidType($type, $value)) { + return true; + } + + if (!$invalidTypes) { + $invalidTypes[$this->formatTypeOf($value, null)] = true; + } + + return false; + } + + /** + * Returns whether a resolved option with the given name exists. + * + * @param string $option The option name + * + * @return bool Whether the option is set + * + * @throws AccessException If accessing this method outside of {@link resolve()} + * + * @see \ArrayAccess::offsetExists() + */ + public function offsetExists($option) + { + if (!$this->locked) { + throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); + } + + return array_key_exists($option, $this->defaults); + } + + /** + * Not supported. + * + * @throws AccessException + */ + public function offsetSet($option, $value) + { + throw new AccessException('Setting options via array access is not supported. Use setDefault() instead.'); + } + + /** + * Not supported. + * + * @throws AccessException + */ + public function offsetUnset($option) + { + throw new AccessException('Removing options via array access is not supported. Use remove() instead.'); + } + + /** + * Returns the number of set options. + * + * This may be only a subset of the defined options. + * + * @return int Number of options + * + * @throws AccessException If accessing this method outside of {@link resolve()} + * + * @see \Countable::count() + */ + public function count() + { + if (!$this->locked) { + throw new AccessException('Counting is only supported within closures of lazy options and normalizers.'); + } + + return count($this->defaults); + } + + /** + * Returns a string representation of the type of the value. + * + * This method should be used if you pass the type of a value as + * message parameter to a constraint violation. Note that such + * parameters should usually not be included in messages aimed at + * non-technical people. + * + * @param mixed $value The value to return the type of + * @param string $type + * + * @return string The type of the value + */ + private function formatTypeOf($value, $type) + { + $suffix = ''; + + if ('[]' === substr($type, -2)) { + $suffix = '[]'; + $type = substr($type, 0, -2); + while ('[]' === substr($type, -2)) { + $type = substr($type, 0, -2); + $value = array_shift($value); + if (!is_array($value)) { + break; + } + $suffix .= '[]'; + } + + if (is_array($value)) { + $subTypes = array(); + foreach ($value as $val) { + $subTypes[$this->formatTypeOf($val, null)] = true; + } + + return implode('|', array_keys($subTypes)).$suffix; + } + } + + return (is_object($value) ? get_class($value) : gettype($value)).$suffix; + } + + /** + * Returns a string representation of the value. + * + * This method returns the equivalent PHP tokens for most scalar types + * (i.e. "false" for false, "1" for 1 etc.). Strings are always wrapped + * in double quotes ("). + * + * @param mixed $value The value to format as string + * + * @return string The string representation of the passed value + */ + private function formatValue($value) + { + if (is_object($value)) { + return get_class($value); + } + + if (is_array($value)) { + return 'array'; + } + + if (is_string($value)) { + return '"'.$value.'"'; + } + + if (is_resource($value)) { + return 'resource'; + } + + if (null === $value) { + return 'null'; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + return (string) $value; + } + + /** + * Returns a string representation of a list of values. + * + * Each of the values is converted to a string using + * {@link formatValue()}. The values are then concatenated with commas. + * + * @param array $values A list of values + * + * @return string The string representation of the value list + * + * @see formatValue() + */ + private function formatValues(array $values) + { + foreach ($values as $key => $value) { + $values[$key] = $this->formatValue($value); + } + + return implode(', ', $values); + } + + private static function isValueValidType($type, $value) + { + return (function_exists($isFunction = 'is_'.$type) && $isFunction($value)) || $value instanceof $type; + } +} diff --git a/vendor/symfony/options-resolver/README.md b/vendor/symfony/options-resolver/README.md new file mode 100644 index 0000000..245e69b --- /dev/null +++ b/vendor/symfony/options-resolver/README.md @@ -0,0 +1,15 @@ +OptionsResolver Component +========================= + +The OptionsResolver component is `array_replace` on steroids. It allows you to +create an options system with required options, defaults, validation (type, +value), normalization and more. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/options_resolver.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/options-resolver/Tests/Debug/OptionsResolverIntrospectorTest.php b/vendor/symfony/options-resolver/Tests/Debug/OptionsResolverIntrospectorTest.php new file mode 100644 index 0000000..7c4753a --- /dev/null +++ b/vendor/symfony/options-resolver/Tests/Debug/OptionsResolverIntrospectorTest.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\OptionsResolver\Tests\Debug; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class OptionsResolverIntrospectorTest extends TestCase +{ + public function testGetDefault() + { + $resolver = new OptionsResolver(); + $resolver->setDefault($option = 'foo', 'bar'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getDefault($option)); + } + + public function testGetDefaultNull() + { + $resolver = new OptionsResolver(); + $resolver->setDefault($option = 'foo', null); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertNull($debug->getDefault($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException + * @expectedExceptionMessage No default value was set for the "foo" option. + */ + public function testGetDefaultThrowsOnNoConfiguredValue() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getDefault($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. + */ + public function testGetDefaultThrowsOnNotDefinedOption() + { + $resolver = new OptionsResolver(); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getDefault('foo')); + } + + public function testGetLazyClosures() + { + $resolver = new OptionsResolver(); + $closures = array(); + $resolver->setDefault($option = 'foo', $closures[] = function (Options $options) {}); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame($closures, $debug->getLazyClosures($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException + * @expectedExceptionMessage No lazy closures were set for the "foo" option. + */ + public function testGetLazyClosuresThrowsOnNoConfiguredValue() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getLazyClosures($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. + */ + public function testGetLazyClosuresThrowsOnNotDefinedOption() + { + $resolver = new OptionsResolver(); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getLazyClosures('foo')); + } + + public function testGetAllowedTypes() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + $resolver->setAllowedTypes($option = 'foo', $allowedTypes = array('string', 'bool')); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame($allowedTypes, $debug->getAllowedTypes($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException + * @expectedExceptionMessage No allowed types were set for the "foo" option. + */ + public function testGetAllowedTypesThrowsOnNoConfiguredValue() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getAllowedTypes($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. + */ + public function testGetAllowedTypesThrowsOnNotDefinedOption() + { + $resolver = new OptionsResolver(); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getAllowedTypes('foo')); + } + + public function testGetAllowedValues() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + $resolver->setAllowedValues($option = 'foo', $allowedValues = array('bar', 'baz')); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame($allowedValues, $debug->getAllowedValues($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException + * @expectedExceptionMessage No allowed values were set for the "foo" option. + */ + public function testGetAllowedValuesThrowsOnNoConfiguredValue() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getAllowedValues($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. + */ + public function testGetAllowedValuesThrowsOnNotDefinedOption() + { + $resolver = new OptionsResolver(); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getAllowedValues('foo')); + } + + public function testGetNormalizer() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + $resolver->setNormalizer($option = 'foo', $normalizer = function () {}); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame($normalizer, $debug->getNormalizer($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException + * @expectedExceptionMessage No normalizer was set for the "foo" option. + */ + public function testGetNormalizerThrowsOnNoConfiguredValue() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getNormalizer($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. + */ + public function testGetNormalizerThrowsOnNotDefinedOption() + { + $resolver = new OptionsResolver(); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getNormalizer('foo')); + } +} diff --git a/vendor/symfony/options-resolver/Tests/OptionsResolverTest.php b/vendor/symfony/options-resolver/Tests/OptionsResolverTest.php new file mode 100644 index 0000000..440af8b --- /dev/null +++ b/vendor/symfony/options-resolver/Tests/OptionsResolverTest.php @@ -0,0 +1,1653 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Tests; + +use PHPUnit\Framework\Assert; +use PHPUnit\Framework\TestCase; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class OptionsResolverTest extends TestCase +{ + /** + * @var OptionsResolver + */ + private $resolver; + + protected function setUp() + { + $this->resolver = new OptionsResolver(); + } + + //////////////////////////////////////////////////////////////////////////// + // resolve() + //////////////////////////////////////////////////////////////////////////// + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. Defined options are: "a", "z". + */ + public function testResolveFailsIfNonExistingOption() + { + $this->resolver->setDefault('z', '1'); + $this->resolver->setDefault('a', '2'); + + $this->resolver->resolve(array('foo' => 'bar')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The options "baz", "foo", "ping" do not exist. Defined options are: "a", "z". + */ + public function testResolveFailsIfMultipleNonExistingOptions() + { + $this->resolver->setDefault('z', '1'); + $this->resolver->setDefault('a', '2'); + + $this->resolver->resolve(array('ping' => 'pong', 'foo' => 'bar', 'baz' => 'bam')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testResolveFailsFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->resolve(array()); + }); + + $this->resolver->resolve(); + } + + //////////////////////////////////////////////////////////////////////////// + // setDefault()/hasDefault() + //////////////////////////////////////////////////////////////////////////// + + public function testSetDefaultReturnsThis() + { + $this->assertSame($this->resolver, $this->resolver->setDefault('foo', 'bar')); + } + + public function testSetDefault() + { + $this->resolver->setDefault('one', '1'); + $this->resolver->setDefault('two', '20'); + + $this->assertEquals(array( + 'one' => '1', + 'two' => '20', + ), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetDefaultFromLazyOption() + { + $this->resolver->setDefault('lazy', function (Options $options) { + $options->setDefault('default', 42); + }); + + $this->resolver->resolve(); + } + + public function testHasDefault() + { + $this->assertFalse($this->resolver->hasDefault('foo')); + $this->resolver->setDefault('foo', 42); + $this->assertTrue($this->resolver->hasDefault('foo')); + } + + public function testHasDefaultWithNullValue() + { + $this->assertFalse($this->resolver->hasDefault('foo')); + $this->resolver->setDefault('foo', null); + $this->assertTrue($this->resolver->hasDefault('foo')); + } + + //////////////////////////////////////////////////////////////////////////// + // lazy setDefault() + //////////////////////////////////////////////////////////////////////////// + + public function testSetLazyReturnsThis() + { + $this->assertSame($this->resolver, $this->resolver->setDefault('foo', function (Options $options) {})); + } + + public function testSetLazyClosure() + { + $this->resolver->setDefault('foo', function (Options $options) { + return 'lazy'; + }); + + $this->assertEquals(array('foo' => 'lazy'), $this->resolver->resolve()); + } + + public function testClosureWithoutTypeHintNotInvoked() + { + $closure = function ($options) { + Assert::fail('Should not be called'); + }; + + $this->resolver->setDefault('foo', $closure); + + $this->assertSame(array('foo' => $closure), $this->resolver->resolve()); + } + + public function testClosureWithoutParametersNotInvoked() + { + $closure = function () { + Assert::fail('Should not be called'); + }; + + $this->resolver->setDefault('foo', $closure); + + $this->assertSame(array('foo' => $closure), $this->resolver->resolve()); + } + + public function testAccessPreviousDefaultValue() + { + // defined by superclass + $this->resolver->setDefault('foo', 'bar'); + + // defined by subclass + $this->resolver->setDefault('foo', function (Options $options, $previousValue) { + Assert::assertEquals('bar', $previousValue); + + return 'lazy'; + }); + + $this->assertEquals(array('foo' => 'lazy'), $this->resolver->resolve()); + } + + public function testAccessPreviousLazyDefaultValue() + { + // defined by superclass + $this->resolver->setDefault('foo', function (Options $options) { + return 'bar'; + }); + + // defined by subclass + $this->resolver->setDefault('foo', function (Options $options, $previousValue) { + Assert::assertEquals('bar', $previousValue); + + return 'lazy'; + }); + + $this->assertEquals(array('foo' => 'lazy'), $this->resolver->resolve()); + } + + public function testPreviousValueIsNotEvaluatedIfNoSecondArgument() + { + // defined by superclass + $this->resolver->setDefault('foo', function () { + Assert::fail('Should not be called'); + }); + + // defined by subclass, no $previousValue argument defined! + $this->resolver->setDefault('foo', function (Options $options) { + return 'lazy'; + }); + + $this->assertEquals(array('foo' => 'lazy'), $this->resolver->resolve()); + } + + public function testOverwrittenLazyOptionNotEvaluated() + { + $this->resolver->setDefault('foo', function (Options $options) { + Assert::fail('Should not be called'); + }); + + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testInvokeEachLazyOptionOnlyOnce() + { + $calls = 0; + + $this->resolver->setDefault('lazy1', function (Options $options) use (&$calls) { + Assert::assertSame(1, ++$calls); + + $options['lazy2']; + }); + + $this->resolver->setDefault('lazy2', function (Options $options) use (&$calls) { + Assert::assertSame(2, ++$calls); + }); + + $this->resolver->resolve(); + + $this->assertSame(2, $calls); + } + + //////////////////////////////////////////////////////////////////////////// + // setRequired()/isRequired()/getRequiredOptions() + //////////////////////////////////////////////////////////////////////////// + + public function testSetRequiredReturnsThis() + { + $this->assertSame($this->resolver, $this->resolver->setRequired('foo')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetRequiredFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->setRequired('bar'); + }); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException + */ + public function testResolveFailsIfRequiredOptionMissing() + { + $this->resolver->setRequired('foo'); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfRequiredOptionSet() + { + $this->resolver->setRequired('foo'); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + public function testResolveSucceedsIfRequiredOptionPassed() + { + $this->resolver->setRequired('foo'); + + $this->assertNotEmpty($this->resolver->resolve(array('foo' => 'bar'))); + } + + public function testIsRequired() + { + $this->assertFalse($this->resolver->isRequired('foo')); + $this->resolver->setRequired('foo'); + $this->assertTrue($this->resolver->isRequired('foo')); + } + + public function testRequiredIfSetBefore() + { + $this->assertFalse($this->resolver->isRequired('foo')); + + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setRequired('foo'); + + $this->assertTrue($this->resolver->isRequired('foo')); + } + + public function testStillRequiredAfterSet() + { + $this->assertFalse($this->resolver->isRequired('foo')); + + $this->resolver->setRequired('foo'); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertTrue($this->resolver->isRequired('foo')); + } + + public function testIsNotRequiredAfterRemove() + { + $this->assertFalse($this->resolver->isRequired('foo')); + $this->resolver->setRequired('foo'); + $this->resolver->remove('foo'); + $this->assertFalse($this->resolver->isRequired('foo')); + } + + public function testIsNotRequiredAfterClear() + { + $this->assertFalse($this->resolver->isRequired('foo')); + $this->resolver->setRequired('foo'); + $this->resolver->clear(); + $this->assertFalse($this->resolver->isRequired('foo')); + } + + public function testGetRequiredOptions() + { + $this->resolver->setRequired(array('foo', 'bar')); + $this->resolver->setDefault('bam', 'baz'); + $this->resolver->setDefault('foo', 'boo'); + + $this->assertSame(array('foo', 'bar'), $this->resolver->getRequiredOptions()); + } + + //////////////////////////////////////////////////////////////////////////// + // isMissing()/getMissingOptions() + //////////////////////////////////////////////////////////////////////////// + + public function testIsMissingIfNotSet() + { + $this->assertFalse($this->resolver->isMissing('foo')); + $this->resolver->setRequired('foo'); + $this->assertTrue($this->resolver->isMissing('foo')); + } + + public function testIsNotMissingIfSet() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->assertFalse($this->resolver->isMissing('foo')); + $this->resolver->setRequired('foo'); + $this->assertFalse($this->resolver->isMissing('foo')); + } + + public function testIsNotMissingAfterRemove() + { + $this->resolver->setRequired('foo'); + $this->resolver->remove('foo'); + $this->assertFalse($this->resolver->isMissing('foo')); + } + + public function testIsNotMissingAfterClear() + { + $this->resolver->setRequired('foo'); + $this->resolver->clear(); + $this->assertFalse($this->resolver->isRequired('foo')); + } + + public function testGetMissingOptions() + { + $this->resolver->setRequired(array('foo', 'bar')); + $this->resolver->setDefault('bam', 'baz'); + $this->resolver->setDefault('foo', 'boo'); + + $this->assertSame(array('bar'), $this->resolver->getMissingOptions()); + } + + //////////////////////////////////////////////////////////////////////////// + // setDefined()/isDefined()/getDefinedOptions() + //////////////////////////////////////////////////////////////////////////// + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetDefinedFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->setDefined('bar'); + }); + + $this->resolver->resolve(); + } + + public function testDefinedOptionsNotIncludedInResolvedOptions() + { + $this->resolver->setDefined('foo'); + + $this->assertSame(array(), $this->resolver->resolve()); + } + + public function testDefinedOptionsIncludedIfDefaultSetBefore() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setDefined('foo'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testDefinedOptionsIncludedIfDefaultSetAfter() + { + $this->resolver->setDefined('foo'); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testDefinedOptionsIncludedIfPassedToResolve() + { + $this->resolver->setDefined('foo'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve(array('foo' => 'bar'))); + } + + public function testIsDefined() + { + $this->assertFalse($this->resolver->isDefined('foo')); + $this->resolver->setDefined('foo'); + $this->assertTrue($this->resolver->isDefined('foo')); + } + + public function testLazyOptionsAreDefined() + { + $this->assertFalse($this->resolver->isDefined('foo')); + $this->resolver->setDefault('foo', function (Options $options) {}); + $this->assertTrue($this->resolver->isDefined('foo')); + } + + public function testRequiredOptionsAreDefined() + { + $this->assertFalse($this->resolver->isDefined('foo')); + $this->resolver->setRequired('foo'); + $this->assertTrue($this->resolver->isDefined('foo')); + } + + public function testSetOptionsAreDefined() + { + $this->assertFalse($this->resolver->isDefined('foo')); + $this->resolver->setDefault('foo', 'bar'); + $this->assertTrue($this->resolver->isDefined('foo')); + } + + public function testGetDefinedOptions() + { + $this->resolver->setDefined(array('foo', 'bar')); + $this->resolver->setDefault('baz', 'bam'); + $this->resolver->setRequired('boo'); + + $this->assertSame(array('foo', 'bar', 'baz', 'boo'), $this->resolver->getDefinedOptions()); + } + + public function testRemovedOptionsAreNotDefined() + { + $this->assertFalse($this->resolver->isDefined('foo')); + $this->resolver->setDefined('foo'); + $this->assertTrue($this->resolver->isDefined('foo')); + $this->resolver->remove('foo'); + $this->assertFalse($this->resolver->isDefined('foo')); + } + + public function testClearedOptionsAreNotDefined() + { + $this->assertFalse($this->resolver->isDefined('foo')); + $this->resolver->setDefined('foo'); + $this->assertTrue($this->resolver->isDefined('foo')); + $this->resolver->clear(); + $this->assertFalse($this->resolver->isDefined('foo')); + } + + //////////////////////////////////////////////////////////////////////////// + // setAllowedTypes() + //////////////////////////////////////////////////////////////////////////// + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + */ + public function testSetAllowedTypesFailsIfUnknownOption() + { + $this->resolver->setAllowedTypes('foo', 'string'); + } + + public function testResolveTypedArray() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'string[]'); + $options = $this->resolver->resolve(array('foo' => array('bar', 'baz'))); + + $this->assertSame(array('foo' => array('bar', 'baz')), $options); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetAllowedTypesFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->setAllowedTypes('bar', 'string'); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but is of type "DateTime[]". + */ + public function testResolveFailsIfInvalidTypedArray() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'int[]'); + + $this->resolver->resolve(array('foo' => array(new \DateTime()))); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value "bar" is expected to be of type "int[]", but is of type "string". + */ + public function testResolveFailsWithNonArray() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'int[]'); + + $this->resolver->resolve(array('foo' => 'bar')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but is of type "integer|stdClass|array|DateTime[]". + */ + public function testResolveFailsIfTypedArrayContainsInvalidTypes() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'int[]'); + $values = range(1, 5); + $values[] = new \stdClass(); + $values[] = array(); + $values[] = new \DateTime(); + $values[] = 123; + + $this->resolver->resolve(array('foo' => $values)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but is of type "double[][]". + */ + public function testResolveFailsWithCorrectLevelsButWrongScalar() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'int[][]'); + + $this->resolver->resolve( + array( + 'foo' => array( + array(1.2), + ), + ) + ); + } + + /** + * @dataProvider provideInvalidTypes + */ + public function testResolveFailsIfInvalidType($actualType, $allowedType, $exceptionMessage) + { + $this->resolver->setDefined('option'); + $this->resolver->setAllowedTypes('option', $allowedType); + + if (method_exists($this, 'expectException')) { + $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); + $this->expectExceptionMessage($exceptionMessage); + } else { + $this->setExpectedException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException', $exceptionMessage); + } + + $this->resolver->resolve(array('option' => $actualType)); + } + + public function provideInvalidTypes() + { + return array( + array(true, 'string', 'The option "option" with value true is expected to be of type "string", but is of type "boolean".'), + array(false, 'string', 'The option "option" with value false is expected to be of type "string", but is of type "boolean".'), + array(fopen(__FILE__, 'r'), 'string', 'The option "option" with value resource is expected to be of type "string", but is of type "resource".'), + array(array(), 'string', 'The option "option" with value array is expected to be of type "string", but is of type "array".'), + array(new OptionsResolver(), 'string', 'The option "option" with value Symfony\Component\OptionsResolver\OptionsResolver is expected to be of type "string", but is of type "Symfony\Component\OptionsResolver\OptionsResolver".'), + array(42, 'string', 'The option "option" with value 42 is expected to be of type "string", but is of type "integer".'), + array(null, 'string', 'The option "option" with value null is expected to be of type "string", but is of type "NULL".'), + array('bar', '\stdClass', 'The option "option" with value "bar" is expected to be of type "\stdClass", but is of type "string".'), + ); + } + + public function testResolveSucceedsIfValidType() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedTypes('foo', 'string'); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value 42 is expected to be of type "string" or "bool", but is of type "integer". + */ + public function testResolveFailsIfInvalidTypeMultiple() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->setAllowedTypes('foo', array('string', 'bool')); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidTypeMultiple() + { + $this->resolver->setDefault('foo', true); + $this->resolver->setAllowedTypes('foo', array('string', 'bool')); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + public function testResolveSucceedsIfInstanceOfClass() + { + $this->resolver->setDefault('foo', new \stdClass()); + $this->resolver->setAllowedTypes('foo', '\stdClass'); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + public function testResolveSucceedsIfTypedArray() + { + $this->resolver->setDefault('foo', null); + $this->resolver->setAllowedTypes('foo', array('null', 'DateTime[]')); + + $data = array( + 'foo' => array( + new \DateTime(), + new \DateTime(), + ), + ); + $result = $this->resolver->resolve($data); + $this->assertEquals($data, $result); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfNotInstanceOfClass() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedTypes('foo', '\stdClass'); + + $this->resolver->resolve(); + } + + //////////////////////////////////////////////////////////////////////////// + // addAllowedTypes() + //////////////////////////////////////////////////////////////////////////// + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + */ + public function testAddAllowedTypesFailsIfUnknownOption() + { + $this->resolver->addAllowedTypes('foo', 'string'); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfAddAllowedTypesFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->addAllowedTypes('bar', 'string'); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfInvalidAddedType() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->addAllowedTypes('foo', 'string'); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidAddedType() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->addAllowedTypes('foo', 'string'); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfInvalidAddedTypeMultiple() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->addAllowedTypes('foo', array('string', 'bool')); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidAddedTypeMultiple() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->addAllowedTypes('foo', array('string', 'bool')); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + public function testAddAllowedTypesDoesNotOverwrite() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedTypes('foo', 'string'); + $this->resolver->addAllowedTypes('foo', 'bool'); + + $this->resolver->setDefault('foo', 'bar'); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + public function testAddAllowedTypesDoesNotOverwrite2() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedTypes('foo', 'string'); + $this->resolver->addAllowedTypes('foo', 'bool'); + + $this->resolver->setDefault('foo', false); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + //////////////////////////////////////////////////////////////////////////// + // setAllowedValues() + //////////////////////////////////////////////////////////////////////////// + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + */ + public function testSetAllowedValuesFailsIfUnknownOption() + { + $this->resolver->setAllowedValues('foo', 'bar'); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetAllowedValuesFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->setAllowedValues('bar', 'baz'); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value 42 is invalid. Accepted values are: "bar". + */ + public function testResolveFailsIfInvalidValue() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedValues('foo', 'bar'); + + $this->resolver->resolve(array('foo' => 42)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value null is invalid. Accepted values are: "bar". + */ + public function testResolveFailsIfInvalidValueIsNull() + { + $this->resolver->setDefault('foo', null); + $this->resolver->setAllowedValues('foo', 'bar'); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfInvalidValueStrict() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->setAllowedValues('foo', '42'); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidValue() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', 'bar'); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testResolveSucceedsIfValidValueIsNull() + { + $this->resolver->setDefault('foo', null); + $this->resolver->setAllowedValues('foo', null); + + $this->assertEquals(array('foo' => null), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value 42 is invalid. Accepted values are: "bar", false, null. + */ + public function testResolveFailsIfInvalidValueMultiple() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->setAllowedValues('foo', array('bar', false, null)); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidValueMultiple() + { + $this->resolver->setDefault('foo', 'baz'); + $this->resolver->setAllowedValues('foo', array('bar', 'baz')); + + $this->assertEquals(array('foo' => 'baz'), $this->resolver->resolve()); + } + + public function testResolveFailsIfClosureReturnsFalse() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->setAllowedValues('foo', function ($value) use (&$passedValue) { + $passedValue = $value; + + return false; + }); + + try { + $this->resolver->resolve(); + $this->fail('Should fail'); + } catch (InvalidOptionsException $e) { + } + + $this->assertSame(42, $passedValue); + } + + public function testResolveSucceedsIfClosureReturnsTrue() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', function ($value) use (&$passedValue) { + $passedValue = $value; + + return true; + }); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + $this->assertSame('bar', $passedValue); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfAllClosuresReturnFalse() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->setAllowedValues('foo', array( + function () { return false; }, + function () { return false; }, + function () { return false; }, + )); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfAnyClosureReturnsTrue() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', array( + function () { return false; }, + function () { return true; }, + function () { return false; }, + )); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + //////////////////////////////////////////////////////////////////////////// + // addAllowedValues() + //////////////////////////////////////////////////////////////////////////// + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + */ + public function testAddAllowedValuesFailsIfUnknownOption() + { + $this->resolver->addAllowedValues('foo', 'bar'); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfAddAllowedValuesFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->addAllowedValues('bar', 'baz'); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfInvalidAddedValue() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->addAllowedValues('foo', 'bar'); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidAddedValue() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->addAllowedValues('foo', 'bar'); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testResolveSucceedsIfValidAddedValueIsNull() + { + $this->resolver->setDefault('foo', null); + $this->resolver->addAllowedValues('foo', null); + + $this->assertEquals(array('foo' => null), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfInvalidAddedValueMultiple() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->addAllowedValues('foo', array('bar', 'baz')); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidAddedValueMultiple() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->addAllowedValues('foo', array('bar', 'baz')); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testAddAllowedValuesDoesNotOverwrite() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', 'bar'); + $this->resolver->addAllowedValues('foo', 'baz'); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testAddAllowedValuesDoesNotOverwrite2() + { + $this->resolver->setDefault('foo', 'baz'); + $this->resolver->setAllowedValues('foo', 'bar'); + $this->resolver->addAllowedValues('foo', 'baz'); + + $this->assertEquals(array('foo' => 'baz'), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfAllAddedClosuresReturnFalse() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->setAllowedValues('foo', function () { return false; }); + $this->resolver->addAllowedValues('foo', function () { return false; }); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfAnyAddedClosureReturnsTrue() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', function () { return false; }); + $this->resolver->addAllowedValues('foo', function () { return true; }); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testResolveSucceedsIfAnyAddedClosureReturnsTrue2() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', function () { return true; }); + $this->resolver->addAllowedValues('foo', function () { return false; }); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + //////////////////////////////////////////////////////////////////////////// + // setNormalizer() + //////////////////////////////////////////////////////////////////////////// + + public function testSetNormalizerReturnsThis() + { + $this->resolver->setDefault('foo', 'bar'); + $this->assertSame($this->resolver, $this->resolver->setNormalizer('foo', function () {})); + } + + public function testSetNormalizerClosure() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setNormalizer('foo', function () { + return 'normalized'; + }); + + $this->assertEquals(array('foo' => 'normalized'), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + */ + public function testSetNormalizerFailsIfUnknownOption() + { + $this->resolver->setNormalizer('foo', function () {}); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetNormalizerFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->setNormalizer('foo', function () {}); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + public function testNormalizerReceivesSetOption() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->resolver->setNormalizer('foo', function (Options $options, $value) { + return 'normalized['.$value.']'; + }); + + $this->assertEquals(array('foo' => 'normalized[bar]'), $this->resolver->resolve()); + } + + public function testNormalizerReceivesPassedOption() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->resolver->setNormalizer('foo', function (Options $options, $value) { + return 'normalized['.$value.']'; + }); + + $resolved = $this->resolver->resolve(array('foo' => 'baz')); + + $this->assertEquals(array('foo' => 'normalized[baz]'), $resolved); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testValidateTypeBeforeNormalization() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->resolver->setAllowedTypes('foo', 'int'); + + $this->resolver->setNormalizer('foo', function () { + Assert::fail('Should not be called.'); + }); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testValidateValueBeforeNormalization() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->resolver->setAllowedValues('foo', 'baz'); + + $this->resolver->setNormalizer('foo', function () { + Assert::fail('Should not be called.'); + }); + + $this->resolver->resolve(); + } + + public function testNormalizerCanAccessOtherOptions() + { + $this->resolver->setDefault('default', 'bar'); + $this->resolver->setDefault('norm', 'baz'); + + $this->resolver->setNormalizer('norm', function (Options $options) { + /* @var TestCase $test */ + Assert::assertSame('bar', $options['default']); + + return 'normalized'; + }); + + $this->assertEquals(array( + 'default' => 'bar', + 'norm' => 'normalized', + ), $this->resolver->resolve()); + } + + public function testNormalizerCanAccessLazyOptions() + { + $this->resolver->setDefault('lazy', function (Options $options) { + return 'bar'; + }); + $this->resolver->setDefault('norm', 'baz'); + + $this->resolver->setNormalizer('norm', function (Options $options) { + /* @var TestCase $test */ + Assert::assertEquals('bar', $options['lazy']); + + return 'normalized'; + }); + + $this->assertEquals(array( + 'lazy' => 'bar', + 'norm' => 'normalized', + ), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException + */ + public function testFailIfCyclicDependencyBetweenNormalizers() + { + $this->resolver->setDefault('norm1', 'bar'); + $this->resolver->setDefault('norm2', 'baz'); + + $this->resolver->setNormalizer('norm1', function (Options $options) { + $options['norm2']; + }); + + $this->resolver->setNormalizer('norm2', function (Options $options) { + $options['norm1']; + }); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException + */ + public function testFailIfCyclicDependencyBetweenNormalizerAndLazyOption() + { + $this->resolver->setDefault('lazy', function (Options $options) { + $options['norm']; + }); + + $this->resolver->setDefault('norm', 'baz'); + + $this->resolver->setNormalizer('norm', function (Options $options) { + $options['lazy']; + }); + + $this->resolver->resolve(); + } + + public function testCaughtExceptionFromNormalizerDoesNotCrashOptionResolver() + { + $throw = true; + + $this->resolver->setDefaults(array('catcher' => null, 'thrower' => null)); + + $this->resolver->setNormalizer('catcher', function (Options $options) { + try { + return $options['thrower']; + } catch (\Exception $e) { + return false; + } + }); + + $this->resolver->setNormalizer('thrower', function () use (&$throw) { + if ($throw) { + $throw = false; + throw new \UnexpectedValueException('throwing'); + } + + return true; + }); + + $this->assertSame(array('catcher' => false, 'thrower' => true), $this->resolver->resolve()); + } + + public function testCaughtExceptionFromLazyDoesNotCrashOptionResolver() + { + $throw = true; + + $this->resolver->setDefault('catcher', function (Options $options) { + try { + return $options['thrower']; + } catch (\Exception $e) { + return false; + } + }); + + $this->resolver->setDefault('thrower', function (Options $options) use (&$throw) { + if ($throw) { + $throw = false; + throw new \UnexpectedValueException('throwing'); + } + + return true; + }); + + $this->assertSame(array('catcher' => false, 'thrower' => true), $this->resolver->resolve()); + } + + public function testInvokeEachNormalizerOnlyOnce() + { + $calls = 0; + + $this->resolver->setDefault('norm1', 'bar'); + $this->resolver->setDefault('norm2', 'baz'); + + $this->resolver->setNormalizer('norm1', function ($options) use (&$calls) { + Assert::assertSame(1, ++$calls); + + $options['norm2']; + }); + $this->resolver->setNormalizer('norm2', function () use (&$calls) { + Assert::assertSame(2, ++$calls); + }); + + $this->resolver->resolve(); + + $this->assertSame(2, $calls); + } + + public function testNormalizerNotCalledForUnsetOptions() + { + $this->resolver->setDefined('norm'); + + $this->resolver->setNormalizer('norm', function () { + Assert::fail('Should not be called.'); + }); + + $this->assertEmpty($this->resolver->resolve()); + } + + //////////////////////////////////////////////////////////////////////////// + // setDefaults() + //////////////////////////////////////////////////////////////////////////// + + public function testSetDefaultsReturnsThis() + { + $this->assertSame($this->resolver, $this->resolver->setDefaults(array('foo', 'bar'))); + } + + public function testSetDefaults() + { + $this->resolver->setDefault('one', '1'); + $this->resolver->setDefault('two', 'bar'); + + $this->resolver->setDefaults(array( + 'two' => '2', + 'three' => '3', + )); + + $this->assertEquals(array( + 'one' => '1', + 'two' => '2', + 'three' => '3', + ), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetDefaultsFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->setDefaults(array('two' => '2')); + }); + + $this->resolver->resolve(); + } + + //////////////////////////////////////////////////////////////////////////// + // remove() + //////////////////////////////////////////////////////////////////////////// + + public function testRemoveReturnsThis() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame($this->resolver, $this->resolver->remove('foo')); + } + + public function testRemoveSingleOption() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setDefault('baz', 'boo'); + $this->resolver->remove('foo'); + + $this->assertSame(array('baz' => 'boo'), $this->resolver->resolve()); + } + + public function testRemoveMultipleOptions() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setDefault('baz', 'boo'); + $this->resolver->setDefault('doo', 'dam'); + + $this->resolver->remove(array('foo', 'doo')); + + $this->assertSame(array('baz' => 'boo'), $this->resolver->resolve()); + } + + public function testRemoveLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + return 'lazy'; + }); + $this->resolver->remove('foo'); + + $this->assertSame(array(), $this->resolver->resolve()); + } + + public function testRemoveNormalizer() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setNormalizer('foo', function (Options $options, $value) { + return 'normalized'; + }); + $this->resolver->remove('foo'); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testRemoveAllowedTypes() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedTypes('foo', 'int'); + $this->resolver->remove('foo'); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testRemoveAllowedValues() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', array('baz', 'boo')); + $this->resolver->remove('foo'); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfRemoveFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->remove('bar'); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + public function testRemoveUnknownOptionIgnored() + { + $this->assertNotNull($this->resolver->remove('foo')); + } + + //////////////////////////////////////////////////////////////////////////// + // clear() + //////////////////////////////////////////////////////////////////////////// + + public function testClearReturnsThis() + { + $this->assertSame($this->resolver, $this->resolver->clear()); + } + + public function testClearRemovesAllOptions() + { + $this->resolver->setDefault('one', 1); + $this->resolver->setDefault('two', 2); + + $this->resolver->clear(); + + $this->assertEmpty($this->resolver->resolve()); + } + + public function testClearLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + return 'lazy'; + }); + $this->resolver->clear(); + + $this->assertSame(array(), $this->resolver->resolve()); + } + + public function testClearNormalizer() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setNormalizer('foo', function (Options $options, $value) { + return 'normalized'; + }); + $this->resolver->clear(); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testClearAllowedTypes() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedTypes('foo', 'int'); + $this->resolver->clear(); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testClearAllowedValues() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', 'baz'); + $this->resolver->clear(); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfClearFromLazyption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->clear(); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + public function testClearOptionAndNormalizer() + { + $this->resolver->setDefault('foo1', 'bar'); + $this->resolver->setNormalizer('foo1', function (Options $options) { + return ''; + }); + $this->resolver->setDefault('foo2', 'bar'); + $this->resolver->setNormalizer('foo2', function (Options $options) { + return ''; + }); + + $this->resolver->clear(); + $this->assertEmpty($this->resolver->resolve()); + } + + //////////////////////////////////////////////////////////////////////////// + // ArrayAccess + //////////////////////////////////////////////////////////////////////////// + + public function testArrayAccess() + { + $this->resolver->setDefault('default1', 0); + $this->resolver->setDefault('default2', 1); + $this->resolver->setRequired('required'); + $this->resolver->setDefined('defined'); + $this->resolver->setDefault('lazy1', function (Options $options) { + return 'lazy'; + }); + + $this->resolver->setDefault('lazy2', function (Options $options) { + Assert::assertArrayHasKey('default1', $options); + Assert::assertArrayHasKey('default2', $options); + Assert::assertArrayHasKey('required', $options); + Assert::assertArrayHasKey('lazy1', $options); + Assert::assertArrayHasKey('lazy2', $options); + Assert::assertArrayNotHasKey('defined', $options); + + Assert::assertSame(0, $options['default1']); + Assert::assertSame(42, $options['default2']); + Assert::assertSame('value', $options['required']); + Assert::assertSame('lazy', $options['lazy1']); + + // Obviously $options['lazy'] and $options['defined'] cannot be + // accessed + }); + + $this->resolver->resolve(array('default2' => 42, 'required' => 'value')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testArrayAccessGetFailsOutsideResolve() + { + $this->resolver->setDefault('default', 0); + + $this->resolver['default']; + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testArrayAccessExistsFailsOutsideResolve() + { + $this->resolver->setDefault('default', 0); + + isset($this->resolver['default']); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testArrayAccessSetNotSupported() + { + $this->resolver['default'] = 0; + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testArrayAccessUnsetNotSupported() + { + $this->resolver->setDefault('default', 0); + + unset($this->resolver['default']); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoSuchOptionException + * @expectedExceptionMessage The option "undefined" does not exist. Defined options are: "foo", "lazy". + */ + public function testFailIfGetNonExisting() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->resolver->setDefault('lazy', function (Options $options) { + $options['undefined']; + }); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoSuchOptionException + * @expectedExceptionMessage The optional option "defined" has no value set. You should make sure it is set with "isset" before reading it. + */ + public function testFailIfGetDefinedButUnset() + { + $this->resolver->setDefined('defined'); + + $this->resolver->setDefault('lazy', function (Options $options) { + $options['defined']; + }); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException + */ + public function testFailIfCyclicDependency() + { + $this->resolver->setDefault('lazy1', function (Options $options) { + $options['lazy2']; + }); + + $this->resolver->setDefault('lazy2', function (Options $options) { + $options['lazy1']; + }); + + $this->resolver->resolve(); + } + + //////////////////////////////////////////////////////////////////////////// + // Countable + //////////////////////////////////////////////////////////////////////////// + + public function testCount() + { + $this->resolver->setDefault('default', 0); + $this->resolver->setRequired('required'); + $this->resolver->setDefined('defined'); + $this->resolver->setDefault('lazy1', function () {}); + + $this->resolver->setDefault('lazy2', function (Options $options) { + Assert::assertCount(4, $options); + }); + + $this->assertCount(4, $this->resolver->resolve(array('required' => 'value'))); + } + + /** + * In resolve() we count the options that are actually set (which may be + * only a subset of the defined options). Outside of resolve(), it's not + * clear what is counted. + * + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testCountFailsOutsideResolve() + { + $this->resolver->setDefault('foo', 0); + $this->resolver->setRequired('bar'); + $this->resolver->setDefined('bar'); + $this->resolver->setDefault('lazy1', function () {}); + + count($this->resolver); + } +} diff --git a/vendor/symfony/options-resolver/composer.json b/vendor/symfony/options-resolver/composer.json new file mode 100644 index 0000000..895847e --- /dev/null +++ b/vendor/symfony/options-resolver/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/options-resolver", + "type": "library", + "description": "Symfony OptionsResolver Component", + "keywords": ["options", "config", "configuration"], + "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" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + } +} diff --git a/vendor/symfony/options-resolver/phpunit.xml.dist b/vendor/symfony/options-resolver/phpunit.xml.dist new file mode 100644 index 0000000..7e04e60 --- /dev/null +++ b/vendor/symfony/options-resolver/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/polyfill-php56/LICENSE b/vendor/symfony/polyfill-php56/LICENSE new file mode 100644 index 0000000..24fa32c --- /dev/null +++ b/vendor/symfony/polyfill-php56/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php56/Php56.php b/vendor/symfony/polyfill-php56/Php56.php new file mode 100644 index 0000000..6ec354e --- /dev/null +++ b/vendor/symfony/polyfill-php56/Php56.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php56; + +use Symfony\Polyfill\Util\Binary; + +/** + * @internal + */ +final class Php56 +{ + const LDAP_ESCAPE_FILTER = 1; + const LDAP_ESCAPE_DN = 2; + + public static function hash_equals($knownString, $userInput) + { + if (!\is_string($knownString)) { + trigger_error('Expected known_string to be a string, '.gettype($knownString).' given', E_USER_WARNING); + + return false; + } + + if (!\is_string($userInput)) { + trigger_error('Expected user_input to be a string, '.gettype($userInput).' given', E_USER_WARNING); + + return false; + } + + $knownLen = Binary::strlen($knownString); + $userLen = Binary::strlen($userInput); + + if ($knownLen !== $userLen) { + return false; + } + + $result = 0; + + for ($i = 0; $i < $knownLen; ++$i) { + $result |= \ord($knownString[$i]) ^ \ord($userInput[$i]); + } + + return 0 === $result; + } + + /** + * Stub implementation of the {@link ldap_escape()} function of the ldap + * extension. + * + * Escape strings for safe use in LDAP filters and DNs. + * + * @author Chris Wright + * + * @param string $subject + * @param string $ignore + * @param int $flags + * + * @return string + * + * @see http://stackoverflow.com/a/8561604 + */ + public static function ldap_escape($subject, $ignore = '', $flags = 0) + { + static $charMaps = null; + + if (null === $charMaps) { + $charMaps = array( + self::LDAP_ESCAPE_FILTER => array('\\', '*', '(', ')', "\x00"), + self::LDAP_ESCAPE_DN => array('\\', ',', '=', '+', '<', '>', ';', '"', '#', "\r"), + ); + + $charMaps[0] = array(); + + for ($i = 0; $i < 256; ++$i) { + $charMaps[0][\chr($i)] = sprintf('\\%02x', $i); + } + + for ($i = 0, $l = \count($charMaps[self::LDAP_ESCAPE_FILTER]); $i < $l; ++$i) { + $chr = $charMaps[self::LDAP_ESCAPE_FILTER][$i]; + unset($charMaps[self::LDAP_ESCAPE_FILTER][$i]); + $charMaps[self::LDAP_ESCAPE_FILTER][$chr] = $charMaps[0][$chr]; + } + + for ($i = 0, $l = \count($charMaps[self::LDAP_ESCAPE_DN]); $i < $l; ++$i) { + $chr = $charMaps[self::LDAP_ESCAPE_DN][$i]; + unset($charMaps[self::LDAP_ESCAPE_DN][$i]); + $charMaps[self::LDAP_ESCAPE_DN][$chr] = $charMaps[0][$chr]; + } + } + + // Create the base char map to escape + $flags = (int) $flags; + $charMap = array(); + + if ($flags & self::LDAP_ESCAPE_FILTER) { + $charMap += $charMaps[self::LDAP_ESCAPE_FILTER]; + } + + if ($flags & self::LDAP_ESCAPE_DN) { + $charMap += $charMaps[self::LDAP_ESCAPE_DN]; + } + + if (!$charMap) { + $charMap = $charMaps[0]; + } + + // Remove any chars to ignore from the list + $ignore = (string) $ignore; + + for ($i = 0, $l = \strlen($ignore); $i < $l; ++$i) { + unset($charMap[$ignore[$i]]); + } + + // Do the main replacement + $result = strtr($subject, $charMap); + + // Encode leading/trailing spaces if self::LDAP_ESCAPE_DN is passed + if ($flags & self::LDAP_ESCAPE_DN) { + if ($result[0] === ' ') { + $result = '\\20'.substr($result, 1); + } + + if ($result[\strlen($result) - 1] === ' ') { + $result = substr($result, 0, -1).'\\20'; + } + } + + return $result; + } +} diff --git a/vendor/symfony/polyfill-php56/README.md b/vendor/symfony/polyfill-php56/README.md new file mode 100644 index 0000000..307ce5b --- /dev/null +++ b/vendor/symfony/polyfill-php56/README.md @@ -0,0 +1,15 @@ +Symfony Polyfill / Php56 +======================== + +This component provides functions unavailable in releases prior to PHP 5.6: + +- [`hash_equals`](http://php.net/hash_equals) (part of [hash](http://php.net/hash) extension) +- [`ldap_escape`](http://php.net/ldap_escape) (part of [ldap](http://php.net/ldap) extension) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php56/bootstrap.php b/vendor/symfony/polyfill-php56/bootstrap.php new file mode 100644 index 0000000..587c2a8 --- /dev/null +++ b/vendor/symfony/polyfill-php56/bootstrap.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php56 as p; + +if (PHP_VERSION_ID < 50600) { + if (!function_exists('hash_equals')) { + function hash_equals($knownString, $userInput) { return p\Php56::hash_equals($knownString, $userInput); } + } + if (extension_loaded('ldap') && !function_exists('ldap_escape')) { + define('LDAP_ESCAPE_FILTER', 1); + define('LDAP_ESCAPE_DN', 2); + + function ldap_escape($subject, $ignore = '', $flags = 0) { return p\Php56::ldap_escape($subject, $ignore, $flags); } + } + + if (50509 === PHP_VERSION_ID && 4 === PHP_INT_SIZE) { + // Missing functions in PHP 5.5.9 - affects 32 bit builds of Ubuntu 14.04LTS + // See https://bugs.launchpad.net/ubuntu/+source/php5/+bug/1315888 + if (!function_exists('gzopen') && function_exists('gzopen64')) { + function gzopen($filename, $mode, $use_include_path = 0) { return gzopen64($filename, $mode, $use_include_path); } + } + if (!function_exists('gzseek') && function_exists('gzseek64')) { + function gzseek($zp, $offset, $whence = SEEK_SET) { return gzseek64($zp, $offset, $whence); } + } + if (!function_exists('gztell') && function_exists('gztell64')) { + function gztell($zp) { return gztell64($zp); } + } + } +} diff --git a/vendor/symfony/polyfill-php56/composer.json b/vendor/symfony/polyfill-php56/composer.json new file mode 100644 index 0000000..87de14e --- /dev/null +++ b/vendor/symfony/polyfill-php56/composer.json @@ -0,0 +1,32 @@ +{ + "name": "symfony/polyfill-php56", + "type": "library", + "description": "Symfony polyfill backporting some PHP 5.6+ 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", + "symfony/polyfill-util": "~1.0" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php56\\": "" }, + "files": [ "bootstrap.php" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + } +} diff --git a/vendor/symfony/polyfill-util/Binary.php b/vendor/symfony/polyfill-util/Binary.php new file mode 100644 index 0000000..815bc75 --- /dev/null +++ b/vendor/symfony/polyfill-util/Binary.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Util; + +if (extension_loaded('mbstring')) { + class Binary extends BinaryOnFuncOverload {} +} else { + class Binary extends BinaryNoFuncOverload {} +} diff --git a/vendor/symfony/polyfill-util/BinaryNoFuncOverload.php b/vendor/symfony/polyfill-util/BinaryNoFuncOverload.php new file mode 100644 index 0000000..800ad75 --- /dev/null +++ b/vendor/symfony/polyfill-util/BinaryNoFuncOverload.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\Polyfill\Util; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class BinaryNoFuncOverload +{ + public static function strlen($s) + { + return \strlen($s); + } + + public static function strpos($haystack, $needle, $offset = 0) + { + return strpos($haystack, $needle, $offset); + } + + public static function strrpos($haystack, $needle, $offset = 0) + { + return strrpos($haystack, $needle, $offset); + } + + public static function substr($string, $start, $length = PHP_INT_MAX) + { + return substr($string, $start, $length); + } + + public static function stripos($s, $needle, $offset = 0) + { + return stripos($s, $needle, $offset); + } + + public static function stristr($s, $needle, $part = false) + { + return stristr($s, $needle, $part); + } + + public static function strrchr($s, $needle, $part = false) + { + return strrchr($s, $needle, $part); + } + + public static function strripos($s, $needle, $offset = 0) + { + return strripos($s, $needle, $offset); + } + + public static function strstr($s, $needle, $part = false) + { + return strstr($s, $needle, $part); + } +} diff --git a/vendor/symfony/polyfill-util/BinaryOnFuncOverload.php b/vendor/symfony/polyfill-util/BinaryOnFuncOverload.php new file mode 100644 index 0000000..e1b886e --- /dev/null +++ b/vendor/symfony/polyfill-util/BinaryOnFuncOverload.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\Polyfill\Util; + +/** + * Binary safe version of string functions overloaded when MB_OVERLOAD_STRING is enabled. + * + * @author Nicolas Grekas + * + * @internal + */ +class BinaryOnFuncOverload +{ + public static function strlen($s) + { + return mb_strlen($s, '8bit'); + } + + public static function strpos($haystack, $needle, $offset = 0) + { + return mb_strpos($haystack, $needle, $offset, '8bit'); + } + + public static function strrpos($haystack, $needle, $offset = 0) + { + return mb_strrpos($haystack, $needle, $offset, '8bit'); + } + + public static function substr($string, $start, $length = 2147483647) + { + return mb_substr($string, $start, $length, '8bit'); + } + + public static function stripos($s, $needle, $offset = 0) + { + return mb_stripos($s, $needle, $offset, '8bit'); + } + + public static function stristr($s, $needle, $part = false) + { + return mb_stristr($s, $needle, $part, '8bit'); + } + + public static function strrchr($s, $needle, $part = false) + { + return mb_strrchr($s, $needle, $part, '8bit'); + } + + public static function strripos($s, $needle, $offset = 0) + { + return mb_strripos($s, $needle, $offset, '8bit'); + } + + public static function strstr($s, $needle, $part = false) + { + return mb_strstr($s, $needle, $part, '8bit'); + } +} diff --git a/vendor/symfony/polyfill-util/LICENSE b/vendor/symfony/polyfill-util/LICENSE new file mode 100644 index 0000000..24fa32c --- /dev/null +++ b/vendor/symfony/polyfill-util/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-util/LegacyTestListener.php b/vendor/symfony/polyfill-util/LegacyTestListener.php new file mode 100644 index 0000000..87caa50 --- /dev/null +++ b/vendor/symfony/polyfill-util/LegacyTestListener.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Util; + +/** + * @author Nicolas Grekas + */ +class LegacyTestListener extends \PHPUnit_Framework_TestSuite implements \PHPUnit_Framework_TestListener +{ + private $suite; + private $trait; + + public function __construct(\PHPUnit_Framework_TestSuite $suite = null) + { + if ($suite) { + $this->suite = $suite; + $this->setName($suite->getName().' with polyfills enabled'); + $this->addTest($suite); + } + $this->trait = new TestListenerTrait(); + } + + public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) + { + $this->trait->startTestSuite($suite); + } + + protected function setUp() + { + TestListenerTrait::$enabledPolyfills = $this->suite->getName(); + } + + protected function tearDown() + { + TestListenerTrait::$enabledPolyfills = false; + } + + public function addError(\PHPUnit_Framework_Test $test, \Exception $e, $time) + { + $this->trait->addError($test, $e, $time); + } + + public function addWarning(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_Warning $e, $time) + { + } + + public function addFailure(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_AssertionFailedError $e, $time) + { + $this->trait->addError($test, $e, $time); + } + + public function addIncompleteTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) + { + } + + public function addRiskyTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) + { + } + + public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) + { + } + + public function endTestSuite(\PHPUnit_Framework_TestSuite $suite) + { + } + + public function startTest(\PHPUnit_Framework_Test $test) + { + } + + public function endTest(\PHPUnit_Framework_Test $test, $time) + { + } +} diff --git a/vendor/symfony/polyfill-util/README.md b/vendor/symfony/polyfill-util/README.md new file mode 100644 index 0000000..1c655fc --- /dev/null +++ b/vendor/symfony/polyfill-util/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Util +======================= + +This component provides binary-safe string functions, using the +[mbstring](https://php.net/mbstring) extension when available. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-util/TestListener.php b/vendor/symfony/polyfill-util/TestListener.php new file mode 100644 index 0000000..3a9a7bc --- /dev/null +++ b/vendor/symfony/polyfill-util/TestListener.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Util; + +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\TestListener as TestListenerInterface; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; + +if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { + class_alias('Symfony\Polyfill\Util\LegacyTestListener', 'Symfony\Polyfill\Util\TestListener'); +// Using an early return instead of a else does not work when using the PHPUnit phar due to some weird PHP behavior (the class +// gets defined without executing the code before it and so the definition is not properly conditional) +} else { + /** + * @author Nicolas Grekas + */ + class TestListener extends TestSuite implements TestListenerInterface + { + private $suite; + private $trait; + + public function __construct(TestSuite $suite = null) + { + if ($suite) { + $this->suite = $suite; + $this->setName($suite->getName().' with polyfills enabled'); + $this->addTest($suite); + } + $this->trait = new TestListenerTrait(); + } + + public function startTestSuite(TestSuite $suite) + { + $this->trait->startTestSuite($suite); + } + + protected function setUp() + { + TestListenerTrait::$enabledPolyfills = $this->suite->getName(); + } + + protected function tearDown() + { + TestListenerTrait::$enabledPolyfills = false; + } + + public function addError(Test $test, \Exception $e, $time) + { + $this->trait->addError($test, $e, $time); + } + + public function addWarning(Test $test, Warning $e, $time) + { + } + + public function addFailure(Test $test, AssertionFailedError $e, $time) + { + $this->trait->addError($test, $e, $time); + } + + public function addIncompleteTest(Test $test, \Exception $e, $time) + { + } + + public function addRiskyTest(Test $test, \Exception $e, $time) + { + } + + public function addSkippedTest(Test $test, \Exception $e, $time) + { + } + + public function endTestSuite(TestSuite $suite) + { + } + + public function startTest(Test $test) + { + } + + public function endTest(Test $test, $time) + { + } + } +} diff --git a/vendor/symfony/polyfill-util/TestListenerTrait.php b/vendor/symfony/polyfill-util/TestListenerTrait.php new file mode 100644 index 0000000..758e383 --- /dev/null +++ b/vendor/symfony/polyfill-util/TestListenerTrait.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Util; + +/** + * @author Nicolas Grekas + */ +class TestListenerTrait +{ + public static $enabledPolyfills; + + public function startTestSuite($mainSuite) + { + if (null !== self::$enabledPolyfills) { + return; + } + self::$enabledPolyfills = false; + $SkippedTestError = class_exists('PHPUnit\Framework\SkippedTestError') ? 'PHPUnit\Framework\SkippedTestError' : 'PHPUnit_Framework_SkippedTestError'; + $TestListener = class_exists('Symfony\Polyfill\Util\TestListener', false) ? 'Symfony\Polyfill\Util\TestListener' : 'Symfony\Polyfill\Util\LegacyTestListener'; + + foreach ($mainSuite->tests() as $suite) { + $testClass = $suite->getName(); + if (!$tests = $suite->tests()) { + continue; + } + if (!preg_match('/^(.+)\\\\Tests(\\\\.*)Test$/', $testClass, $m)) { + $mainSuite->addTest($TestListener::warning('Unknown naming convention for '.$testClass)); + continue; + } + if (!class_exists($m[1].$m[2])) { + continue; + } + $testedClass = new \ReflectionClass($m[1].$m[2]); + $bootstrap = new \SplFileObject(dirname($testedClass->getFileName()).'/bootstrap.php'); + $warnings = array(); + $defLine = null; + + foreach (new \RegexIterator($bootstrap, '/return p\\\\'.$testedClass->getShortName().'::/') as $defLine) { + if (!preg_match('/^\s*function (?P[^\(]++)(?P\([^\)]*+\)) \{ (?return p\\\\'.$testedClass->getShortName().'::[^\(]++)(?P\([^\)]*+\)); \}$/', $defLine, $f)) { + $warnings[] = $TestListener::warning('Invalid line in bootstrap.php: '.trim($defLine)); + continue; + } + $testNamespace = substr($testClass, 0, strrpos($testClass, '\\')); + if (function_exists($testNamespace.'\\'.$f['name'])) { + continue; + } + + try { + $r = new \ReflectionFunction($f['name']); + if ($r->isUserDefined()) { + throw new \ReflectionException(); + } + if (false !== strpos($f['signature'], '&')) { + $defLine = sprintf('return \\%s%s', $f['name'], $f['args']); + } else { + $defLine = sprintf("return \\call_user_func_array('%s', func_get_args())", $f['name']); + } + } catch (\ReflectionException $e) { + $defLine = sprintf("throw new \\{$SkippedTestError}('Internal function not found: %s')", $f['name']); + } + + eval(<<getNamespaceName()} as p; + +function {$f['name']}{$f['signature']} +{ + if ('{$testClass}' === TestListenerTrait::\$enabledPolyfills) { + {$f['return']}{$f['args']}; + } + + {$defLine}; +} +EOPHP + ); + } + if (!$warnings && null === $defLine) { + $warnings[] = new $SkippedTestError('No Polyfills found in bootstrap.php for '.$testClass); + } else { + $mainSuite->addTest(new $TestListener($suite)); + } + } + foreach ($warnings as $w) { + $mainSuite->addTest($w); + } + } + + public function addError($test, \Exception $e, $time) + { + if (false !== self::$enabledPolyfills) { + $r = new \ReflectionProperty('Exception', 'message'); + $r->setAccessible(true); + $r->setValue($e, 'Polyfills enabled, '.$r->getValue($e)); + } + } +} diff --git a/vendor/symfony/polyfill-util/composer.json b/vendor/symfony/polyfill-util/composer.json new file mode 100644 index 0000000..133968f --- /dev/null +++ b/vendor/symfony/polyfill-util/composer.json @@ -0,0 +1,30 @@ +{ + "name": "symfony/polyfill-util", + "type": "library", + "description": "Symfony utilities for portability of PHP codes", + "keywords": ["polyfill", "shim", "compat", "compatibility"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Util\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + } +}