diff --git a/.all-contributorsrc b/.all-contributorsrc index 41f8ba6f..174060f4 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -31,6 +31,52 @@ "contributions": [ "code" ] + }, + { + "login": "LadySolveig", + "name": "Martina Scholz", + "avatar_url": "https://avatars.githubusercontent.com/u/64533137?v=4", + "profile": "https://github.com/LadySolveig", + "contributions": [ + "code" + ] + }, + { + "login": "escopecz", + "name": "John Linhart", + "avatar_url": "https://avatars.githubusercontent.com/u/1235442?v=4", + "profile": "http://johnlinhart.com", + "contributions": [ + "review" + ] + }, + { + "login": "Rocksheep", + "name": "Marinus van Velzen", + "avatar_url": "https://avatars.githubusercontent.com/u/1311371?v=4", + "profile": "https://github.com/Rocksheep", + "contributions": [ + "code" + ] + }, + { + "login": "PierreAmmeloot", + "name": "Pierre Ammeloot", + "avatar_url": "https://avatars.githubusercontent.com/u/4603318?v=4", + "profile": "https://pierre.ammeloot.fr", + "contributions": [ + "userTesting" + ] + }, + { + "login": "matbcvo", + "name": "Martin Vooremäe", + "avatar_url": "https://avatars.githubusercontent.com/u/1006437?v=4", + "profile": "https://matbcvo.github.io", + "contributions": [ + "code", + "test" + ] } ], "contributorsPerLine": 7, @@ -38,5 +84,7 @@ "projectOwner": "mautic", "repoType": "github", "repoHost": "https://github.com", - "skipCi": true + "skipCi": true, + "commitConvention": "angular", + "commitType": "docs" } diff --git a/.ddev/local.config.php.dist b/.ddev/local.config.php.dist index c145ba34..fbf689e4 100644 --- a/.ddev/local.config.php.dist +++ b/.ddev/local.config.php.dist @@ -52,10 +52,10 @@ return [ 'version' => 'OAuth1a', // Required for OAuth1a and OAuth2 - 'baseUrl' => 'http://localhost/mautic/index_dev.php', + 'baseUrl' => 'http://localhost/mautic/index.php', // Required for All tests - 'apiUrl' => 'http://localhost/mautic/index_dev.php/api/', + 'apiUrl' => 'http://localhost/mautic/index.php/api/', // Required for EmailsTest 'testEmail' => 'notexisting@email.com', diff --git a/.ddev/mautic-local.php.dist b/.ddev/mautic-local.php.dist index 72f0206f..4afef178 100644 --- a/.ddev/mautic-local.php.dist +++ b/.ddev/mautic-local.php.dist @@ -14,4 +14,9 @@ $parameters = [ 'db_password' => 'db', 'admin_email' => 'mautic@ddev.local', 'admin_password' => 'mautic', + 'mailer_from_name' => 'DDEV', + 'mailer_from_email' => 'mautic@ddev.local', + 'mailer_transport' => 'smtp', + 'mailer_host' => 'localhost', + 'mailer_port' => '1025', ]; diff --git a/.ddev/mautic-setup.sh b/.ddev/mautic-setup.sh index 265697e3..ae7b8f2c 100755 --- a/.ddev/mautic-setup.sh +++ b/.ddev/mautic-setup.sh @@ -11,9 +11,7 @@ setup_mautic() { cp ../.ddev/mautic-local.php.dist ./app/config/local.php printf "Installing Mautic...\n" - php bin/console mautic:install --force http://localhost/mautic \ - --mailer_from_name="DDEV" --mailer_from_email="mautic@ddev.local" \ - --mailer_transport="smtp" --mailer_host="localhost" --mailer_port="1025" + php bin/console mautic:install --force http://localhost/mautic php bin/console cache:warmup --no-interaction --env=dev printf "Enabling Twilio plugin for tests...\n" diff --git a/.github/ci-files/.env b/.github/ci-files/.env new file mode 100644 index 00000000..ad4a26e6 --- /dev/null +++ b/.github/ci-files/.env @@ -0,0 +1,28 @@ +# In all environments, the following files are loaded if they exist, +# the latter taking precedence over the former: +# +# * .env contains default values for the environment variables needed by the app +# * .env.local uncommitted file with local overrides +# * .env.$APP_ENV committed environment-specific defaults +# * .env.$APP_ENV.local uncommitted environment-specific overrides +# +# Real environment variables win over .env files. +# +# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. +# +# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). +# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration +APP_ENV=test +APP_DEBUG=0 + +DB_HOST=127.0.0.1 +DB_NAME=mautictest +DB_USER=root +DB_PORT=3306 +DB_PASSWD= +MAUTIC_DB_PREFIX= +MAUTIC_TABLE_PREFIX= +MAUTIC_ENV=test +MAUTIC_ADMIN_USERNAME=admin +MAUTIC_ADMIN_PASSWORD=mautic +MAUTIC_DB_DRIVER=pdo_mysql diff --git a/.github/ci-files/local.config.php b/.github/ci-files/local.config.php index d9213085..a61f0b97 100644 --- a/.github/ci-files/local.config.php +++ b/.github/ci-files/local.config.php @@ -34,7 +34,7 @@ | */ 'userName' => 'admin', - 'password' => 'mautic', + 'password' => 'Maut1cR0cks!', /* |-------------------------------------------------------------------------- @@ -52,10 +52,10 @@ 'version' => 'OAuth1a', // Required for OAuth1a and OAuth2 - 'baseUrl' => 'http://localhost/index_dev.php', + 'baseUrl' => 'http://localhost/index.php', // Required for All tests - 'apiUrl' => 'http://localhost/index_dev.php/api/', + 'apiUrl' => 'http://localhost/index.php/api/', // Required for EmailsTest 'testEmail' => 'notexisting@email.com', diff --git a/.github/ci-files/local.php b/.github/ci-files/local_4.php similarity index 81% rename from .github/ci-files/local.php rename to .github/ci-files/local_4.php index 0fc13904..a207e534 100644 --- a/.github/ci-files/local.php +++ b/.github/ci-files/local_4.php @@ -14,4 +14,7 @@ 'db_password' => '', 'admin_email' => 'github-actions@mautic.org', 'admin_password' => 'mautic', + 'mailer_transport' => 'smtp', + 'mailer_host' => 'localhost', + 'mailer_port' => '1025', ]; diff --git a/.github/ci-files/local_5.php b/.github/ci-files/local_5.php new file mode 100644 index 00000000..5c5371e5 --- /dev/null +++ b/.github/ci-files/local_5.php @@ -0,0 +1,21 @@ + true, + 'api_enable_basic_auth' => true, + 'db_driver' => 'pdo_mysql', + 'db_host' => '127.0.0.1', + 'db_table_prefix' => null, + 'db_port' => getenv('DB_PORT'), + 'db_name' => 'mautictest', + 'db_user' => 'root', + 'db_password' => '', + 'admin_email' => 'github-actions@mautic.org', + 'admin_password' => 'Maut1cR0cks!', + 'mailer_from_name' => 'GitHub Actions', + 'mailer_from_email' => 'github-actions@mautic.org', + 'mailer_transport' => 'smtp', + 'mailer_dsn' => 'smtp://localhost:1025', +]; diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4f458d5e..f33e88b1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,12 +13,12 @@ on: required: false jobs: phpunit: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 name: PHPUnit tests services: mysql: - image: mysql:5.7 + image: mysql:8.4 env: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: mautictest @@ -30,7 +30,7 @@ jobs: image: mailhog/mailhog:latest ports: - 1025:1025 - + redis: image: redis:6 ports: @@ -45,7 +45,7 @@ jobs: - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.3 ini-values: session.save_handler=redis, session.save_path="tcp://127.0.0.1:6379" extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, mysql, pdo_mysql coverage: pcov @@ -57,14 +57,15 @@ jobs: # We need the sed command at the bottom to set the PHP session save path to a directory that's writable for PHP # NOTE: update the PHP version below as well if you're updating PHP! run: | - sudo add-apt-repository ppa:ondrej/php -y - sudo add-apt-repository ppa:ondrej/apache2 -y - sudo apt-get install apache2 libapache2-mod-php8.0 + sudo apt-get update -o Acquire::Retries=3 + sudo apt-get install -y --no-install-recommends apache2 libapache2-mod-php8.3 + sudo a2enmod rewrite - sudo sed -i 's,^session.save_handler =.*$,session.save_handler = redis,' /etc/php/8.0/apache2/php.ini - sudo sed -i 's,^;session.save_path =.*$,session.save_path = "tcp://127.0.0.1:6379",' /etc/php/8.0/apache2/php.ini + sudo sed -i 's,^session.save_handler =.*$,session.save_handler = redis,' /etc/php/8.3/apache2/php.ini + sudo sed -i 's,^;session.save_path =.*$,session.save_path = "tcp://127.0.0.1:6379",' /etc/php/8.3/apache2/php.ini + sudo sed -i 's,^memory_limit =.*$,memory_limit = 256M,' /etc/php/8.3/apache2/php.ini sudo service apache2 restart - cat /etc/php/8.0/apache2/php.ini | grep session + cat /etc/php/8.3/apache2/php.ini | grep session - name: Install dependencies run: | @@ -89,37 +90,49 @@ jobs: sudo chown -R www-data:www-data /var/www/html rm -rf /var/www/html/* mv $GITHUB_WORKSPACE/mautic/* /var/www/html/ + cp ./.github/ci-files/.env /var/www/html/ + sed -i 's/DB_PORT=3306/DB_PORT=${{ job.services.mysql.ports[3306] }}/g' /var/www/html/.env + rm -f /var/www/html/.env.test - name: Install Mautic env: DB_PORT: ${{ job.services.mysql.ports[3306] }} run: | composer install --prefer-dist --no-progress - cp $GITHUB_WORKSPACE/.github/ci-files/local.php ./app/config/local.php - php bin/console mautic:install http://localhost/ \ - --force --mailer_from_name="GitHub Actions" --mailer_from_email="github-actions@mautic.org" \ - --mailer_transport="smtp" --mailer_host="localhost" --mailer_port="1025" --env=dev - php bin/console cache:warmup --no-interaction --env=dev + if [[ "$(jq -r '.version | .[0:1]' app/release_metadata.json)" == "4" ]]; then + cp $GITHUB_WORKSPACE/.github/ci-files/local_4.php ./app/config/local.php + php bin/console mautic:install http://localhost/ --force --env=test --mailer_from_name="GitHub Actions" --mailer_from_email="github-actions@mautic.org" + sed -i "s/('prod',/('test',/g" ./index.php + echo "MAUTIC_VERSION=4" >> $GITHUB_ENV + else + cp $GITHUB_WORKSPACE/.github/ci-files/local_5.php ./config/local.php + php bin/console mautic:install http://localhost/ --force --env=test + echo "MAUTIC_VERSION=5" >> $GITHUB_ENV + fi + + php bin/console cache:warmup --no-interaction --env=test working-directory: /var/www/html/ # Enable Twilio plugin with random credentials (needed for MessagesTest to function, doesn't actually contact Twilio API). - name: Enable Twilio plugin run: | mysql -uroot -P${{ job.services.mysql.ports[3306] }} -h127.0.0.1 -e "USE mautictest; INSERT INTO plugin_integration_settings (plugin_id, name, is_published, supported_features, api_keys, feature_settings) VALUES (NULL, 'Twilio', 1, 'a:0:{}', 'a:2:{s:8:\"username\";s:169:\"bzFmNlIydWRSZXlIN2lQVkdpanJ4aTQ2NUh6RVdDbHlLRVhsWGZ4b0kyZVNxLzYrQ1J6V1RvMnlhVEp0c245TEp6eStQekx5ZVhLWjB1YVdoR3RnR2dHQ3k1emVVdGt5NzZKUmtjUnJ3c1E9|L8tbZRIYhwatT7Mq+HAdYA==\";s:8:\"password\";s:169:\"T2d2cFpXQWE5YVZnNFFianJSYURRYUtGRHBNZGZjM1VETXg2Wm5Va3NheW43MjVWUlJhTVlCL2pYMDBpbElONStiVVBNbEM3M3BaeGJMNkFKNUFEN1pTNldSRjc4bUM4SDh1SE9OY1k5MTg9|TeuSvfx4XSUOvp0O7T49Cg==\";}', 'a:4:{s:20:\"sending_phone_number\";N;s:22:\"disable_trackable_urls\";i:0;s:16:\"frequency_number\";N;s:14:\"frequency_time\";N;}');" - php bin/console mautic:plugins:reload --env=dev + php bin/console mautic:plugins:reload --env=test working-directory: /var/www/html - name: Set correct ownership so Apache can access the files run: sudo chown -R www-data:www-data /var/www/html - + - name: Run tests - run: vendor/bin/paratest -p 3 --coverage-clover coverage.xml + run: | + sed -i 's/env name="MAUTIC_VERSION" value="5"/env name="MAUTIC_VERSION" value="${{ env.MAUTIC_VERSION }}"/g' ./phpunit.xml.dist + vendor/bin/phpunit --coverage-clover coverage.xml - name: Upload code coverage run: bash <(curl -s https://codecov.io/bash) - + - name: Upload logs as artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: always() with: name: mautic-logs @@ -132,9 +145,9 @@ jobs: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} SLACK_MESSAGE: 'The daily API library tests against mautic/mautic have failed. Most likely a PR was merged recently which introduced a regression of some sort.' - cs: - runs-on: ubuntu-20.04 - name: CS tests + tests: + runs-on: ubuntu-24.04 + name: CS & PHPSTAN tests steps: - uses: actions/checkout@v3 @@ -142,7 +155,7 @@ jobs: - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.3 extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, mysql, pdo_mysql - name: Install dependencies @@ -151,4 +164,7 @@ jobs: composer install --prefer-dist --no-progress - name: Run CS tests - run: vendor/bin/php-cs-fixer fix --config=.php_cs -v --dry-run --using-cache=no --show-progress=dots --diff $(git diff -- '*.php' --name-only --diff-filter=ACMRTUXB "HEAD~..HEAD") + run: vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php -v --dry-run --using-cache=no --show-progress=dots --diff $(git diff -- '*.php' --name-only --diff-filter=ACMRTUXB "HEAD~..HEAD") + + - name: Run PHPSTAN tests + run: composer phpstan \ No newline at end of file diff --git a/.gitignore b/.gitignore index c847cd4a..574b2f92 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,11 @@ /tests/local.config.php_* /tests/local.tokens.php /tests/phpunit.phar +/composer.lock /vendor .DS_Store +/.php-cs-fixer.cache .phpunit.result.cache /.ddev/mautic-preference /mautic +CLAUDE.md diff --git a/.php_cs b/.php_cs deleted file mode 100644 index d3b6865b..00000000 --- a/.php_cs +++ /dev/null @@ -1,20 +0,0 @@ -in(__DIR__.'/lib') - ->in(__DIR__.'/tests'); - -return PhpCsFixer\Config::create() - ->setRules([ - '@Symfony' => true, - 'binary_operator_spaces' => [ - 'align_double_arrow' => true, - 'align_equals' => true - ], - 'ordered_imports' => true, - 'array_syntax' => [ - 'syntax' => 'short' - ], - 'no_unused_imports' => false, - ]) - ->setFinder($finder); \ No newline at end of file diff --git a/.well-known/funding-manifest-urls b/.well-known/funding-manifest-urls new file mode 100644 index 00000000..1f60e471 --- /dev/null +++ b/.well-known/funding-manifest-urls @@ -0,0 +1 @@ +https://github.com/mautic/mautic/blob/5.x/funding.json diff --git a/README.md b/README.md index 6fc99f22..6fc2eebc 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ [](https://codecov.io/gh/mautic/api-library) [](//packagist.org/packages/mautic/api-library) [](//packagist.org/packages/mautic/api-library) [](//packagist.org/packages/mautic/api-library) [](//packagist.org/packages/mautic/api-library) -[](#contributors-) +[](#contributors-) # Using the Mautic API Library ## Requirements -* PHP 7.2 or newer -* cURL support +* PHP 8.0 or newer ## Installing the API Library You can install the API Library with the following command: @@ -16,6 +15,38 @@ You can install the API Library with the following command: composer require mautic/api-library ``` +N.B. Make sure you have installed a PSR-18 HTTP Client before you install this package or install one at the same time e.g. `composer require mautic/api-library guzzlehttp/guzzle:^7.3`. + +### HTTP Client + +We are decoupled from any HTTP messaging client with the help of [PSR-18 HTTP Client](https://www.php-fig.org/psr/psr-18/). This requires an extra package providing [psr/http-client-implementation](https://packagist.org/providers/psr/http-client-implementation). To use Guzzle 7, for example, simply require `guzzlehttp/guzzle`: + +``` bash +composer require guzzlehttp/guzzle:^7.3 +``` + +The installed HTTP Client is auto-discovered using [php-http/discovery](https://packagist.org/providers/php-http/discovery), but you can also provide your own HTTP Client if you like. + +```php + 10, +]); + +// Initiate the auth object +$initAuth = new ApiAuth($httpClient); +$auth = $initAuth->newAuth($settings); +// etc. +``` + ## Mautic Setup The API must be enabled in Mautic. Within Mautic, go to the Configuration page (located in the Settings menu) and under API Settings enable Mautic's API. If you intend to use Basic Authentication, ensure you enable it. You can also choose which OAuth protocol to use here. After saving the configuration, go to the API Credentials page @@ -167,13 +198,47 @@ $auth = $initAuth->newAuth($settings, 'BasicAuth'); ]; ``` -**Note:** You can also specify a CURLOPT_TIMEOUT in the request (default is set to wait indefinitely): + +### Using 2-Legged OAuth2 (Client Credentials) + +The Client Credentials grant is used when applications request an access token to access their own resources, not on behalf of a user. This is ideal for server-to-server communication (cron jobs, background processes, etc.). + +**Note:** This requires Mautic 4.0+ with the client_credentials grant type enabled. + ```php + 'TwoLeggedOAuth2', + 'baseUrl' => 'https://your-mautic.com', + 'clientKey' => '', // Client ID from Mautic API credentials + 'clientSecret' => '', // Client Secret from Mautic API credentials +]; + +// If you have a stored access token, you can pass it to avoid requesting a new one +// $settings['accessToken'] = 'your_stored_access_token'; +// $settings['accessTokenExpires'] = 1234567890; // Unix timestamp + $initAuth = new ApiAuth(); -$auth = $initAuth->newAuth($settings, 'BasicAuth'); -$timeout = 10; +$auth = $initAuth->newAuth($settings, $settings['AuthMethod']); + +// Request a new access token if needed +if (!$auth->isAuthorized()) { + $auth->requestAccessToken(); +} + +// Check if token was updated (for caching purposes) +if ($auth->accessTokenUpdated()) { + $tokenData = $auth->getAccessTokenData(); + // Store $tokenData['access_token'] and $tokenData['expires'] for future use +} -$auth->setCurlTimeout($timeout); +// The auth object is now ready to use with API contexts ``` ## API Requests @@ -325,9 +390,16 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Zdeno Kuzmany 💻 |
- dlopez-akalam 💻 |
- mollux 💻 |
+ Zdeno Kuzmany 💻 |
+ dlopez-akalam 💻 |
+ mollux 💻 |
+ Martina Scholz 💻 |
+ John Linhart 👀 |
+ Marinus van Velzen 💻 |
+ Pierre Ammeloot 📓 |
+
Martin Vooremäe 💻 ⚠️ |
Custom HTML content with {{contactfield=firstname}}
', +]); +``` + +**Note:** This feature requires Mautic with [PR #12854](https://github.com/mautic/mautic/pull/12854) merged. +It is not available in standard Mautic 5.x releases. + +# Version Compatibility + +| Library Version | PHP | Mautic | +|-----------------|-----|--------| +| 3.x | 7.2+ | 3.x, 4.x | +| 4.x | 8.0+ | 4.x, 5.x | diff --git a/composer.json b/composer.json index 6fbb18f4..783515b5 100644 --- a/composer.json +++ b/composer.json @@ -14,18 +14,31 @@ } }, "require": { - "php": ">=7.2.0", - "ext-curl": "*", + "php": ">=8.0", "ext-json": "*", - "psr/log": "~1.0 || ~2.0 || ~3.0" + "psr/log": "~1.0 || ~2.0 || ~3.0", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "^1.0", + "guzzlehttp/psr7": "^2.4", + "php-http/discovery": "^1.15" }, "require-dev": { "kint-php/kint": "^4.1", - "friendsofphp/php-cs-fixer": "^2.19.3", + "friendsofphp/php-cs-fixer": "^3.73", "phpunit/phpunit": "~9.5.0", - "brianium/paratest": "^6.2" + "guzzlehttp/guzzle": "^7.5", + "phpstan/phpstan": "^1.11" + }, + "suggest": { + "guzzlehttp/guzzle": "A popular HTTP client that implements psr/http-client-implementation." }, "scripts": { - "test": "vendor/bin/paratest" + "test": "vendor/bin/phpunit", + "phpstan": "vendor/bin/phpstan analyse" + }, + "config": { + "allow-plugins": { + "php-http/discovery": true + } } } diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 6347dae6..00000000 --- a/composer.lock +++ /dev/null @@ -1,4263 +0,0 @@ -{ - "_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": "786e116622ff75904afc35a26f231705", - "packages": [ - { - "name": "psr/log", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" - }, - "time": "2021-05-03T11:20:27+00:00" - } - ], - "packages-dev": [ - { - "name": "brianium/paratest", - "version": "v6.8.0", - "source": { - "type": "git", - "url": "https://github.com/paratestphp/paratest.git", - "reference": "4b70abf4c2ffa08c64e2e89c7b5b7e43cdf26d52" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/4b70abf4c2ffa08c64e2e89c7b5b7e43cdf26d52", - "reference": "4b70abf4c2ffa08c64e2e89c7b5b7e43cdf26d52", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-simplexml": "*", - "fidry/cpu-core-counter": "^0.4.1", - "jean85/pretty-package-versions": "^2.0.5", - "php": "^7.3 || ^8.0", - "phpunit/php-code-coverage": "^9.2.23", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-timer": "^5.0.3", - "phpunit/phpunit": "^9.5.27", - "sebastian/environment": "^5.1.4", - "symfony/console": "^5.4.16 || ^6.2.3", - "symfony/process": "^5.4.11 || ^6.2" - }, - "require-dev": { - "doctrine/coding-standard": "^10.0.0", - "ext-pcov": "*", - "ext-posix": "*", - "infection/infection": "^0.26.16", - "squizlabs/php_codesniffer": "^3.7.1", - "symfony/filesystem": "^5.4.13 || ^6.2", - "vimeo/psalm": "^5.4" - }, - "bin": [ - "bin/paratest", - "bin/paratest.bat", - "bin/paratest_for_phpstorm" - ], - "type": "library", - "autoload": { - "psr-4": { - "ParaTest\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Brian Scaturro", - "email": "scaturrob@gmail.com", - "role": "Developer" - }, - { - "name": "Filippo Tessarotto", - "email": "zoeslam@gmail.com", - "role": "Developer" - } - ], - "description": "Parallel testing for PHP", - "homepage": "https://github.com/paratestphp/paratest", - "keywords": [ - "concurrent", - "parallel", - "phpunit", - "testing" - ], - "support": { - "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v6.8.0" - }, - "funding": [ - { - "url": "https://github.com/sponsors/Slamdunk", - "type": "github" - }, - { - "url": "https://paypal.me/filippotessarotto", - "type": "paypal" - } - ], - "time": "2022-12-29T09:42:35+00:00" - }, - { - "name": "composer/pcre", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/67a32d7d6f9f560b726ab25a061b38ff3a80c560", - "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/1.0.1" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-01-21T20:24:37+00:00" - }, - { - "name": "composer/semver", - "version": "3.3.2", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-04-01T19:23:25+00:00" - }, - { - "name": "composer/xdebug-handler", - "version": "2.0.5", - "source": { - "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "9e36aeed4616366d2b690bdce11f71e9178c579a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/9e36aeed4616366d2b690bdce11f71e9178c579a", - "reference": "9e36aeed4616366d2b690bdce11f71e9178c579a", - "shasum": "" - }, - "require": { - "composer/pcre": "^1", - "php": "^5.3.2 || ^7.0 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/2.0.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-02-24T20:20:32+00:00" - }, - { - "name": "doctrine/annotations", - "version": "1.14.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "ad785217c1e9555a7d6c6c8c9f406395a5e2882b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/ad785217c1e9555a7d6c6c8c9f406395a5e2882b", - "reference": "ad785217c1e9555a7d6c6c8c9f406395a5e2882b", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^1 || ^2", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "~1.4.10 || ^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "vimeo/psalm": "^4.10" - }, - "suggest": { - "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.14.2" - }, - "time": "2022-12-15T06:48:22+00:00" - }, - { - "name": "doctrine/deprecations", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5|^8.5|^9.5", - "psr/log": "^1|^2|^3" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" - }, - "time": "2022-05-02T15:47:09+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9 || ^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-12-30T00:15:36+00:00" - }, - { - "name": "doctrine/lexer", - "version": "2.1.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", - "shasum": "" - }, - "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2022-12-14T08:49:07+00:00" - }, - { - "name": "fidry/cpu-core-counter", - "version": "0.4.1", - "source": { - "type": "git", - "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "79261cc280aded96d098e1b0e0ba0c4881b432c2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/79261cc280aded96d098e1b0e0ba0c4881b432c2", - "reference": "79261cc280aded96d098e1b0e0ba0c4881b432c2", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "fidry/makefile": "^0.2.0", - "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^1.9.2", - "phpstan/phpstan-deprecation-rules": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.2.2", - "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^9.5.26 || ^8.5.31", - "theofidry/php-cs-fixer-config": "^1.0", - "webmozarts/strict-phpunit": "^7.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Fidry\\CpuCoreCounter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com" - } - ], - "description": "Tiny utility to get the number of CPU cores.", - "keywords": [ - "CPU", - "core" - ], - "support": { - "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/0.4.1" - }, - "funding": [ - { - "url": "https://github.com/theofidry", - "type": "github" - } - ], - "time": "2022-12-16T22:01:02+00:00" - }, - { - "name": "friendsofphp/php-cs-fixer", - "version": "v2.19.3", - "source": { - "type": "git", - "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "75ac86f33fab4714ea5a39a396784d83ae3b5ed8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/75ac86f33fab4714ea5a39a396784d83ae3b5ed8", - "reference": "75ac86f33fab4714ea5a39a396784d83ae3b5ed8", - "shasum": "" - }, - "require": { - "composer/semver": "^1.4 || ^2.0 || ^3.0", - "composer/xdebug-handler": "^1.2 || ^2.0", - "doctrine/annotations": "^1.2", - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^5.6 || ^7.0 || ^8.0", - "php-cs-fixer/diff": "^1.3", - "symfony/console": "^3.4.43 || ^4.1.6 || ^5.0", - "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", - "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", - "symfony/finder": "^3.0 || ^4.0 || ^5.0", - "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0", - "symfony/polyfill-php70": "^1.0", - "symfony/polyfill-php72": "^1.4", - "symfony/process": "^3.0 || ^4.0 || ^5.0", - "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" - }, - "require-dev": { - "justinrainbow/json-schema": "^5.0", - "keradus/cli-executor": "^1.4", - "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^2.4.2", - "php-cs-fixer/accessible-object": "^1.0", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy-phpunit": "^1.1 || ^2.0", - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.13 || ^9.5", - "phpunitgoodpractices/polyfill": "^1.5", - "phpunitgoodpractices/traits": "^1.9.1", - "sanmai/phpunit-legacy-adapter": "^6.4 || ^8.2.1", - "symfony/phpunit-bridge": "^5.2.1", - "symfony/yaml": "^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters.", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", - "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "extra": { - "branch-alias": { - "dev-master": "2.19-dev" - } - }, - "autoload": { - "psr-4": { - "PhpCsFixer\\": "src/" - }, - "classmap": [ - "tests/Test/AbstractFixerTestCase.php", - "tests/Test/AbstractIntegrationCaseFactory.php", - "tests/Test/AbstractIntegrationTestCase.php", - "tests/Test/Assert/AssertTokensTrait.php", - "tests/Test/IntegrationCase.php", - "tests/Test/IntegrationCaseFactory.php", - "tests/Test/IntegrationCaseFactoryInterface.php", - "tests/Test/InternalIntegrationCaseFactory.php", - "tests/Test/IsIdenticalConstraint.php", - "tests/Test/TokensWithObservedTransformers.php", - "tests/TestCase.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.19.3" - }, - "funding": [ - { - "url": "https://github.com/keradus", - "type": "github" - } - ], - "time": "2021-11-15T17:17:55+00:00" - }, - { - "name": "jean85/pretty-package-versions", - "version": "2.0.5", - "source": { - "type": "git", - "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af", - "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.0.0", - "php": "^7.1|^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.17", - "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^0.12.66", - "phpunit/phpunit": "^7.5|^8.5|^9.4", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Jean85\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" - } - ], - "description": "A library to get pretty versions strings of installed dependencies", - "keywords": [ - "composer", - "package", - "release", - "versions" - ], - "support": { - "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5" - }, - "time": "2021-10-08T21:21:46+00:00" - }, - { - "name": "kint-php/kint", - "version": "4.2.3", - "source": { - "type": "git", - "url": "https://github.com/kint-php/kint.git", - "reference": "7601bfd95ccc50a1b903c2764b31d00919e8edd9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/kint-php/kint/zipball/7601bfd95ccc50a1b903c2764b31d00919e8edd9", - "reference": "7601bfd95ccc50a1b903c2764b31d00919e8edd9", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.0", - "phpspec/prophecy-phpunit": "^2", - "phpunit/phpunit": "^9.0", - "seld/phar-utils": "^1.0", - "symfony/finder": "^3.0 || ^4.0 || ^5.0", - "vimeo/psalm": "^4.0" - }, - "suggest": { - "kint-php/kint-helpers": "Provides extra helper functions", - "kint-php/kint-twig": "Provides d() and s() functions in twig templates" - }, - "type": "library", - "autoload": { - "files": [ - "init.php" - ], - "psr-4": { - "Kint\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jonathan Vollebregt", - "homepage": "https://github.com/jnvsor" - }, - { - "name": "Contributors", - "homepage": "https://github.com/kint-php/kint/graphs/contributors" - } - ], - "description": "Kint - debugging tool for PHP developers", - "homepage": "https://kint-php.github.io/kint/", - "keywords": [ - "debug", - "kint", - "php" - ], - "support": { - "issues": "https://github.com/kint-php/kint/issues", - "source": "https://github.com/kint-php/kint/tree/4.2.3" - }, - "time": "2022-10-01T20:16:33+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2022-03-03T13:19:32+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v4.15.2", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.0" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.9-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2" - }, - "time": "2022-11-12T15:38:23+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" - }, - "time": "2021-07-20T11:28:43+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "php-cs-fixer/diff", - "version": "v1.3.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "dbd31aeb251639ac0b9e7e29405c1441907f5759" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/dbd31aeb251639ac0b9e7e29405c1441907f5759", - "reference": "dbd31aeb251639ac0b9e7e29405c1441907f5759", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", - "symfony/process": "^3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "SpacePossum" - } - ], - "description": "sebastian/diff v2 backport support for PHP5.6", - "homepage": "https://github.com/PHP-CS-Fixer", - "keywords": [ - "diff" - ], - "support": { - "issues": "https://github.com/PHP-CS-Fixer/diff/issues", - "source": "https://github.com/PHP-CS-Fixer/diff/tree/v1.3.1" - }, - "abandoned": true, - "time": "2020-10-14T08:39:05+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "9.2.23", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", - "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.23" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-12-28T12:41:10+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "3.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-12-02T12:48:52+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:58:55+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T05:33:50+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "5.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:16:10+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "9.5.28", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e", - "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.3.1 || ^2", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", - "sebastian/version": "^3.0.2" - }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.5-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2023-01-14T12:32:24+00:00" - }, - { - "name": "psr/cache", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/master" - }, - "time": "2016-08-06T20:24:11+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:08:49+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "1.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:08:54+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:30:19+00:00" - }, - { - "name": "sebastian/comparator", - "version": "4.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-14T12:41:17+00:00" - }, - { - "name": "sebastian/complexity", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T15:52:27+00:00" - }, - { - "name": "sebastian/diff", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:10:38+00:00" - }, - { - "name": "sebastian/environment", - "version": "5.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-04-03T09:37:03+00:00" - }, - { - "name": "sebastian/exporter", - "version": "4.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-14T06:03:37+00:00" - }, - { - "name": "sebastian/global-state", - "version": "5.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-02-14T08:28:10+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-28T06:42:11+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:12:34+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:14:26+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:17:30+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:45:17+00:00" - }, - { - "name": "sebastian/type", - "version": "3.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-12T14:47:03+00:00" - }, - { - "name": "sebastian/version", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:39:44+00:00" - }, - { - "name": "symfony/console", - "version": "v5.4.17", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f", - "reference": "58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" - }, - "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0" - }, - "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "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": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v5.4.17" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-12-28T14:15:31+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.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": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:53:40+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v5.4.17", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8e18a9d559eb8ebc2220588f1faa726a2fcd31c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e18a9d559eb8ebc2220588f1faa726a2fcd31c9", - "reference": "8e18a9d559eb8ebc2220588f1faa726a2fcd31c9", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher-contracts": "^2|^3", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/dependency-injection": "<4.4" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/stopwatch": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.17" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-12-12T15:54:21+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/event-dispatcher": "^1" - }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:53:40+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v5.4.13", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "ac09569844a9109a5966b9438fc29113ce77cf51" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/ac09569844a9109a5966b9438fc29113ce77cf51", - "reference": "ac09569844a9109a5966b9438fc29113ce77cf51", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "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": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.13" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-09-21T19:53:16+00:00" - }, - { - "name": "symfony/finder", - "version": "v5.4.17", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "40c08632019838dfb3350f18cf5563b8080055fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/40c08632019838dfb3350f18cf5563b8080055fc", - "reference": "40c08632019838dfb3350f18cf5563b8080055fc", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "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": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.17" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-12-22T10:31:03+00:00" - }, - { - "name": "symfony/options-resolver", - "version": "v5.4.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/54f14e36aa73cb8f7261d7686691fd4d75ea2690", - "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "~1.0", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "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": "Provides an improved replacement for the array_replace PHP function", - "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], - "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.4.11" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-20T13:00:38+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-php70", - "version": "v1.20.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644", - "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "metapackage", - "extra": { - "branch-alias": { - "dev-main": "1.20-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php70/tree/v1.20.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-23T14:02:19+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-php73", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/process", - "version": "v5.4.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1", - "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "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": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v5.4.11" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-06-27T16:58:25+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-30T19:17:29+00:00" - }, - { - "name": "symfony/stopwatch", - "version": "v5.4.13", - "source": { - "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "6df7a3effde34d81717bbef4591e5ffe32226d69" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6df7a3effde34d81717bbef4591e5ffe32226d69", - "reference": "6df7a3effde34d81717bbef4591e5ffe32226d69", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1|^2|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" - }, - "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": "Provides a way to profile code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.13" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-09-28T13:19:49+00:00" - }, - { - "name": "symfony/string", - "version": "v5.4.17", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "55733a8664b8853b003e70251c58bc8cb2d82a6b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/55733a8664b8853b003e70251c58bc8cb2d82a6b", - "reference": "55733a8664b8853b003e70251c58bc8cb2d82a6b", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" - }, - "conflict": { - "symfony/translation-contracts": ">=3.0" - }, - "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v5.4.17" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-12-12T15:54:21+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2021-07-28T10:34:58+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=7.2.0", - "ext-curl": "*", - "ext-json": "*" - }, - "platform-dev": [], - "plugin-api-version": "2.2.0" -} diff --git a/lib/Api/Api.php b/lib/Api/Api.php index 01b3825f..ed351c51 100644 --- a/lib/Api/Api.php +++ b/lib/Api/Api.php @@ -1,4 +1,5 @@ logger; } - /** - * Sets a logger. - * - * @return $this - */ - public function setLogger(LoggerInterface $logger) - { - $this->logger = $logger; - - return $this; - } - /** * Get the array of available search commands. * @@ -198,7 +185,7 @@ public function setBaseUrl($url) /** * Make the API request. * - * @param $endpoint + * @param string $endpoint * @param string $method * * @return array @@ -244,12 +231,12 @@ public function makeRequest($endpoint, array $parameters = [], $method = 'GET') if (false === strpos($url, 'http')) { $error = [ - 'code' => 500, - 'message' => sprintf( - 'URL is incomplete. Please use %s, set the base URL as the third argument to $MauticApi->newApi(), or make $endpoint a complete URL.', - __CLASS__.'setBaseUrl()' - ), - ]; + 'code' => 500, + 'message' => sprintf( + 'URL is incomplete. Please use %s, set the base URL as the third argument to $MauticApi->newApi(), or make $endpoint a complete URL.', + __CLASS__.'setBaseUrl()' + ), + ]; } else { try { $settings = []; @@ -263,36 +250,35 @@ public function makeRequest($endpoint, array $parameters = [], $method = 'GET') if (!is_array($response)) { $this->getLogger()->warning($response); - //assume an error + // assume an error $error = [ - 'code' => 500, - 'message' => $response, - ]; + 'code' => 500, + 'message' => $response, + ]; } } catch (\Exception $e) { $this->getLogger()->error('Failed connecting to Mautic API: '.$e->getMessage(), ['trace' => $e->getTraceAsString()]); $error = [ - 'code' => $e->getCode(), - 'message' => $e->getMessage(), - ]; + 'code' => $e->getCode(), + 'message' => $e->getMessage(), + ]; } } if (!empty($error)) { return [ - 'errors' => [$error], - ]; + 'errors' => [$error], + ]; } elseif (!empty($response['errors'])) { $this->getLogger()->error('Mautic API returned errors: '.var_export($response['errors'], true)); } // Ensure a code is present in the error array if (!empty($response['errors'])) { - $info = $this->auth->getResponseInfo(); foreach ($response['errors'] as $key => $error) { if (!isset($response['errors'][$key]['code'])) { - $response['errors'][$key]['code'] = $info['http_code']; + $response['errors'][$key]['code'] = $this->auth->getResponse()->getStatusCode(); } } } @@ -308,16 +294,6 @@ public function makeRequest($endpoint, array $parameters = [], $method = 'GET') return $response; } - /** - * Returns HTTP response info. - * - * @return array - */ - public function getResponseInfo() - { - return $this->auth->getResponseInfo(); - } - /** * Returns HTTP response headers. * @@ -336,10 +312,10 @@ public function getResponseHeaders() */ public function getMauticVersion() { - $headers = $this->auth->getResponseHeaders(); + $headers = array_change_key_case($this->auth->getResponseHeaders(), CASE_LOWER); - if (isset($headers['Mautic-Version'])) { - return $headers['Mautic-Version']; + if (isset($headers['mautic-version'])) { + return $headers['mautic-version']; } return null; @@ -358,7 +334,7 @@ public function get($id) } /** - * @param $id + * @param int $id * * @return array|bool */ @@ -496,7 +472,7 @@ public function editBatch(array $parameters, $createIfNotExists = false) /** * Delete an item. * - * @param $id + * @param int $id * * @return array|mixed */ @@ -510,8 +486,6 @@ public function delete($id) /** * Delete a batch of items. * - * @param $ids - * * @return array|mixed */ public function deleteBatch(array $ids) @@ -543,7 +517,7 @@ protected function actionNotSupported($action) /** * Verify that a default endpoint is supported by the API. * - * @param $action + * @param string $action * * @return bool */ diff --git a/lib/Api/Assets.php b/lib/Api/Assets.php index 3a7e5285..baffe5d9 100644 --- a/lib/Api/Assets.php +++ b/lib/Api/Assets.php @@ -1,4 +1,5 @@ 'campaigns/$1/contact/remove/$2', // 2.6.0 ]; - /** - * {@inheritdoc} - */ protected $searchCommands = [ 'ids', 'is:published', diff --git a/lib/Api/Categories.php b/lib/Api/Categories.php index bcf9cebc..5660a698 100644 --- a/lib/Api/Categories.php +++ b/lib/Api/Categories.php @@ -1,4 +1,5 @@ 'companies/$1/contact/remove/$2', // 2.6.0 ]; - /** - * {@inheritdoc} - */ protected $searchCommands = [ 'ids', 'is:mine', diff --git a/lib/Api/CompanyFields.php b/lib/Api/CompanyFields.php index 9fcd39ce..2c479905 100644 --- a/lib/Api/CompanyFields.php +++ b/lib/Api/CompanyFields.php @@ -1,4 +1,5 @@ 'contacts/$1/dnc/remove/$2', // 2.6.0 ]; - /** - * {@inheritdoc} - */ protected $searchCommands = [ 'ids', 'is:anonymous', @@ -121,13 +110,11 @@ public function getSegments() /** * Get a list of contact activity events for all contacts. * - * @param int $id Contact ID - * @param string $search - * @param string $orderBy - * @param string $orderByDir - * @param int $page - * @param \DateTime $dateFrom - * @param \DateTime $dateTo + * @param int $id Contact ID + * @param string $search + * @param string $orderBy + * @param string $orderByDir + * @param int $page * * @return array|mixed */ @@ -139,8 +126,8 @@ public function getActivityForContact( $orderBy = '', $orderByDir = 'ASC', $page = 1, - \DateTime $dateFrom = null, - \DateTime $dateTo = null + ?\DateTime $dateFrom = null, + ?\DateTime $dateTo = null, ) { return $this->fetchActivity('/'.$id.'/activity', $search, $includeEvents, $excludeEvents, $orderBy, $orderByDir, $page, $dateFrom, $dateTo); } @@ -149,12 +136,10 @@ public function getActivityForContact( * Get a list of contact engagement events. * Not related to a specific contact ID. * - * @param string $search - * @param string $orderBy - * @param string $orderByDir - * @param int $page - * @param \DateTime $dateFrom - * @param \DateTime $dateTo + * @param string $search + * @param string $orderBy + * @param string $orderByDir + * @param int $page * * @return array|mixed */ @@ -165,8 +150,8 @@ public function getActivity( $orderBy = '', $orderByDir = 'ASC', $page = 1, - \DateTime $dateFrom = null, - \DateTime $dateTo = null + ?\DateTime $dateFrom = null, + ?\DateTime $dateTo = null, ) { return $this->fetchActivity('/activity', $search, $includeEvents, $excludeEvents, $orderBy, $orderByDir, $page, $dateFrom, $dateTo); } @@ -174,13 +159,11 @@ public function getActivity( /** * Get a list of contact activity events for all contacts. * - * @param string $path of the URL after the endpoint - * @param string $search - * @param string $orderBy - * @param string $orderByDir - * @param int $page - * @param \DateTime $dateFrom - * @param \DateTime $dateTo + * @param string $path of the URL after the endpoint + * @param string $search + * @param string $orderBy + * @param string $orderByDir + * @param int $page * * @return array|mixed */ @@ -192,8 +175,8 @@ protected function fetchActivity( $orderBy = '', $orderByDir = 'ASC', $page = 1, - \DateTime $dateFrom = null, - \DateTime $dateTo = null + ?\DateTime $dateFrom = null, + ?\DateTime $dateTo = null, ) { $parameters = [ 'filters' => [ @@ -276,7 +259,7 @@ public function getContactDevices($id, $search = '', $start = 0, $limit = 0, $or /** * Get a list of smart segments the contact is in. * - * @param $id + * @param int $id * * @return array|mixed */ @@ -288,7 +271,7 @@ public function getContactSegments($id) /** * Get a list of companies the contact is in. * - * @param $id + * @param int $id * * @return array|mixed */ @@ -300,7 +283,7 @@ public function getContactCompanies($id) /** * Get a list of campaigns the contact is in. * - * @param $id + * @param int $id * * @return array|mixed */ @@ -315,8 +298,6 @@ public function getContactCampaigns($id) * @param int $id * @param int $points * @param array $parameters 'eventName' and 'actionName' - * - * @return mixed */ public function addPoints($id, $points, array $parameters = []) { @@ -329,14 +310,78 @@ public function addPoints($id, $points, array $parameters = []) * @param int $id * @param int $points * @param array $parameters 'eventName' and 'actionName' - * - * @return mixed */ public function subtractPoints($id, $points, array $parameters = []) { return $this->makeRequest('contacts/'.$id.'/points/minus/'.$points, $parameters, 'POST'); } + /** + * Get all point group scores associated with contact. + */ + public function getPointGroupScores(int $contactId): array + { + return $this->makeRequest('contacts/'.$contactId.'/points/groups'); + } + + /** + * Get the contact score for a specified point group. + */ + public function getPointGroupScore(int $contactId, int $groupId): array + { + return $this->makeRequest('contacts/'.$contactId.'/points/groups/'.$groupId); + } + + /** + * Increase the score of the contact point group. + * + * @param array $parameters 'eventName' and 'actionName' + */ + public function addPointGroupScore(int $contactId, int $groupId, int $points, array $parameters = []): array + { + return $this->makeRequest('contacts/'.$contactId.'/points/groups/'.$groupId.'/plus/'.$points, $parameters, 'POST'); + } + + /** + * Decrease the score of the contact point group. + * + * @param array $parameters 'eventName' and 'actionName' + */ + public function subtractPointGroupScore(int $contactId, int $groupId, int $points, array $parameters = []): array + { + return $this->makeRequest('contacts/'.$contactId.'/points/groups/'.$groupId.'/minus/'.$points, $parameters, 'POST'); + } + + /** + * Multiply the score of the contact point group. + * + * @param array $parameters 'eventName' and 'actionName' + */ + public function multiplyPointGroupScore(int $contactId, int $groupId, int $value, array $parameters = []): array + { + return $this->makeRequest('contacts/'.$contactId.'/points/groups/'.$groupId.'/times/'.$value, $parameters, 'POST'); + } + + /** + * Divide the score of the contact point group. + * + * @param array $parameters 'eventName' and 'actionName' + */ + public function dividePointGroupScore(int $contactId, int $groupId, int $value, array $parameters = []): array + { + return $this->makeRequest('contacts/'.$contactId.'/points/groups/'.$groupId.'/divide/'.$value, $parameters, 'POST'); + } + + /** + * Set the score of the contact point group. + * + * @param array $parameters 'eventName' and 'actionName' + */ + public function setPointGroupScore(int $contactId, int $groupId, int $points, array $parameters = []): array + { + return $this->makeRequest('contacts/'.$contactId.'/points/groups/'.$groupId.'/set/'.$points, $parameters, 'POST'); + } + /** * Adds Do Not Contact. * @@ -366,8 +411,6 @@ public function addDnc($id, $channel = 'email', $reason = Contacts::MANUAL, $cha * * @param int $id * @param string $channel - * - * @return mixed */ public function removeDnc($id, $channel = 'email') { @@ -383,8 +426,6 @@ public function removeDnc($id, $channel = 'email') * * @param int $id * @param array $utmTags - * - * @return mixed */ public function addUtm($id, $utmTags) { @@ -396,7 +437,28 @@ public function addUtm($id, $utmTags) } /** - * Create a new item (if supported). + * Remove UTM Tags from a Contact. + * + * @param int $id + * @param int $utmId + */ + public function removeUtm($id, $utmId) + { + return $this->makeRequest( + 'contacts/'.$id.'/utm/'.$utmId.'/remove', + [], + 'POST' + ); + } + + /** + * Create a new contact. + * + * Overrides parent to support additional query arguments for plugins + * like Custom Objects (e.g., ['includeCustomObjects' => true]). + * + * @param array $parameters Contact data + * @param array $queryArguments Optional query parameters (e.g., for Custom Objects) * * @return array|mixed */ @@ -411,7 +473,13 @@ public function create(array $parameters, array $queryArguments = []) } /** - * Create a batch of new items. + * Create a batch of new contacts. + * + * Overrides parent to support additional query arguments for plugins + * like Custom Objects (e.g., ['includeCustomObjects' => true]). + * + * @param array $parameters Array of contact data + * @param array $queryArguments Optional query parameters (e.g., for Custom Objects) * * @return array|mixed */ @@ -424,21 +492,4 @@ public function createBatch(array $parameters, array $queryArguments = []) ? $this->makeRequest($this->endpoint.'/batch/new'.$queryAppend, $parameters, 'POST') : $supported; } - - /** - * Remove UTM Tags from a Contact. - * - * @param int $id - * @param int $utmId - * - * @return mixed - */ - public function removeUtm($id, $utmId) - { - return $this->makeRequest( - 'contacts/'.$id.'/utm/'.$utmId.'/remove', - [], - 'POST' - ); - } } diff --git a/lib/Api/Data.php b/lib/Api/Data.php index b1959b77..5d21504f 100644 --- a/lib/Api/Data.php +++ b/lib/Api/Data.php @@ -1,4 +1,5 @@ makeRequest("{$this->endpoint}/$id", $options); } - /** - * {@inheritdoc} - */ public function getPublishedList($search = '', $start = 0, $limit = 0, $orderBy = '', $orderByDir = 'ASC') { return $this->actionNotSupported(__FUNCTION__); } - /** - * {@inheritdoc} - */ public function create(array $parameters) { return $this->actionNotSupported(__FUNCTION__); } - /** - * {@inheritdoc} - */ public function edit($id, array $parameters, $createIfNotExists = false) { return $this->actionNotSupported(__FUNCTION__); } - /** - * {@inheritdoc} - */ public function delete($id) { return $this->actionNotSupported(__FUNCTION__); diff --git a/lib/Api/Devices.php b/lib/Api/Devices.php index a7af1266..b14a990a 100644 --- a/lib/Api/Devices.php +++ b/lib/Api/Devices.php @@ -1,4 +1,5 @@ 'emails/$1/send/contact/$2', // 2.6.0 ]; - /** - * {@inheritdoc} - */ protected $searchCommands = [ 'ids', 'is:published', @@ -89,4 +78,32 @@ public function sendToLead($id, $leadId) { return $this->sendToContact($id, $leadId); } + + /** + * Send a custom email to a specific contact. + * + * This allows sending an email with custom content directly to a contact + * without using a pre-defined email template. + * + * Required data parameters: + * - fromEmail: Sender email address + * - subject: Email subject + * - content: Email body (HTML) + * + * Optional data parameters: + * - fromName: Sender name + * - replyToEmail: Reply-to email address + * - replyToName: Reply-to name + * + * Note: This endpoint requires Mautic with PR #12854 merged (not in standard Mautic 5.x). + * + * @param int $contactId The ID of the contact to send the email to + * @param array $data email data array with keys: fromEmail, subject, content, etc + * + * @return array Response with 'success' and 'trackingHash' on success + */ + public function sendCustomToContact(int $contactId, array $data = []): array + { + return $this->makeRequest($this->endpoint.'/contact/'.$contactId.'/send/custom', $data, 'POST'); + } } diff --git a/lib/Api/Files.php b/lib/Api/Files.php index d22ad01e..f97e25c8 100644 --- a/lib/Api/Files.php +++ b/lib/Api/Files.php @@ -1,4 +1,5 @@ endpoint = 'files/'.$folder; } - /** - * {@inheritdoc} - */ public function edit($id, array $parameters, $createIfNotExists = false) { return $this->actionNotSupported('edit'); @@ -61,25 +50,16 @@ public function create(array $parameters) return parent::create($parameters); } - /** - * {@inheritdoc} - */ public function createBatch(array $parameters) { return $this->actionNotSupported('createBatch'); } - /** - * {@inheritdoc} - */ public function editBatch(array $parameters, $createIfNotExists = false) { return $this->actionNotSupported('editBatch'); } - /** - * {@inheritdoc} - */ public function deleteBatch(array $ids) { return $this->actionNotSupported('deleteBatch'); diff --git a/lib/Api/Focus.php b/lib/Api/Focus.php index e09ac8a7..7258c74e 100644 --- a/lib/Api/Focus.php +++ b/lib/Api/Focus.php @@ -1,4 +1,5 @@ 'stages/$1/contact/remove/$2', // 2.6.0 ]; - /** - * {@inheritdoc} - */ protected $searchCommands = [ 'ids', ]; diff --git a/lib/Api/Stats.php b/lib/Api/Stats.php index d1576c24..ee48218c 100644 --- a/lib/Api/Stats.php +++ b/lib/Api/Stats.php @@ -1,4 +1,5 @@ makeRequest($endpoint, $parameters); } - /** - * {@inheritdoc} - */ public function delete($id) { return $this->actionNotSupported('delete'); } - /** - * {@inheritdoc} - */ public function getList($search = '', $start = 0, $limit = 0, $orderBy = '', $orderByDir = 'ASC', $publishedOnly = false, $minimal = false) { return $this->actionNotSupported('getList'); } - /** - * {@inheritdoc} - */ public function create(array $parameters) { return $this->actionNotSupported('create'); } - /** - * {@inheritdoc} - */ public function getPublishedList($search = '', $start = 0, $limit = 0, $orderBy = '', $orderByDir = 'ASC') { return $this->actionNotSupported('getPublishedList'); } - /** - * {@inheritdoc} - */ public function edit($id, array $parameters, $createIfNotExists = false) { return $this->actionNotSupported('edit'); diff --git a/lib/Api/Tags.php b/lib/Api/Tags.php index 9267ec36..fa99bdaa 100644 --- a/lib/Api/Tags.php +++ b/lib/Api/Tags.php @@ -1,4 +1,5 @@ actionNotSupported('edit'); @@ -52,25 +41,16 @@ public function create(array $parameters) return parent::create($parameters); } - /** - * {@inheritdoc} - */ public function createBatch(array $parameters) { return $this->actionNotSupported('createBatch'); } - /** - * {@inheritdoc} - */ public function editBatch(array $parameters, $createIfNotExists = false) { return $this->actionNotSupported('editBatch'); } - /** - * {@inheritdoc} - */ public function deleteBatch(array $ids) { return $this->actionNotSupported('deleteBatch'); diff --git a/lib/Api/Tweets.php b/lib/Api/Tweets.php index 23bcb26f..c80660cd 100644 --- a/lib/Api/Tweets.php +++ b/lib/Api/Tweets.php @@ -1,4 +1,5 @@ client = $client; + } /** - * @param $url - * @param $method - * - * @return mixed + * @param string $url + * @param string $method */ abstract protected function prepareRequest($url, array $headers, array $parameters, $method, array $settings); @@ -84,161 +76,108 @@ public function getDebugInfo() */ public function getResponseHeaders() { - return $this->parseHeaders($this->_httpResponseHeaders); + return array_map( + static function ($values) { + return $values[0]; + }, + $this->_httpResponse->getHeaders() + ); } /** - * Returns array of HTTP response headers. + * Returns the HTTP response. * - * @return array + * @return ResponseInterface */ - public function getResponseInfo() + public function getResponse() { - return $this->_httpResponseInfo; + return $this->_httpResponse; } /** - * {@inheritdoc} - * * @throws UnexpectedResponseFormatException|Exception */ public function makeRequest($url, array $parameters = [], $method = 'GET', array $settings = []) { $this->log('makeRequest('.$url.', '.http_build_query($parameters).', '.$method.',...)'); - if ($method === 'GET') { // We want to keep get arguments for non GET - list($url, $parameters) = $this->separateUrlParams($url, $parameters); + if ('GET' === $method) { // We want to keep get arguments for non-GET requests + [$url, $parameters] = $this->separateUrlParams($url, $parameters); } - //make sure $method is capitalized for congruency + // Make sure $method is capitalized for congruency $method = strtoupper($method); $headers = (isset($settings['headers']) && is_array($settings['headers'])) ? $settings['headers'] : []; - list($headers, $parameters) = $this->prepareRequest($url, $headers, $parameters, $method, $settings); + [$headers, $parameters] = $this->prepareRequest($url, $headers, $parameters, $method, $settings); - //Set default CURL options - $options = [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_SSL_VERIFYPEER => false, - CURLOPT_HEADER => true, - ]; - - if (null !== $this->_curlTimeout) { - $options[CURLOPT_TIMEOUT] = $this->_curlTimeout; - } - - // CURLOPT_FOLLOWLOCATION cannot be activated when an open_basedir is set - $options[CURLOPT_FOLLOWLOCATION] = (ini_get('open_basedir')) ? false : true; - - //Set custom REST method if not GET or POST - if (!in_array($method, ['GET', 'POST'])) { - $options[CURLOPT_CUSTOMREQUEST] = $method; - } - - //Set post fields for POST/PUT/PATCH requests - $isPost = false; + // Prepare parameters/body + $body = null; if (in_array($method, ['POST', 'PUT', 'PATCH'])) { - $isPost = true; - // Set file to upload - // Sending file data requires an array to set - // the Content-Type header to multipart/form-data if (!empty($parameters['file']) && file_exists($parameters['file'])) { - $options[CURLOPT_INFILESIZE] = filesize($parameters['file']); - $parameters['file'] = $this->createCurlFile($parameters['file']); - $headers[] = 'Content-Type: multipart/form-data'; + $elements = []; + foreach ($parameters as $key => $value) { + $elements[] = [ + 'name' => $key, + 'contents' => 'file' === $key ? Utils::tryFopen($value, 'r+') : $value, + ]; + } + + $body = new MultipartStream($elements); + $headers[] = 'Content-Type: multipart/form-data; boundary='.$body->getBoundary(); } else { - $parameters = json_encode($parameters); - $headers[] = 'Content-Type: application/json'; + $body = Utils::streamFor(json_encode($parameters)); + $headers[] = 'Content-Type: application/json'; } - $options[CURLOPT_POST] = true; - $options[CURLOPT_POSTFIELDS] = $parameters; - $this->log('Posted parameters = '.print_r($parameters, true)); } - $query = $this->getQueryParameters($isPost, $parameters); + $query = $this->getQueryParameters(null !== $body, $parameters); $this->log('Query parameters = '.print_r($query, true)); - //Create a query string for GET/DELETE requests + // Create a query string for GET/DELETE requests if (count($query) > 0) { $queryGlue = false === strpos($url, '?') ? '?' : '&'; - $url = $url.$queryGlue.http_build_query($query, '', '&'); + $url .= $queryGlue.http_build_query($query, '', '&'); $this->log('URL updated to '.$url); } - // Set the URL - $options[CURLOPT_URL] = $url; - - $headers[] = 'Accept: application/json'; - $options[CURLOPT_HTTPHEADER] = $headers; + $headers[] = 'Accept: application/json'; - //Make CURL request - $curl = curl_init(); - curl_setopt_array($curl, $options); - - $response = new Response(curl_exec($curl), curl_getinfo($curl)); + // Build request + $request = new Request($method, $url); + foreach ($headers as $header) { + [$name, $value] = explode(':', $header, 2); + $request = $request->withHeader(trim($name), trim($value)); + } + if ($body) { + $request = $request->withBody($body); + } - $this->_httpResponseHeaders = $response->getHeaders(); - $this->_httpResponseInfo = $response->getInfo(); + // Send request + $this->_httpResponse = $this->client->sendRequest($request); - curl_close($curl); + // Parse response + $response = new Response($this->_httpResponse); if ($this->_debug) { - $_SESSION['oauth']['debug']['info'] = $response->getInfo(); - $_SESSION['oauth']['debug']['returnedHeaders'] = $response->getHeaders(); - $_SESSION['oauth']['debug']['returnedBody'] = $response->getBody(); + $_SESSION['oauth']['debug']['returnedStatusCode'] = $response->getStatusCode(); + $_SESSION['oauth']['debug']['returnedHeaders'] = $response->getHeaders(); + $_SESSION['oauth']['debug']['returnedBody'] = $response->getBody(); } // Handle zip file response if ($response->isZip()) { - $temporaryFilePath = isset($settings['temporaryFilePath']) ? $settings['temporaryFilePath'] : sys_get_temp_dir(); - - return $response->saveToFile($temporaryFilePath); - } else { - return $response->getDecodedBody(); - } - } - - /** - * @deprecated 2.6.0 to be removed in 3.0; use createCurlFile instead - * - * @param $filename - * @param string $mimetype - * @param string $postname - * - * @return \CURLFile|string - */ - protected function crateCurlFile($filename, $mimetype = '', $postname = '') - { - return $this->createCurlFile($filename, $mimetype, $postname); - } - - /** - * Build the CURL file based on PHP version. - * - * @param string $filename - * @param string $mimetype - * @param string $postname - * - * @return string|\CURLFile - */ - protected function createCurlFile($filename, $mimetype = '', $postname = '') - { - if (!function_exists('curl_file_create')) { - // For PHP < 5.5 - return "@$filename;filename=" - .($postname ?: basename($filename)) - .($mimetype ? ";type=$mimetype" : ''); + return $response->saveToFile($settings['temporaryFilePath'] ?? sys_get_temp_dir()); } - // For PHP >= 5.5 - return curl_file_create($filename, $mimetype, $postname); + return $response->getDecodedBody(); } /** - * @param $isPost - * @param $parameters + * @param bool $isPost + * @param array $parameters * * @return array */ @@ -257,35 +196,11 @@ protected function log($message) } } - /** - * Build the HTTP response array out of the headers string. - * - * @param string $headersStr - * - * @return array - */ - protected function parseHeaders($headersStr) - { - $headersArr = []; - $headersHlpr = explode("\r\n", $headersStr); - - foreach ($headersHlpr as $header) { - $pos = strpos($header, ':'); - if (false === $pos) { - $headersArr[] = trim($header); - } else { - $headersArr[trim(substr($header, 0, $pos))] = trim(substr($header, ($pos + 1))); - } - } - - return $headersArr; - } - /** * Separates parameters from base URL. * - * @param $url - * @param $params + * @param string $url + * @param array $params * * @return array */ @@ -306,14 +221,4 @@ protected function separateUrlParams($url, $params) return [$url, $params]; } - - /** - * Set the timeout for a cURL request. - * - * @param int $timeout - */ - public function setCurlTimeout($timeout) - { - $this->_curlTimeout = $timeout; - } } diff --git a/lib/Auth/ApiAuth.php b/lib/Auth/ApiAuth.php index 3c55dd43..044bf58b 100644 --- a/lib/Auth/ApiAuth.php +++ b/lib/Auth/ApiAuth.php @@ -11,11 +11,21 @@ namespace Mautic\Auth; +use Http\Discovery\Psr18ClientDiscovery; +use Psr\Http\Client\ClientInterface; + /** * OAuth Client modified from https://code.google.com/p/simple-php-oauth/. */ class ApiAuth { + protected ClientInterface $client; + + public function __construct(?ClientInterface $client = null) + { + $this->client = $client ?: Psr18ClientDiscovery::find(); + } + /** * Get an API Auth object. * @@ -44,7 +54,7 @@ public static function initiate($parameters = [], $authMethod = 'OAuth') public function newAuth($parameters = [], $authMethod = 'OAuth') { $class = 'Mautic\\Auth\\'.$authMethod; - $authObject = new $class(); + $authObject = new $class($this->client); $reflection = new \ReflectionMethod($class, 'setup'); $pass = []; diff --git a/lib/Auth/AuthInterface.php b/lib/Auth/AuthInterface.php index 92f72af4..4924f8f4 100644 --- a/lib/Auth/AuthInterface.php +++ b/lib/Auth/AuthInterface.php @@ -11,6 +11,8 @@ namespace Mautic\Auth; +use Psr\Http\Client\ClientInterface; + interface AuthInterface { /** diff --git a/lib/Auth/BasicAuth.php b/lib/Auth/BasicAuth.php index 781aff05..b7d6f6f1 100644 --- a/lib/Auth/BasicAuth.php +++ b/lib/Auth/BasicAuth.php @@ -74,9 +74,6 @@ class BasicAuth extends AbstractAuth */ private $userName; - /** - * {@inheritdoc} - */ public function isAuthorized() { return !empty($this->userName) && !empty($this->password); @@ -97,7 +94,7 @@ public function setup($userName, $password) $password = trim($password); if (empty($userName) || empty($password)) { - //Throw exception if the required parameters were not found + // Throw exception if the required parameters were not found $this->log('parameters did not include username and/or password'); throw new RequiredParameterMissingException('One or more required parameters was not supplied. Both userName and password required!'); } @@ -107,14 +104,14 @@ public function setup($userName, $password) } /** - * @param $url - * @param $method + * @param string $url + * @param string $method * * @return array */ protected function prepareRequest($url, array $headers, array $parameters, $method, array $settings) { - //Set Basic Auth parameters/headers + // Set Basic Auth parameters/headers $headers = array_merge($headers, [$this->buildAuthorizationHeader(), 'Expect:']); return [$headers, $parameters]; diff --git a/lib/Auth/OAuth.php b/lib/Auth/OAuth.php index 1ea8ca9a..36d30899 100755 --- a/lib/Auth/OAuth.php +++ b/lib/Auth/OAuth.php @@ -155,39 +155,16 @@ public function getAccessTokenData() ]; } - /** - * Returns array of HTTP response headers. - * - * @return array - */ - public function getResponseHeaders() - { - return $this->parseHeaders($this->_httpResponseHeaders); - } - - /** - * Returns array of HTTP response headers. - * - * @return array - */ - public function getResponseInfo() - { - return $this->_httpResponseInfo; - } - - /** - * {@inheritdoc} - */ public function isAuthorized() { - //Check for existing access token + // Check for existing access token if (!empty($this->_request_token_url)) { if (strlen($this->_access_token) > 0 && strlen($this->_access_token_secret) > 0) { return true; } } - //Check to see if token in session has expired + // Check to see if token in session has expired if (!empty($this->_expires) && $this->_expires < time()) { return false; } @@ -217,7 +194,7 @@ public function setAccessTokenDetails(array $accessTokenDetails) /** * Set access token URL. * - * @param $url + * @param string $url * * @return $this */ @@ -231,7 +208,7 @@ public function setAccessTokenUrl($url) /** * Set authorization URL. * - * @param $url + * @param string $url * * @return $this */ @@ -245,7 +222,7 @@ public function setAuthorizeUrl($url) /** * Set redirect type for OAuth2. * - * @param $type + * @param string $type * * @return $this */ @@ -259,7 +236,7 @@ public function setRedirectType($type) /** * Set request token URL. * - * @param $url + * @param string $url * * @return $this */ @@ -310,7 +287,7 @@ public function setup( $accessTokenExpires = null, $callback = null, $scope = null, - $refreshToken = null + $refreshToken = null, ) { $this->_client_id = $clientKey; $this->_client_secret = $clientSecret; @@ -369,50 +346,50 @@ public function validateAccessToken($redirect = true) $this->_do_not_redirect = !$redirect; $this->log('validateAccessToken()'); - //Check to see if token in session has expired + // Check to see if token in session has expired if (!empty($this->_expires) && $this->_expires < time()) { $this->log('access token expired so reauthorize'); if (strlen($this->_refresh_token) > 0) { - //use a refresh token to get a new token + // use a refresh token to get a new token return $this->requestAccessToken(); } - //Reauthorize + // Reauthorize $this->authorize($this->_scope); return false; } - //Check for existing access token - if (strlen($this->_access_token) > 0) { + // Check for existing access token + if ($this->_access_token) { $this->log('has access token'); return true; } - //Reauthorize if no token was found - if (0 == strlen($this->_access_token)) { + // Reauthorize if no token was found + if (!$this->_access_token) { $this->log('access token empty so authorize'); - //OAuth flows + // OAuth flows if ($this->isOauth1()) { - //OAuth 1.0 + // OAuth 1.0 $this->log('authorizing with OAuth1.0a spec'); - //Request token and authorize app + // Request token and authorize app if (!isset($_GET['oauth_token']) && !isset($_GET['oauth_verifier'])) { $this->log('initializing authorization'); - //Request token + // Request token $this->requestToken(); - //Authorize token + // Authorize token $this->authorize(); return false; } - //Request access token + // Request access token if ($_GET['oauth_token'] != $_SESSION['oauth']['token']) { unset($_SESSION['oauth']['token'], $_SESSION['oauth']['token_secret']); @@ -425,10 +402,10 @@ public function validateAccessToken($redirect = true) return true; } - //OAuth 2.0 + // OAuth 2.0 $this->log('authorizing with OAuth2 spec'); - //Authorize app + // Authorize app if (!isset($_GET['state']) && !isset($_GET['code'])) { $this->authorize($this->_scope); @@ -439,7 +416,7 @@ public function validateAccessToken($redirect = true) $_SESSION['oauth']['debug']['received_state'] = $_GET['state']; } - //Request an access token + // Request an access token if ($_GET['state'] != $_SESSION['oauth']['state']) { unset($_SESSION['oauth']['state']); @@ -467,16 +444,16 @@ protected function authorize(array $scope = [], $scope_separator = ',', $attach { $authUrl = $this->_authorize_url; - //Build authorization URL + // Build authorization URL if ($this->isOauth1()) { - //OAuth 1.0 + // OAuth 1.0 $authUrl .= '?oauth_token='.$_SESSION['oauth']['token']; if (!empty($this->_callback)) { $authUrl .= '&oauth_callback='.urlencode($this->_callback); } } else { - //OAuth 2.0 + // OAuth 2.0 $authUrl .= '?client_id='.$this->_client_id.'&redirect_uri='.urlencode($this->_callback); $state = md5(time().mt_rand()); $_SESSION['oauth']['state'] = $state; @@ -490,7 +467,7 @@ protected function authorize(array $scope = [], $scope_separator = ',', $attach $this->log('redirecting to auth url '.$authUrl); - //Redirect to authorization URL + // Redirect to authorization URL if (!$this->_do_not_redirect) { header('Location: '.$authUrl); exit; @@ -500,8 +477,8 @@ protected function authorize(array $scope = [], $scope_separator = ',', $attach } /** - * @param $isPost - * @param $parameters + * @param bool $isPost + * @param array $parameters * * @return array */ @@ -522,36 +499,12 @@ protected function getQueryParameters($isPost, $parameters) */ protected function isOauth1() { - return !empty($this->_request_token_url) && strlen($this->_request_token_url) > 0; - } - - /** - * Build the HTTP response array out of the headers string. - * - * @param string $headersStr - * - * @return array - */ - protected function parseHeaders($headersStr) - { - $headersArr = []; - $headersHlpr = explode("\r\n", $headersStr); - - foreach ($headersHlpr as $header) { - $pos = strpos($header, ':'); - if (false === $pos) { - $headersArr[] = trim($header); - } else { - $headersArr[trim(substr($header, 0, $pos))] = trim(substr($header, ($pos + 1))); - } - } - - return $headersArr; + return !empty($this->_request_token_url); } /** - * @param $url - * @param array $method + * @param string $url + * @param string $method * * @return array */ @@ -560,12 +513,12 @@ protected function prepareRequest($url, array $headers, array $parameters, $meth $includeCallback = (isset($settings['includeCallback'])) ? $settings['includeCallback'] : false; $includeVerifier = (isset($settings['includeVerifier'])) ? $settings['includeVerifier'] : false; - //Set OAuth parameters/headers + // Set OAuth parameters/headers if ($this->isOauth1()) { - //OAuth 1.0 + // OAuth 1.0 $this->log('making request using OAuth1.0a spec'); - //Get standard OAuth headers + // Get standard OAuth headers $oAuthHeaders = $this->getOauthHeaders($includeCallback); if ($includeVerifier && isset($_GET['oauth_verifier'])) { @@ -576,7 +529,7 @@ protected function prepareRequest($url, array $headers, array $parameters, $meth } } - //Add the parameters + // Add the parameters $oAuthHeaders = array_merge($oAuthHeaders, $parameters); $base_info = $this->buildBaseString($url, $method, $oAuthHeaders); $composite_key = $this->getCompositeKey(); @@ -589,7 +542,7 @@ protected function prepareRequest($url, array $headers, array $parameters, $meth $_SESSION['oauth']['debug']['headers'] = $headers; } } else { - //OAuth 2.0 + // OAuth 2.0 $this->log('making request using OAuth2 spec'); $headers[] = 'Authorization: Bearer '.$this->_access_token; @@ -612,15 +565,15 @@ protected function requestAccessToken($method = 'POST', array $params = [], $res { $this->log('requestAccessToken()'); - //Set OAuth flow parameters + // Set OAuth flow parameters if ($this->isOauth1()) { - //OAuth 1.0 + // OAuth 1.0 $this->log('using OAuth1.0a spec'); $parameters = ['oauth_verifier' => $_GET['oauth_verifier']]; $parameters = array_merge($parameters, $params); } else { - //OAuth 2.0 + // OAuth 2.0 $this->log('using OAuth2 spec'); $parameters = [ @@ -634,7 +587,7 @@ protected function requestAccessToken($method = 'POST', array $params = [], $res $parameters['code'] = $_GET['code']; } - if (strlen($this->_refresh_token) > 0) { + if ($this->_refresh_token) { $this->log('Using refresh token'); $parameters['grant_type'] = 'refresh_token'; $parameters['refresh_token'] = $this->_refresh_token; @@ -643,7 +596,7 @@ protected function requestAccessToken($method = 'POST', array $params = [], $res $parameters = array_merge($parameters, $params); } - //Make the request + // Make the request $settings = [ 'responseType' => $responseType, 'includeCallback' => true, @@ -652,10 +605,10 @@ protected function requestAccessToken($method = 'POST', array $params = [], $res $params = $this->makeRequest($this->_access_token_url, $parameters, $method, $settings); - //Add the token and secret to session + // Add the token and secret to session if (is_array($params)) { if ($this->isOauth1()) { - //OAuth 1.0a + // OAuth 1.0a if (isset($params['oauth_token']) && isset($params['oauth_token_secret'])) { $this->log('access token set as '.$params['oauth_token']); @@ -671,7 +624,7 @@ protected function requestAccessToken($method = 'POST', array $params = [], $res return true; } } else { - //OAuth 2.0 + // OAuth 2.0 if (isset($params['access_token']) && isset($params['expires_in'])) { $this->log('access token set as '.$params['access_token']); @@ -727,7 +680,7 @@ protected function requestToken($responseType = 'flat') { $this->log('requestToken()'); - //Make the request + // Make the request $settings = [ 'responseType' => $responseType, 'includeCallback' => true, @@ -735,7 +688,7 @@ protected function requestToken($responseType = 'flat') ]; $params = $this->makeRequest($this->_request_token_url, [], 'POST', $settings); - //Add token and secret to the session + // Add token and secret to the session if (is_array($params) && isset($params['oauth_token']) && isset($params['oauth_token_secret'])) { $this->log('token set as '.$params['oauth_token']); @@ -747,7 +700,7 @@ protected function requestToken($responseType = 'flat') $_SESSION['oauth']['debug']['token_secret'] = $params['oauth_token_secret']; } } else { - //Throw exception if the required parameters were not found + // Throw exception if the required parameters were not found $this->log('request did not return oauth tokens'); if ($this->_debug) { @@ -768,36 +721,10 @@ protected function requestToken($responseType = 'flat') } } - /** - * Separates parameters from base URL. - * - * @param $url - * @param $params - * - * @return array - */ - protected function separateUrlParams($url, $params) - { - $a = parse_url($url); - - if (!empty($a['query'])) { - parse_str($a['query'], $qparts); - $cleanParams = []; - foreach ($qparts as $k => $v) { - $cleanParams[$k] = $v ? $v : ''; - } - $params = array_merge($params, $cleanParams); - $urlParts = explode('?', $url, 2); - $url = $urlParts[0]; - } - - return [$url, $params]; - } - /** * Build header for OAuth 1 authorization. * - * @param $oauth + * @param array $oauth * * @return string */ diff --git a/lib/Auth/TwoLeggedOAuth2.php b/lib/Auth/TwoLeggedOAuth2.php index ad7c0533..5c70fae8 100644 --- a/lib/Auth/TwoLeggedOAuth2.php +++ b/lib/Auth/TwoLeggedOAuth2.php @@ -11,106 +11,261 @@ namespace Mautic\Auth; +use Mautic\Exception\IncorrectParametersReturnedException; use Mautic\Exception\RequiredParameterMissingException; +/** + * OAuth2 Client Credentials (2-legged OAuth2) Authentication. + * + * This authentication method is used for server-to-server communication + * where no user interaction is required. The application authenticates + * using its own credentials (client_id and client_secret). + * + * @see https://developer.mautic.org/#client-credentials + */ class TwoLeggedOAuth2 extends AbstractAuth { /** - * Password associated with Username. - * - * @var string + * Consumer or client key. */ - private $clientSecret; + protected string $_client_id; /** - * Username or email, basically the Login Identifier. - * - * @var string + * Consumer or client secret. */ - private $clientKey; + protected string $_client_secret; /** * Access token returned by OAuth server. - * - * @var string */ - protected $_access_token; + protected ?string $_access_token = null; + + /** + * Unix timestamp for when token expires. + */ + protected ?int $_expires = null; + + /** + * OAuth2 token type. + */ + protected string $_token_type = 'bearer'; /** - * @var string + * Set to true if access token was updated. */ - private $baseurl; + protected bool $_access_token_updated = false; /** - * @var string + * Access token URL. */ - private $_access_token_url; + protected string $_access_token_url; /** - * {@inheritdoc} + * Check if the current access token is still valid. */ - public function isAuthorized() + public function isAuthorized(): bool { - return !empty($this->clientKey) && !empty($this->clientSecret); + $this->log('isAuthorized()'); + + return $this->validateAccessToken(); } /** - * @param string $baseUrl - * @param string $clientKey The username to use for Authentication *Required* - * @param string $clientSecret The Password to use *Required* + * Setup the authentication credentials. + * + * @param string|null $baseUrl URL of the Mautic instance + * @param string|null $clientKey Client ID from Mautic API credentials + * @param string|null $clientSecret Client Secret from Mautic API credentials + * @param string|null $accessToken Previously stored access token (optional) + * @param int|null $accessTokenExpires Unix timestamp when token expires (optional) * * @throws RequiredParameterMissingException */ - public function setup($baseUrl, $clientKey, $clientSecret, $accessToken = null) - { - // we MUST have the username and password. No Blanks allowed! - // - // remove blanks else Empty doesn't work - $clientKey = trim($clientKey); - $clientSecret = trim($clientSecret); - + public function setup( + ?string $baseUrl = null, + ?string $clientKey = null, + ?string $clientSecret = null, + ?string $accessToken = null, + ?int $accessTokenExpires = null, + ): void { if (empty($clientKey) || empty($clientSecret)) { - //Throw exception if the required parameters were not found - $this->log('parameters did not include clientkey and/or clientSecret'); + $this->log('parameters did not include clientKey and/or clientSecret'); throw new RequiredParameterMissingException('One or more required parameters was not supplied. Both clientKey and clientSecret required!'); } - $this->baseurl = $baseUrl; - $this->clientKey = $clientKey; - $this->clientSecret = $clientSecret; - $this->_access_token = $accessToken; + if (empty($baseUrl)) { + $this->log('parameters did not include baseUrl'); + throw new RequiredParameterMissingException('One or more required parameters was not supplied. baseUrl required!'); + } - if (!$this->_access_token_url) { - $this->_access_token_url = $baseUrl.'/oauth/v2/token'; + $this->_client_id = trim($clientKey); + $this->_client_secret = trim($clientSecret); + $this->_access_token_url = rtrim($baseUrl, '/').'/oauth/v2/token'; + + if (!empty($accessToken)) { + $this->setAccessTokenDetails([ + 'access_token' => $accessToken, + 'expires' => $accessTokenExpires, + ]); } } /** - * @param $url - * @param $method + * Check if the access token was updated during the last request. + */ + public function accessTokenUpdated(): bool + { + return $this->_access_token_updated; + } + + /** + * Get the current access token data. * - * @return array + * @return array{access_token: string|null, expires: int|null, token_type: string} */ - protected function prepareRequest($url, array $headers, array $parameters, $method, array $settings) + public function getAccessTokenData(): array { - if (null !== $this->_access_token) { - $headers = array_merge($headers, ['Authorization: Bearer '.$this->_access_token]); + return [ + 'access_token' => $this->_access_token, + 'expires' => $this->_expires, + 'token_type' => $this->_token_type, + ]; + } + + /** + * Set access token details from stored data. + * + * @param array{access_token?: string|null, expires?: int|null, token_type?: string} $accessTokenDetails + * + * @return $this + */ + public function setAccessTokenDetails(array $accessTokenDetails): static + { + $this->_access_token = $accessTokenDetails['access_token'] ?? null; + $this->_expires = isset($accessTokenDetails['expires']) + ? (int) $accessTokenDetails['expires'] + : null; + + if (isset($accessTokenDetails['token_type'])) { + $this->_token_type = $accessTokenDetails['token_type']; } - return [$headers, $parameters]; + return $this; } - public function getAccessToken(): string + /** + * Validate if the current access token is still valid. + */ + public function validateAccessToken(): bool { - $parameters = [ - 'client_id' => $this->clientKey, - 'client_secret' => $this->clientSecret, + $this->log('validateAccessToken()'); + + // Check if token is expired (with 10 second buffer) + if (!empty($this->_access_token) && !empty($this->_expires) + && $this->_expires < (time() + 10)) { + $this->log('access token expired'); + + return false; + } + + if (!empty($this->_access_token)) { + $this->log('has valid access token'); + + return true; + } + + $this->log('no access token'); + + return false; + } + + /** + * Request a new access token using client credentials. + * + * @throws IncorrectParametersReturnedException + */ + public function requestAccessToken(): bool + { + $this->log('requestAccessToken()'); + + $parameters = [ + 'client_id' => $this->_client_id, + 'client_secret' => $this->_client_secret, 'grant_type' => 'client_credentials', ]; - $accessTokenData = $this->makeRequest($this->_access_token_url, $parameters, 'POST'); - //store access token data however you want - $this->_access_token = $accessTokenData['access_token'] ?? null; + + $params = $this->makeRequest($this->_access_token_url, $parameters, 'POST'); + + if (is_array($params)) { + if (isset($params['access_token']) && isset($params['expires_in'])) { + $this->log('access token set as '.$params['access_token']); + + $this->_access_token = $params['access_token']; + $this->_expires = time() + (int) $params['expires_in']; + $this->_token_type = $params['token_type'] ?? 'bearer'; + $this->_access_token_updated = true; + + if ($this->_debug) { + $_SESSION['oauth']['debug']['tokens']['access_token'] = $params['access_token']; + $_SESSION['oauth']['debug']['tokens']['expires_in'] = $params['expires_in']; + $_SESSION['oauth']['debug']['tokens']['token_type'] = $params['token_type'] ?? null; + } + + return true; + } + } + + $this->log('response did not have an access token'); + + if ($this->_debug) { + $_SESSION['oauth']['debug']['response'] = $params; + } + + if (isset($params['errors'])) { + $errors = []; + foreach ($params['errors'] as $error) { + $errors[] = $error['message']; + } + $response = implode('; ', $errors); + } else { + $response = print_r($params, true); + } + + throw new IncorrectParametersReturnedException('Incorrect access token parameters returned: '.$response); + } + + /** + * Get the access token, requesting a new one if necessary. + * + * @throws IncorrectParametersReturnedException + */ + public function getAccessToken(): string + { + if (!$this->validateAccessToken()) { + $this->requestAccessToken(); + } return $this->_access_token; } + + protected function prepareRequest($url, array $headers, array $parameters, $method, array $settings): array + { + if (!empty($this->_access_token)) { + $headers = array_merge($headers, ['Authorization: Bearer '.$this->_access_token]); + } + + return [$headers, $parameters]; + } + + protected function getQueryParameters($isPost, $parameters): array + { + $query = parent::getQueryParameters($isPost, $parameters); + + // Support for file uploads - pass access token as query parameter + if (isset($parameters['file'])) { + $query['access_token'] = $this->_access_token; + } + + return $query; + } } diff --git a/lib/Exception/AbstractApiException.php b/lib/Exception/AbstractApiException.php index 324b11c3..ee4b99e5 100644 --- a/lib/Exception/AbstractApiException.php +++ b/lib/Exception/AbstractApiException.php @@ -1,4 +1,5 @@ authUrl = $authUrl; diff --git a/lib/Exception/ContextNotFoundException.php b/lib/Exception/ContextNotFoundException.php index d05999cb..13dc0658 100644 --- a/lib/Exception/ContextNotFoundException.php +++ b/lib/Exception/ContextNotFoundException.php @@ -1,4 +1,5 @@ response = $response; diff --git a/lib/MauticApi.php b/lib/MauticApi.php index 15c44ba8..cf464c3b 100644 --- a/lib/MauticApi.php +++ b/lib/MauticApi.php @@ -1,4 +1,5 @@ info = $info; - $this->parseResponse($response); + $this->response = $response; + $this->body = (string) $this->response->getBody(); $this->validate(); } + /** + * @return int + */ + public function getStatusCode() + { + return $this->response->getStatusCode(); + } + /** * @return array */ public function getHeaders() { - return $this->headers; + return $this->response->getHeaders(); } /** @@ -95,20 +108,12 @@ public function decodeFromUrlParams() return $parsed; } - /** - * @return array - */ - public function getInfo() - { - return $this->info; - } - /** * @return bool */ public function isZip() { - return !empty($this->info['content_type']) && 'application/zip' === $this->info['content_type']; + return $this->response->hasHeader('Content-Type') && 'application/zip' === $this->response->getHeader('Content-Type')[0]; } /** @@ -152,24 +157,14 @@ public function saveToFile($path) ]; } - /** - * @param string $response - */ - private function parseResponse($response) - { - $exploded = explode("\r\n\r\n", $response); - $this->body = array_pop($exploded); - $this->headers = implode("\r\n\r\n", $exploded); - } - /** * @throws UnexpectedResponseFormatException */ private function validate() { - if (!in_array($this->info['http_code'], [200, 201])) { - $message = 'The response has unexpected status code ('.$this->info['http_code'].').'; - throw new UnexpectedResponseFormatException($this, $message, $this->info['http_code']); + if (!in_array($this->response->getStatusCode(), [200, 201])) { + $message = 'The response has unexpected status code ('.$this->response->getStatusCode().').'; + throw new UnexpectedResponseFormatException($this, $message, $this->response->getStatusCode()); } if ($this->isHtml()) { diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..6c4b2eb1 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 1 + paths: + - lib + - tests \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 64811a6b..dead1160 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,4 +10,7 @@