From 35cc13cd0ff0aabfc4dcdd08000bb9e04bbd5e6a Mon Sep 17 00:00:00 2001 From: Matthew Batchelder Date: Sat, 27 Aug 2022 23:26:45 -0400 Subject: [PATCH] Initial version --- .editorconfig | 18 + .env.testing | 57 + .env.testing.tric | 57 + .gitattributes | 6 + .gitignore | 3 + CHANGELOG.md | 15 + README.md | 858 ++- codeception.dist.yml | 14 + codeception.tric.yml | 3 + composer.json | 49 + composer.lock | 5964 +++++++++++++++++ phpstan.neon.dist | 28 + src/DB/DB.php | 295 + .../Database/Actions/EnableBigSqlSelects.php | 32 + .../Exceptions/DatabaseQueryException.php | 56 + src/DB/Database/Provider.php | 29 + src/DB/Provider.php | 0 src/DB/QueryBuilder/Clauses/From.php | 29 + src/DB/QueryBuilder/Clauses/Having.php | 121 + src/DB/QueryBuilder/Clauses/Join.php | 59 + src/DB/QueryBuilder/Clauses/JoinCondition.php | 72 + src/DB/QueryBuilder/Clauses/MetaTable.php | 36 + src/DB/QueryBuilder/Clauses/OrderBy.php | 51 + src/DB/QueryBuilder/Clauses/RawSQL.php | 23 + src/DB/QueryBuilder/Clauses/Select.php | 27 + src/DB/QueryBuilder/Clauses/Union.php | 29 + src/DB/QueryBuilder/Clauses/Where.php | 112 + src/DB/QueryBuilder/Concerns/Aggregate.php | 82 + src/DB/QueryBuilder/Concerns/CRUD.php | 115 + src/DB/QueryBuilder/Concerns/FromClause.php | 55 + .../Concerns/GroupByStatement.php | 32 + src/DB/QueryBuilder/Concerns/HavingClause.php | 296 + src/DB/QueryBuilder/Concerns/JoinClause.php | 160 + .../QueryBuilder/Concerns/LimitStatement.php | 30 + src/DB/QueryBuilder/Concerns/MetaQuery.php | 135 + .../QueryBuilder/Concerns/OffsetStatement.php | 30 + .../Concerns/OrderByStatement.php | 47 + .../QueryBuilder/Concerns/SelectStatement.php | 122 + src/DB/QueryBuilder/Concerns/TablePrefix.php | 35 + .../QueryBuilder/Concerns/UnionOperator.php | 55 + src/DB/QueryBuilder/Concerns/WhereClause.php | 488 ++ src/DB/QueryBuilder/JoinQueryBuilder.php | 166 + src/DB/QueryBuilder/QueryBuilder.php | 63 + src/DB/QueryBuilder/Types/JoinType.php | 12 + src/DB/QueryBuilder/Types/Math.php | 14 + src/DB/QueryBuilder/Types/Operator.php | 24 + src/DB/QueryBuilder/Types/Type.php | 19 + src/DB/QueryBuilder/WhereQueryBuilder.php | 19 + tests/_bootstrap.php | 1 + tests/_support/Helper/DBTestCase.php | 18 + tests/wpunit.suite.dist.yml | 17 + tests/wpunit/QueryBuilder/AggregateTest.php | 243 + tests/wpunit/QueryBuilder/AttachMetaTest.php | 109 + tests/wpunit/QueryBuilder/CRUDTest.php | 132 + tests/wpunit/QueryBuilder/FromTest.php | 52 + tests/wpunit/QueryBuilder/GroupByTest.php | 25 + tests/wpunit/QueryBuilder/HavingTest.php | 259 + tests/wpunit/QueryBuilder/JoinTest.php | 116 + .../QueryBuilder/LimitAndOffsetTest.php | 44 + tests/wpunit/QueryBuilder/OrderByTest.php | 56 + tests/wpunit/QueryBuilder/SelectTest.php | 91 + tests/wpunit/QueryBuilder/UnionTest.php | 58 + tests/wpunit/QueryBuilder/WhereTest.php | 495 ++ tests/wpunit/_bootstrap.php | 1 + 64 files changed, 11757 insertions(+), 2 deletions(-) create mode 100644 .editorconfig create mode 100644 .env.testing create mode 100644 .env.testing.tric create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 codeception.dist.yml create mode 100644 codeception.tric.yml create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 phpstan.neon.dist create mode 100644 src/DB/DB.php create mode 100644 src/DB/Database/Actions/EnableBigSqlSelects.php create mode 100644 src/DB/Database/Exceptions/DatabaseQueryException.php create mode 100644 src/DB/Database/Provider.php create mode 100644 src/DB/Provider.php create mode 100644 src/DB/QueryBuilder/Clauses/From.php create mode 100644 src/DB/QueryBuilder/Clauses/Having.php create mode 100644 src/DB/QueryBuilder/Clauses/Join.php create mode 100644 src/DB/QueryBuilder/Clauses/JoinCondition.php create mode 100644 src/DB/QueryBuilder/Clauses/MetaTable.php create mode 100644 src/DB/QueryBuilder/Clauses/OrderBy.php create mode 100644 src/DB/QueryBuilder/Clauses/RawSQL.php create mode 100644 src/DB/QueryBuilder/Clauses/Select.php create mode 100644 src/DB/QueryBuilder/Clauses/Union.php create mode 100644 src/DB/QueryBuilder/Clauses/Where.php create mode 100644 src/DB/QueryBuilder/Concerns/Aggregate.php create mode 100644 src/DB/QueryBuilder/Concerns/CRUD.php create mode 100644 src/DB/QueryBuilder/Concerns/FromClause.php create mode 100644 src/DB/QueryBuilder/Concerns/GroupByStatement.php create mode 100644 src/DB/QueryBuilder/Concerns/HavingClause.php create mode 100644 src/DB/QueryBuilder/Concerns/JoinClause.php create mode 100644 src/DB/QueryBuilder/Concerns/LimitStatement.php create mode 100644 src/DB/QueryBuilder/Concerns/MetaQuery.php create mode 100644 src/DB/QueryBuilder/Concerns/OffsetStatement.php create mode 100644 src/DB/QueryBuilder/Concerns/OrderByStatement.php create mode 100644 src/DB/QueryBuilder/Concerns/SelectStatement.php create mode 100644 src/DB/QueryBuilder/Concerns/TablePrefix.php create mode 100644 src/DB/QueryBuilder/Concerns/UnionOperator.php create mode 100644 src/DB/QueryBuilder/Concerns/WhereClause.php create mode 100644 src/DB/QueryBuilder/JoinQueryBuilder.php create mode 100644 src/DB/QueryBuilder/QueryBuilder.php create mode 100644 src/DB/QueryBuilder/Types/JoinType.php create mode 100644 src/DB/QueryBuilder/Types/Math.php create mode 100644 src/DB/QueryBuilder/Types/Operator.php create mode 100644 src/DB/QueryBuilder/Types/Type.php create mode 100644 src/DB/QueryBuilder/WhereQueryBuilder.php create mode 100644 tests/_bootstrap.php create mode 100644 tests/_support/Helper/DBTestCase.php create mode 100644 tests/wpunit.suite.dist.yml create mode 100644 tests/wpunit/QueryBuilder/AggregateTest.php create mode 100644 tests/wpunit/QueryBuilder/AttachMetaTest.php create mode 100644 tests/wpunit/QueryBuilder/CRUDTest.php create mode 100644 tests/wpunit/QueryBuilder/FromTest.php create mode 100644 tests/wpunit/QueryBuilder/GroupByTest.php create mode 100644 tests/wpunit/QueryBuilder/HavingTest.php create mode 100644 tests/wpunit/QueryBuilder/JoinTest.php create mode 100644 tests/wpunit/QueryBuilder/LimitAndOffsetTest.php create mode 100644 tests/wpunit/QueryBuilder/OrderByTest.php create mode 100644 tests/wpunit/QueryBuilder/SelectTest.php create mode 100644 tests/wpunit/QueryBuilder/UnionTest.php create mode 100644 tests/wpunit/QueryBuilder/WhereTest.php create mode 100644 tests/wpunit/_bootstrap.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d240d02 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = true + +[*.{neon,neon.dist}] +indent_style = tab + +[**.{jshintrc,json,scss-lint,yml}] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.env.testing b/.env.testing new file mode 100644 index 0000000..ac84e89 --- /dev/null +++ b/.env.testing @@ -0,0 +1,57 @@ +# This file will be consumed by both the CI and the tests. +# Some environment variables might not apply to one but might apply to the other: modify with care. + +# What version of WordPress we want to install and test against. +# This has to be compatible with the `wp core download` command, see https://developer.wordpress.org/cli/commands/core/download/. +WP_VERSION=latest + +# This is where, in the context of the CI, we'll install and configure WordPress. +# See `.travis.yml` for more information. +WP_ROOT_FOLDER=/tmp/wordpress + +# The WordPress installation will be served from the Docker container. +# See `dev/docker/ci-compose.yml` for more information. +WP_URL=http://localhost:8080 +WP_DOMAIN=localhost:8080 + +# The credentials that will be used to access the site in acceptance tests +# in methods like `$I->loginAsAdmin();`. +WP_ADMIN_USERNAME=admin +WP_ADMIN_PASSWORD=password + +WP_DB_PORT=4306 + +# The databse is served from the Docker `db` container. +# See `dev/docker/ci-compose.yml` for more information. +WP_TABLE_PREFIX=wp_ +WP_DB_HOST=127.0.0.1:4306 +WP_DB_NAME=wordpress +WP_DB_USER=root +WP_DB_PASSWORD= + +# The test databse is served from the Docker `db` container. +# See `dev/docker/ci-compose.yml` for more information. +WP_TEST_DB_HOST=127.0.0.1:4306 +WP_TEST_DB_NAME=test +WP_TEST_DB_USER=root +WP_TEST_DB_PASSWORD= + +# We're using Selenium and Chrome for acceptance testing. +# In CI context we're starting a Docker container to handle that. +# See the `dev/docker/ci-compose.yml` file. +CHROMEDRIVER_HOST=localhost +CHROMEDRIVER_PORT=4444 + +# The URL of the WordPress installation from the point of view of the Chromedriver container. +# Why not just use `wordpress`? While Chrome will accept an `http://wordpress` address WordPress +# will not, we call the WordPress container with a seemingly looking legit URL and leverage the +# lines that, in the `wp-config.php` file, will make it so that WordPress will use as its home +# URL whatever URL we reach it with. +# See the `dev/docker/wp-config.php` template for more information. +WP_CHROMEDRIVER_URL="wp.test" + +# To run the tests let's force the background-processing lib to run in synchronous (single PHP thread) mode. +TRIBE_NO_ASYNC=1 + +# We're using Docker to run the tests. +USING_CONTAINERS=1 diff --git a/.env.testing.tric b/.env.testing.tric new file mode 100644 index 0000000..20de04b --- /dev/null +++ b/.env.testing.tric @@ -0,0 +1,57 @@ +# This file will be consumed by both the CI and the tests. +# Some environment variables might not apply to one but might apply to the other: modify with care. + +# What version of WordPress we want to install and test against. +# This has to be compatible with the `wp core download` command, see https://developer.wordpress.org/cli/commands/core/download/. +WP_VERSION=latest + +# This is where, in the context of the CI, we'll install and configure WordPress. +# See `.travis.yml` for more information. +WP_ROOT_FOLDER=/var/www/html + +# The WordPress installation will be served from the Docker container. +# See `dev/docker/ci-compose.yml` for more information. +WP_URL=http://wordpress.test +WP_DOMAIN=wordpress.test + +# The credentials that will be used to access the site in acceptance tests +# in methods like `$I->loginAsAdmin();`. +WP_ADMIN_USERNAME=admin +WP_ADMIN_PASSWORD=password + +WP_DB_PORT=3306 + +# The databse is served from the Docker `db` container. +# See `dev/docker/ci-compose.yml` for more information. +WP_TABLE_PREFIX=wp_ +WP_DB_HOST=db +WP_DB_NAME=test +WP_DB_USER=root +WP_DB_PASSWORD=password + +# The test databse is served from the Docker `db` container. +# See `dev/docker/ci-compose.yml` for more information. +WP_TEST_DB_HOST=db +WP_TEST_DB_NAME=test +WP_TEST_DB_USER=root +WP_TEST_DB_PASSWORD=password + +# We're using Selenium and Chrome for acceptance testing. +# In CI context we're starting a Docker container to handle that. +# See the `dev/docker/ci-compose.yml` file. +CHROMEDRIVER_HOST=chrome +CHROMEDRIVER_PORT=4444 + +# The URL of the WordPress installation from the point of view of the Chromedriver container. +# Why not just use `wordpress`? While Chrome will accept an `http://wordpress` address WordPress +# will not, we call the WordPress container with a seemingly looking legit URL and leverage the +# lines that, in the `wp-config.php` file, will make it so that WordPress will use as its home +# URL whatever URL we reach it with. +# See the `dev/docker/wp-config.php` template for more information. +WP_CHROMEDRIVER_URL=http://wordpress.test + +# To run the tests let's force the background-processing lib to run in synchronous (single PHP thread) mode. +TRIBE_NO_ASYNC=1 + +# We're using Docker to run the tests. +USING_CONTAINERS=1 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..633e694 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text +.* export-ignore +composer.lock text -diff +phpstan.* export-ignore +phpunit.* export-ignore +tests export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..17a68b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +files/ +repo/ +vendor/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5ff5ee2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +# Change Log + +All notable changes to this project will be documented in this file. This project adhere to the [Semantic Versioning](http://semver.org/) standard. + +## [unreleased] Unreleased + +## [1.0.0] 2022-08-17 + +### Added + +- Initial version +- Documentation +- Automated tests + +[1.0.0]: https://github.com/stellarwp/schema/releases/tag/1.0.0 diff --git a/README.md b/README.md index 66326ad..4f52070 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,856 @@ -# db -A WPDB wrapper and query builder library. +# Query Builder + +Query Builder helper class is used to write SQL queries + +- [DB](#db) + +- [Select statements](#select-statements) + +- [From Clause](#from-clause) + +- [Joins](#joins) + - [LEFT Join](#left-join) + - [RIGHT Join](#right-join) + - [INNER Join](#inner-join) + - [Join Raw](#join-raw) + - [Advanced Join Clauses](#advanced-join-clauses) + +- [Unions](#unions) + +- [Where Clauses](#where-clauses) + - [Where](#where-clauses) + - [Where IN](#where-in-clauses) + - [Where BETWEEN](#where-between-clauses) + - [Where LIKE](#where-like-clauses) + - [Where IS NULL](#where-is-null-clauses) + - [Where EXISTS](#where-exists-clauses) + - [Subquery Where Clauses](#subquery-where-clauses) + - [Nested Where Clauses](#nested-where-clauses) + +- [Ordering, Grouping, Limit & Offset](#ordering-grouping-limit--offset) + - [Ordering](#ordering) + - [Grouping](#grouping) + - [Limit & Offset](#limit--offset) + +- [Special methods for working with meta tables](#special-methods-for-working-with-meta-tables) + - [attachMeta](#attachmeta) + - [configureMetaTable](#configuremetatable) + +- [CRUD](#crud) + - [Insert](#insert) + - [Update](#update) + - [Delete](#delete) + - [Get](#get) + +- [Aggregate Functions](#aggregate-functions) + - [Count](#count) + - [Sum](#sum) + - [Avg](#avg) + - [Min](#min) + - [Max](#max) + +## DB + +`DB` class is a static decorator for the `$wpdb` class, but it has a few methods that are exceptions to that. +Methods `DB::table()` and `DB::raw()`. + +`DB::table()` is a static facade for the `QueryBuilder` class, and it accepts two string arguments, `$tableName` +and `$tableAlias`. + +Under the hood, `DB::table()` will create a new `QueryBuilder` instance, and it will use `QueryBuilder::from` method to set the table name. Calling `QueryBuilder::from` when using `DB::table` method will return an unexpected result. Basically, we are telling the `QueryBuilder` that we want to select data from two tables. + +### Important + +When using `DB::table(tableName)` method, the `tableName` is prefixed with `$wpdb->prefix`. To bypass that, you can +use `DB::raw` method which will tell `QueryBuilder` not to prefix the table name. + +```php +DB::table(DB::raw('posts')); +``` + +## Select statements + +#### Available methods - select / selectRaw / distinct + +By using the `QueryBuilder::select` method, you can specify a custom `SELECT` statement for the query. + +```php +DB::table('posts')->select('ID', 'post_title', 'post_date'); +``` + +Generated SQL + +```sql +SELECT ID, post_title, post_date FROM wp_posts +``` + +You can also specify the column alias by providing an array _[column, alias]_ to the `QueryBuilder::select` method. + +```php +DB::table('posts')->select( + ['ID', 'post_id'], + ['post_status', 'status'], + ['post_date', 'createdAt'] +); +``` + +Generated SQL: + +```sql +SELECT ID AS post_id, post_status AS status, post_date AS createdAt FROM wp_posts +``` + +The distinct method allows you to force the query to return distinct results: + +```php +DB::table('posts')->select('post_status')->distinct(); +``` + +You can also specify a custom `SELECT` statement with `QueryBuilder::selectRaw` method. This method accepts an optional array of +bindings as its second argument. + +```php +DB::table('posts') + ->select('ID') + ->selectRaw('(SELECT ID from wp_posts WHERE post_status = %s) AS subscriptionId', 'give_subscription'); +``` + +Generated SQL + +```sql +SELECT ID, (SELECT ID from wp_posts WHERE post_status = 'give_subscription') AS subscriptionId FROM wp_posts +``` + +By default, all columns will be selected from a database table. + +```php +DB::table('posts'); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts +``` + +## From clause + +By using the `QueryBuilder::from()` method, you can specify a custom `FROM` clause for the query. + + +```php +$builder = new QueryBuilder(); +$builder->from('posts'); +``` + +Set multiple `FROM` clauses + +```php +$builder = new QueryBuilder(); +$builder->from('posts'); +$builder->from('postmeta'); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts, wp_postmeta +``` + +### Important + +Table name is prefixed with `$wpdb->prefix`. To bypass that, you can +use `DB::raw` method which will tell `QueryBuilder` not to prefix the table name. + +```php +$builder = new QueryBuilder(); +$builder->from(DB::raw('posts')); +``` + +## Joins + +The Query Builder may also be used to add `JOIN` clauses to your queries. + +#### Available methods - leftJoin / rightJoin / innerJoin / joinRaw / join + +### LEFT Join + +`LEFT JOIN` clause. + +```php +DB::table('posts', 'donationsTable') + ->select('donationsTable.*', 'metaTable.*') + ->leftJoin('give_donationmeta', 'donationsTable.ID', 'metaTable.donation_id', 'metaTable'); +``` + +Generated SQL + +```sql +SELECT donationsTable.*, metaTable.* FROM wp_posts AS donationsTable LEFT JOIN wp_give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id +``` + +### RIGHT Join + +`RIGHT JOIN` clause. + +```php +DB::table('posts', 'donationsTable') + ->select('donationsTable.*', 'metaTable.*') + ->rightJoin('give_donationmeta', 'donationsTable.ID', 'metaTable.donation_id', 'metaTable'); +``` + +Generated SQL + +```sql +SELECT donationsTable.*, metaTable.* FROM wp_posts AS donationsTable RIGHT JOIN wp_give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id +``` + +### INNER Join + +`INNER JOIN` clause. + +```php +DB::table('posts', 'donationsTable') + ->select('donationsTable.*', 'metaTable.*') + ->innerJoin('give_donationmeta', 'donationsTable.ID', 'metaTable.donation_id', 'metaTable'); +``` + +Generated SQL + +```sql +SELECT donationsTable.*, metaTable.* FROM wp_posts AS donationsTable INNER JOIN wp_give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id +``` + +### Join Raw + +Insert a raw expression into query. + +```php +DB::table('posts', 'donationsTable') + ->select('donationsTable.*', 'metaTable.*') + ->joinRaw('LEFT JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id'); +``` + +Generated SQL + +```sql +SELECT donationsTable.*, metaTable.* FROM wp_posts AS donationsTable LEFT JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id +``` + +### Advanced Join Clauses + +**The closure will receive a `Give\Framework\QueryBuilder\JoinQueryBuilder` instance** + +```php +DB::table('posts') + ->select('donationsTable.*', 'metaTable.*') + ->join(function (JoinQueryBuilder $builder) { + $builder + ->leftJoin('give_donationmeta', 'metaTable') + ->on('donationsTable.ID', 'metaTable.donation_id') + ->andOn('metaTable.meta_key', 'some_key', $qoute = true); + }); +``` + +Generated SQL + +```sql +SELECT donationsTable.*, metaTable.* FROM wp_posts LEFT JOIN wp_give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id AND metaTable.meta_key = 'some_key' +``` + +## Unions + +The Query Builder also provides a convenient method to "union" two or more queries together. + +#### Available methods - union / unionAll + +### Union + +```php +$donations = DB::table('give_donations')->where('author_id', 10); + +DB::table('give_subscriptions') + ->select('ID') + ->where('ID', 100, '>') + ->union($donations); +``` + +Generated SQL: + +```sql +SELECT ID FROM wp_give_subscriptions WHERE ID > '100' UNION SELECT * FROM wp_give_donations WHERE author_id = '10' +``` + +## Where Clauses + +You may use the Query Builder's `where` method to add `WHERE` clauses to the query. + +### Where + +#### Available methods - where / orWhere + +```php +DB::table('posts')->where('ID', 5); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE ID = '5' +``` + +Using `where` multiple times. + +```php +DB::table('posts') + ->where('ID', 5) + ->where('post_author', 10); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE ID = '5' AND post_author = '10' +``` + +### Where IN Clauses + +#### Available methods - whereIn / orWhereIn / whereNotIn / orWhereNotIn + +The `QueryBuilder::whereIn` method verifies that a given column's value is contained within the given array: + +```php +DB::table('posts')->whereIn('ID', [1, 2, 3]); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE ID IN ('1','2','3') +``` + +You can also pass a closure as the second argument which will generate a subquery. + +**The closure will receive a `Give\Framework\QueryBuilder\QueryBuilder` instance** + +```php +DB::table('posts') + ->whereIn('ID', function (QueryBuilder $builder) { + $builder + ->select(['meta_value', 'donation_id']) + ->from('give_donationmeta') + ->where('meta_key', 'donation_id'); + }); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE ID IN (SELECT meta_value AS donation_id FROM wp_give_donationmeta WHERE meta_key = 'donation_id') +``` + +### Where BETWEEN Clauses + +The `QueryBuilder::whereBetween` method verifies that a column's value is between two values: + +#### Available methods - whereBetween / orWhereBetween / whereNotBetween / orWhereNotBetween + +```php +DB::table('posts')->whereBetween('ID', 0, 100); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE ID BETWEEN '0' AND '100' +``` + +### Where LIKE Clauses + +The `QueryBuilder::whereLike` method searches for a specified pattern in a column. + +#### Available methods - whereLike / orWhereLike / whereNotLike / orWhereNotLike + +```php +DB::table('posts')->whereLike('post_title', 'Donation'); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE post_title LIKE '%Donation%' +``` + +### Where IS NULL Clauses + +The `QueryBuilder::whereIsNull` method verifies that a column's value is `NULL` + +#### Available methods - whereIsNull / orWhereIsNull / whereIsNotNull / orWhereIsNotNull + +```php +DB::table('posts')->whereIsNull('post_author'); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE post_author IS NULL +``` + +### Where EXISTS Clauses + +The `QueryBuilder::whereExists` method allows you to write `WHERE EXISTS` SQL clauses. The `QueryBuilder::whereExists` method accepts a closure which will receive a `QueryBuilder` instance. + +#### Available methods - whereExists / whereNotExists + +```php +DB::table('give_donationmeta') + ->whereExists(function (QueryBuilder $builder) { + $builder + ->select(['meta_value', 'donation_id']) + ->where('meta_key', 'donation_id'); + }); +``` + +Generated SQL + +```sql +SELECT * FROM wp_give_donationmeta WHERE EXISTS (SELECT meta_value AS donation_id WHERE meta_key = 'donation_id') +``` + +### Subquery Where Clauses + +Sometimes you may need to construct a `WHERE` clause that compares the results of a subquery to a given value. + +```php +DB::table('posts') + ->where('post_author', function (QueryBuilder $builder) { + $builder + ->select(['meta_value', 'author_id']) + ->from('postmeta') + ->where('meta_key', 'donation_id') + ->where('meta_value', 10); + }); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE post_author = (SELECT meta_value AS author_id FROM wp_postmeta WHERE meta_key = 'donation_id' AND meta_value = '10') +``` + +### Nested Where Clauses + +Sometimes you may need to construct a `WHERE` clause that has nested WHERE clauses. + +**The closure will receive a `Give\Framework\QueryBuilder\WhereQueryBuilder` instance** + +```php +DB::table('posts') + ->where('post_author', 10) + ->where(function (WhereQueryBuilder $builder) { + $builder + ->where('post_status', 'published') + ->orWhere('post_status', 'donation') + ->whereIn('ID', [1, 2, 3]); + }); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE post_author = '10' AND ( post_status = 'published' OR post_status = 'donation' AND ID IN ('1','2','3')) +``` + +## Ordering, Grouping, Limit & Offset + +### Ordering + +The `QueryBuilder::orderBy` method allows you to sort the results of the query by a given column. + +```php +DB::table('posts')->orderBy('ID'); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts ORDER BY ID ASC +``` + +Sorting result by multiple columns + +```php +DB::table('posts') + ->orderBy('ID') + ->orderBy('post_date', 'DESC'); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts ORDER BY ID ASC, post_date DESC +``` + +### Grouping + +The `QueryBuilder::groupBy` and `QueryBuilder::having*` methods are used to group the query results. + +#### Available methods - groupBy / having / orHaving / havingCount / orHavingCount / havingMin / orHavingMin / havingMax / orHavingMax / havingAvg / orHavingAvg / havingSum / orHavingSum / havingRaw + +```php +DB::table('posts') + ->groupBy('id') + ->having('id', '>', 10); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts WHERE GROUP BY id HAVING 'id' > '10' +``` + +### Limit & Offset + +Limit the number of results returned from the query. + +#### Available methods - limit / offset + +```php +DB::table('posts') + ->limit(10) + ->offset(20); +``` + +Generated SQL + +```sql +SELECT * FROM wp_posts LIMIT 10 OFFSET 20 +``` + +## Special methods for working with meta tables + +Query Builder has a few special methods for abstracting the work with meta tables. + + +### attachMeta + +`attachMeta` is used to include meta table _meta_key_ column values as columns in the `SELECT` statement. + +Under the hood `QueryBuilder::attachMeta` will add join clause for each defined `meta_key` column. And each column will be +added in select statement as well, which means the meta columns will be returned in query result. Aliasing meta columns +is recommended when using `QueryBuilder::attachMeta` method. + +```php +DB::table('posts') + ->select( + ['ID', 'id'], + ['post_date', 'createdAt'], + ['post_modified', 'updatedAt'], + ['post_status', 'status'], + ['post_parent', 'parentId'] + ) + ->attachMeta('give_donationmeta', 'ID', 'donation_id', + ['_give_payment_total', 'amount'], + ['_give_payment_currency', 'paymentCurrency'], + ['_give_payment_gateway', 'paymentGateway'], + ['_give_payment_donor_id', 'donorId'], + ['_give_donor_billing_first_name', 'firstName'], + ['_give_donor_billing_last_name', 'lastName'], + ['_give_payment_donor_email', 'donorEmail'], + ['subscription_id', 'subscriptionId'] + ) + ->leftJoin('give_donationmeta', 'ID', 'donationMeta.donation_id', 'donationMeta') + ->where('post_type', 'give_payment') + ->where('post_status', 'give_subscription') + ->where('donationMeta.meta_key', 'subscription_id') + ->where('donationMeta.meta_value', 1) + ->orderBy('post_date', 'DESC'); +``` + +Generated SQL: + +```sql +SELECT ID AS id, + post_date AS createdAt, + post_modified AS updatedAt, + post_status AS status, + post_parent AS parentId, + give_donationmeta_attach_meta_0.meta_value AS amount, + give_donationmeta_attach_meta_1.meta_value AS paymentCurrency, + give_donationmeta_attach_meta_2.meta_value AS paymentGateway, + give_donationmeta_attach_meta_3.meta_value AS donorId, + give_donationmeta_attach_meta_4.meta_value AS firstName, + give_donationmeta_attach_meta_5.meta_value AS lastName, + give_donationmeta_attach_meta_6.meta_value AS donorEmail, + give_donationmeta_attach_meta_7.meta_value AS subscriptionId +FROM wp_posts + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_0 + ON ID = give_donationmeta_attach_meta_0.donation_id AND + give_donationmeta_attach_meta_0.meta_key = '_give_payment_total' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_1 + ON ID = give_donationmeta_attach_meta_1.donation_id AND + give_donationmeta_attach_meta_1.meta_key = '_give_payment_currency' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_2 + ON ID = give_donationmeta_attach_meta_2.donation_id AND + give_donationmeta_attach_meta_2.meta_key = '_give_payment_gateway' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_3 + ON ID = give_donationmeta_attach_meta_3.donation_id AND + give_donationmeta_attach_meta_3.meta_key = '_give_payment_donor_id' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_4 + ON ID = give_donationmeta_attach_meta_4.donation_id AND + give_donationmeta_attach_meta_4.meta_key = '_give_donor_billing_first_name' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_5 + ON ID = give_donationmeta_attach_meta_5.donation_id AND + give_donationmeta_attach_meta_5.meta_key = '_give_donor_billing_last_name' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_6 + ON ID = give_donationmeta_attach_meta_6.donation_id AND + give_donationmeta_attach_meta_6.meta_key = '_give_payment_donor_email' + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_7 + ON ID = give_donationmeta_attach_meta_7.donation_id AND + give_donationmeta_attach_meta_7.meta_key = 'subscription_id' + LEFT JOIN wp_give_donationmeta donationMeta ON ID = donationMeta.donation_id +WHERE post_type = 'give_payment' + AND post_status = 'give_subscription' + AND donationMeta.meta_key = 'subscription_id' + AND donationMeta.meta_value = '1' +ORDER BY post_date DESC +``` + +Returned result: + +``` +stdClass Object +( + [id] => 93 + [createdAt] => 2022-02-21 00:00:00 + [updatedAt] => 2022-01-21 11:08:09 + [status] => give_subscription + [parentId] => 92 + [amount] => 100.000000 + [paymentCurrency] => USD + [paymentGateway] => manual + [donorId] => 1 + [firstName] => Ante + [lastName] => Laca + [donorEmail] => dev-email@flywheel.local + [subscriptionId] => 1 +) +``` + +#### Fetch multiple instances of the same meta key + +Sometimes we need to fetch multiple instances of the same meta key. This is possible by setting the third parameter to `true`, example `['additional_email', 'additionalEmails', true]` + +```php +DB::table('give_donors') + ->select( + 'id', + 'email', + 'name' + ) + ->attachMeta( + 'give_donormeta', + 'id', + 'donor_id', + ['additional_email', 'additionalEmails', true] + ); + +``` + +Generated SQL: + +```sql +SELECT id, email, name, GROUP_CONCAT(DISTINCT give_donormeta_attach_meta_0.meta_value) AS additionalEmails +FROM wp_give_donors + LEFT JOIN wp_give_donormeta give_donormeta_attach_meta_0 ON id = give_donormeta_attach_meta_0.donor_id AND give_donormeta_attach_meta_0.meta_key = 'additional_email' +GROUP BY id +``` + +Returned result: + +Instances with the same key, in this case `additional_email`, will be concatenated into JSON array string. + +```php +Array +( + [0] => stdClass Object + ( + [id] => 1 + [email] => bill@flywheel.local + [name] => Bill Murray + [additionalEmails] => ["email1@lywheel.local","email2@lywheel.local"] + ) + + [1] => stdClass Object + ( + [id] => 2 + [email] => jon@flywheel.local + [name] => Jon Waldstein + [additionalEmails] => ["email3@lywheel.local","email4@lywheel.local","email5@lywheel.local"] + ) + + [2] => stdClass Object + ( + [id] => 3 + [email] => ante@flywheel.local + [name] => Ante laca + [additionalEmails] => + ) + +) +``` + +### configureMetaTable + +By default, `QueryBuilder::attachMeta` will use `meta_key`, and `meta_value` as meta table column names, but that sometimes might not be the case. + +With `QueryBuilder::configureMetaTable` you can define a custom `meta_key` and `meta_value` column names. + +```php +DB::table('posts') + ->select( + ['ID', 'id'], + ['post_date', 'createdAt'] + ) + ->configureMetaTable( + 'give_donationmeta', + 'custom_meta_key', + 'custom_meta_value' + ) + ->attachMeta( + 'give_donationmeta', + 'ID', + 'donation_id', + ['_give_payment_total', 'amount'] + ) + ->leftJoin('give_donationmeta', 'ID', 'donationMeta.donation_id', 'donationMeta') + ->where('post_type', 'give_payment') + ->where('post_status', 'give_subscription') + ->where('donationMeta.custom_meta_key', 'subscription_id') + ->where('donationMeta.custom_meta_value', 1); +``` + +Generated SQL + +```sql +SELECT ID AS id, post_date AS createdAt, give_donationmeta_attach_meta_0.custom_meta_value AS amount +FROM wp_posts + LEFT JOIN wp_give_donationmeta give_donationmeta_attach_meta_0 + ON ID = give_donationmeta_attach_meta_0.donation_id AND + give_donationmeta_attach_meta_0.custom_meta_key = '_give_payment_total' + LEFT JOIN wp_give_donationmeta donationMeta ON ID = donationMeta.donation_id +WHERE post_type = 'give_payment' + AND post_status = 'give_subscription' + AND donationMeta.custom_meta_key = 'subscription_id' + AND donationMeta.custom_meta_value = '1' +``` + +## CRUD + +### Insert + +The QueryBuilder also provides `QueryBuilder::insert` method that may be used to insert records into the database table. + +```php +DB::table('posts') + ->insert([ + 'post_title' => 'Post Title', + 'post_author' => 1, + 'post_content' => 'Post Content' + ]); +``` + + +### Update + +In addition to inserting records into the database, the QueryBuilder can also update existing records using the `QueryBuilder::update` method. + +```php +DB::table('posts') + ->where('post_author', 1) + ->update([ + 'post_title' => 'Post Title 2', + 'post_content' => 'Post Content 2' + ]); +``` + +### Delete + +The `QueryBuilder::delete` method may be used to delete records from the table. + +```php +DB::table('posts') + ->where('post_author', 1) + ->delete(); +``` + + +### Get + +#### Available methods - get / getAll + +Get single row + +```php +$post = DB::table('posts')->where('post_author', 1)->get(); +``` + +Get all rows + +```php +$posts = DB::table('posts')->where('post_status', 'published')->getAll(); +``` + + + + +## Aggregate Functions + +The Query Builder also provides a variety of methods for retrieving aggregate values like `count`, `sum`, `avg`, `min` and `max`. + +### Count + +```php +$count = DB::table('posts') + ->where('post_type', 'published') + ->count(); +``` + +Count rows where provided column is not null. + +```php +$count = DB::table('donations')->count('not_null_value_column'); +``` + +### Sum + +```php +$sum = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->sum('meta_value'); +``` + +### Avg + +```php +$avg = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->avg('meta_value'); +``` + +### Min + +```php +$min = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->min('meta_value'); +``` + +### Max + +```php +$max = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->max('meta_value'); +``` diff --git a/codeception.dist.yml b/codeception.dist.yml new file mode 100644 index 0000000..dc50b32 --- /dev/null +++ b/codeception.dist.yml @@ -0,0 +1,14 @@ +actor: Tester +bootstrap: _bootstrap.php +paths: + tests: tests + log: tests/_output + data: tests/_data + helpers: tests/_support + wp_root: "%WP_ROOT_FOLDER%" +settings: + colors: true + memory_limit: 1024M +params: + # read dynamic configuration parameters from the .env file + - .env.testing diff --git a/codeception.tric.yml b/codeception.tric.yml new file mode 100644 index 0000000..8a383c7 --- /dev/null +++ b/codeception.tric.yml @@ -0,0 +1,3 @@ +params: + # read dynamic configuration parameters from the .env file + - .env.testing.tric diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b982088 --- /dev/null +++ b/composer.json @@ -0,0 +1,49 @@ +{ + "name": "stellarwp/db", + "description": "A WPDB wrapper and query builder library.", + "type": "library", + "license": "GPL-2.0", + "autoload": { + "psr-4": { + "StellarWP\\DB\\": "src/DB/" + } + }, + "autoload-dev": { + "psr-4": { + "StellarWP\\DB\\Tests\\": "tests/_support/Helper/" + } + }, + "authors": [ + { + "name": "StellarWP", + "email": "dev@stellarwp.com" + } + ], + "minimum-stability": "stable", + "require": { + "lucatume/di52": "^3.0" + }, + "require-dev": { + "codeception/module-asserts": "^1.0", + "codeception/module-cli": "^1.0", + "codeception/module-db": "^1.0", + "codeception/module-filesystem": "^1.0", + "codeception/module-phpbrowser": "^1.0", + "codeception/module-rest": "^1.0", + "codeception/module-webdriver": "^1.0", + "codeception/util-universalframework": "^1.0", + "lucatume/wp-browser": "^3.0.14", + "phpunit/phpunit": "~6.0", + "szepeviktor/phpstan-wordpress": "^1.1", + "symfony/event-dispatcher-contracts": "^2.5.1", + "symfony/string": "^5.4" + }, + "scripts": { + "test:analysis": [ + "phpstan analyse -c phpstan.neon.dist --memory-limit=512M" + ] + }, + "scripts-descriptions": { + "test:analysis": "Run static code analysis." + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..f3cd934 --- /dev/null +++ b/composer.lock @@ -0,0 +1,5964 @@ +{ + "_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": "be30bdcb08d7290aba934f26408da912", + "packages": [ + { + "name": "lucatume/di52", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/lucatume/di52.git", + "reference": "5af2320885de6fa352dbaeecba87cd8df16d6db1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lucatume/di52/zipball/5af2320885de6fa352dbaeecba87cd8df16d6db1", + "reference": "5af2320885de6fa352dbaeecba87cd8df16d6db1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.6", + "psr/container": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "files": [ + "aliases.php" + ], + "psr-4": { + "lucatume\\DI52\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0" + ], + "authors": [ + { + "name": "Luca Tumedei", + "email": "luca@theaveragedev.com" + } + ], + "description": "A PHP 5.2 compatible dependency injection container.", + "time": "2022-02-09T16:16:09+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" + ], + "time": "2021-11-05T16:50:12+00:00" + } + ], + "packages-dev": [ + { + "name": "antecedent/patchwork", + "version": "2.1.21", + "source": { + "type": "git", + "url": "https://github.com/antecedent/patchwork.git", + "reference": "25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d", + "reference": "25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignas Rudaitis", + "email": "ignas.rudaitis@gmail.com" + } + ], + "description": "Method redefinition (monkey-patching) functionality for PHP.", + "homepage": "http://patchwork2.org/", + "keywords": [ + "aop", + "aspect", + "interception", + "monkeypatching", + "redefinition", + "runkit", + "testing" + ], + "time": "2022-02-07T07:28:34+00:00" + }, + { + "name": "behat/gherkin", + "version": "v4.9.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/0bc8d1e30e96183e4f36db9dc79caead300beff4", + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4", + "shasum": "" + }, + "require": { + "php": "~7.2|~8.0" + }, + "require-dev": { + "cucumber/cucumber": "dev-gherkin-22.0.0", + "phpunit/phpunit": "~8|~9", + "symfony/yaml": "~3|~4|~5" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "time": "2021-10-12T13:05:09+00:00" + }, + { + "name": "bordoni/phpass", + "version": "0.3.6", + "source": { + "type": "git", + "url": "https://github.com/bordoni/phpass.git", + "reference": "12f8f5cc03ebb7efd69554f104afe9aa1aa46e1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bordoni/phpass/zipball/12f8f5cc03ebb7efd69554f104afe9aa1aa46e1a", + "reference": "12f8f5cc03ebb7efd69554f104afe9aa1aa46e1a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "replace": { + "hautelook/phpass": "0.3.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "Hautelook": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Public Domain" + ], + "authors": [ + { + "name": "Solar Designer", + "email": "solar@openwall.com", + "homepage": "http://openwall.com/phpass/" + }, + { + "name": "Gustavo Bordoni", + "email": "gustavo@bordoni.me", + "homepage": "https://bordoni.me" + } + ], + "description": "Portable PHP password hashing framework", + "homepage": "http://github.com/bordoni/phpass/", + "keywords": [ + "blowfish", + "crypt", + "password", + "security" + ], + "time": "2012-08-31T00:00:00+00:00" + }, + { + "name": "codeception/codeception", + "version": "4.2.2", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Codeception.git", + "reference": "b88014f3348c93f3df99dc6d0967b0dbfa804474" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/b88014f3348c93f3df99dc6d0967b0dbfa804474", + "reference": "b88014f3348c93f3df99dc6d0967b0dbfa804474", + "shasum": "" + }, + "require": { + "behat/gherkin": "^4.4.0", + "codeception/lib-asserts": "^1.0 | 2.0.*@dev", + "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.1.1 | ^9.0", + "codeception/stub": "^2.0 | ^3.0 | ^4.0", + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/psr7": "^1.4 | ^2.0", + "php": ">=5.6.0 <9.0", + "symfony/console": ">=2.7 <6.0", + "symfony/css-selector": ">=2.7 <6.0", + "symfony/event-dispatcher": ">=2.7 <6.0", + "symfony/finder": ">=2.7 <6.0", + "symfony/yaml": ">=2.7 <6.0" + }, + "require-dev": { + "codeception/module-asserts": "^1.0 | 2.0.*@dev", + "codeception/module-cli": "^1.0 | 2.0.*@dev", + "codeception/module-db": "^1.0 | 2.0.*@dev", + "codeception/module-filesystem": "^1.0 | 2.0.*@dev", + "codeception/module-phpbrowser": "^1.0 | 2.0.*@dev", + "codeception/specify": "~0.3", + "codeception/util-universalframework": "*@dev", + "monolog/monolog": "~1.8", + "squizlabs/php_codesniffer": "~2.0", + "symfony/process": ">=2.7 <6.0", + "vlucas/phpdotenv": "^2.0 | ^3.0 | ^4.0 | ^5.0" + }, + "suggest": { + "codeception/specify": "BDD-style code blocks", + "codeception/verify": "BDD-style assertions", + "hoa/console": "For interactive console functionality", + "stecman/symfony-console-completion": "For BASH autocompletion", + "symfony/phpunit-bridge": "For phpunit-bridge support" + }, + "bin": [ + "codecept" + ], + "type": "library", + "extra": { + "branch-alias": [] + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Codeception\\": "src/Codeception", + "Codeception\\Extension\\": "ext" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "https://codegyre.com" + } + ], + "description": "BDD-style testing framework", + "homepage": "https://codeception.com/", + "keywords": [ + "BDD", + "TDD", + "acceptance testing", + "functional testing", + "unit testing" + ], + "funding": [ + { + "url": "https://opencollective.com/codeception", + "type": "open_collective" + } + ], + "time": "2022-08-13T13:28:25+00:00" + }, + { + "name": "codeception/lib-asserts", + "version": "1.13.2", + "source": { + "type": "git", + "url": "https://github.com/Codeception/lib-asserts.git", + "reference": "184231d5eab66bc69afd6b9429344d80c67a33b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/lib-asserts/zipball/184231d5eab66bc69afd6b9429344d80c67a33b6", + "reference": "184231d5eab66bc69afd6b9429344d80c67a33b6", + "shasum": "" + }, + "require": { + "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.0.3 | ^9.0", + "ext-dom": "*", + "php": ">=5.6.0 <9.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + }, + { + "name": "Gintautas Miselis" + }, + { + "name": "Gustavo Nieves", + "homepage": "https://medium.com/@ganieves" + } + ], + "description": "Assertion methods used by Codeception core and Asserts module", + "homepage": "https://codeception.com/", + "keywords": [ + "codeception" + ], + "time": "2020-10-21T16:26:20+00:00" + }, + { + "name": "codeception/lib-innerbrowser", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/Codeception/lib-innerbrowser.git", + "reference": "31b4b56ad53c3464fcb2c0a14d55a51a201bd3c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/lib-innerbrowser/zipball/31b4b56ad53c3464fcb2c0a14d55a51a201bd3c2", + "reference": "31b4b56ad53c3464fcb2c0a14d55a51a201bd3c2", + "shasum": "" + }, + "require": { + "codeception/codeception": "4.*@dev", + "ext-dom": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=5.6.0 <9.0", + "symfony/browser-kit": ">=2.7 <6.0", + "symfony/dom-crawler": ">=2.7 <6.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "require-dev": { + "codeception/util-universalframework": "dev-master" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + }, + { + "name": "Gintautas Miselis" + } + ], + "description": "Parent library for all Codeception framework modules and PhpBrowser", + "homepage": "https://codeception.com/", + "keywords": [ + "codeception" + ], + "time": "2021-08-30T15:21:42+00:00" + }, + { + "name": "codeception/module-asserts", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-asserts.git", + "reference": "59374f2fef0cabb9e8ddb53277e85cdca74328de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-asserts/zipball/59374f2fef0cabb9e8ddb53277e85cdca74328de", + "reference": "59374f2fef0cabb9e8ddb53277e85cdca74328de", + "shasum": "" + }, + "require": { + "codeception/codeception": "*@dev", + "codeception/lib-asserts": "^1.13.1", + "php": ">=5.6.0 <9.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + }, + { + "name": "Gustavo Nieves", + "homepage": "https://medium.com/@ganieves" + } + ], + "description": "Codeception module containing various assertions", + "homepage": "https://codeception.com/", + "keywords": [ + "assertions", + "asserts", + "codeception" + ], + "time": "2020-10-21T16:48:15+00:00" + }, + { + "name": "codeception/module-cli", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-cli.git", + "reference": "1f841ad4a1d43e5d9e60a43c4cc9e5af8008024f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-cli/zipball/1f841ad4a1d43e5d9e60a43c4cc9e5af8008024f", + "reference": "1f841ad4a1d43e5d9e60a43c4cc9e5af8008024f", + "shasum": "" + }, + "require": { + "codeception/codeception": "*@dev", + "php": ">=5.6.0 <9.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + } + ], + "description": "Codeception module for testing basic shell commands and shell output", + "homepage": "http://codeception.com/", + "keywords": [ + "codeception" + ], + "time": "2020-12-26T16:56:19+00:00" + }, + { + "name": "codeception/module-db", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-db.git", + "reference": "04c3e66fbd3a3ced17fcccc49627f6393a97b04b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-db/zipball/04c3e66fbd3a3ced17fcccc49627f6393a97b04b", + "reference": "04c3e66fbd3a3ced17fcccc49627f6393a97b04b", + "shasum": "" + }, + "require": { + "codeception/codeception": "*@dev", + "php": ">=5.6.0 <9.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + } + ], + "description": "DB module for Codeception", + "homepage": "http://codeception.com/", + "keywords": [ + "codeception", + "database-testing", + "db-testing" + ], + "time": "2022-03-05T19:38:40+00:00" + }, + { + "name": "codeception/module-filesystem", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-filesystem.git", + "reference": "781be167fb1557bfc9b61e0a4eac60a32c534ec1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-filesystem/zipball/781be167fb1557bfc9b61e0a4eac60a32c534ec1", + "reference": "781be167fb1557bfc9b61e0a4eac60a32c534ec1", + "shasum": "" + }, + "require": { + "codeception/codeception": "^4.0", + "php": ">=5.6.0 <9.0", + "symfony/finder": ">=2.7 <6.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + } + ], + "description": "Codeception module for testing local filesystem", + "homepage": "http://codeception.com/", + "keywords": [ + "codeception", + "filesystem" + ], + "time": "2020-10-24T14:46:40+00:00" + }, + { + "name": "codeception/module-phpbrowser", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-phpbrowser.git", + "reference": "8ba6bede11d0914e74d98691f427fd8f397f192e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-phpbrowser/zipball/8ba6bede11d0914e74d98691f427fd8f397f192e", + "reference": "8ba6bede11d0914e74d98691f427fd8f397f192e", + "shasum": "" + }, + "require": { + "codeception/codeception": "^4.1", + "codeception/lib-innerbrowser": "^1.3", + "guzzlehttp/guzzle": "^6.3|^7.0", + "php": ">=5.6.0 <9.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "require-dev": { + "codeception/module-rest": "^1.0" + }, + "suggest": { + "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + } + ], + "description": "Codeception module for testing web application over HTTP", + "homepage": "http://codeception.com/", + "keywords": [ + "codeception", + "functional-testing", + "http" + ], + "time": "2022-05-21T13:50:41+00:00" + }, + { + "name": "codeception/module-rest", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-rest.git", + "reference": "9cd7a87fd9343494e7782f7bdb51687c25046917" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-rest/zipball/9cd7a87fd9343494e7782f7bdb51687c25046917", + "reference": "9cd7a87fd9343494e7782f7bdb51687c25046917", + "shasum": "" + }, + "require": { + "codeception/codeception": "^4.0", + "justinrainbow/json-schema": "~5.2.9", + "php": ">=5.6.6 <9.0", + "softcreatr/jsonpath": "^0.5 || ^0.7" + }, + "require-dev": { + "codeception/lib-innerbrowser": "^1.0", + "codeception/util-universalframework": "^1.0" + }, + "suggest": { + "aws/aws-sdk-php": "For using AWS Auth" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gintautas Miselis" + } + ], + "description": "REST module for Codeception", + "homepage": "http://codeception.com/", + "keywords": [ + "codeception", + "rest" + ], + "time": "2021-11-18T18:58:15+00:00" + }, + { + "name": "codeception/module-webdriver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-webdriver.git", + "reference": "baa18b7bf70aa024012f967b5ce5021e1faa9151" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-webdriver/zipball/baa18b7bf70aa024012f967b5ce5021e1faa9151", + "reference": "baa18b7bf70aa024012f967b5ce5021e1faa9151", + "shasum": "" + }, + "require": { + "codeception/codeception": "^4.0", + "php": ">=5.6.0 <9.0", + "php-webdriver/webdriver": "^1.8.0" + }, + "suggest": { + "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + }, + { + "name": "Zaahid Bateson" + } + ], + "description": "WebDriver module for Codeception", + "homepage": "http://codeception.com/", + "keywords": [ + "acceptance-testing", + "browser-testing", + "codeception" + ], + "time": "2021-09-02T12:01:02+00:00" + }, + { + "name": "codeception/phpunit-wrapper", + "version": "6.8.4", + "source": { + "type": "git", + "url": "https://github.com/Codeception/phpunit-wrapper.git", + "reference": "6267fb4e647da0e708b3aef181ff679a64433d67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/6267fb4e647da0e708b3aef181ff679a64433d67", + "reference": "6267fb4e647da0e708b3aef181ff679a64433d67", + "shasum": "" + }, + "require": { + "phpunit/php-code-coverage": ">=4.0.4 <6.0", + "phpunit/phpunit": ">=6.5.13 <7.0", + "sebastian/comparator": ">=1.2.4 <3.0", + "sebastian/diff": ">=1.4 <4.0" + }, + "require-dev": { + "codeception/specify": "*", + "vlucas/phpdotenv": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codeception\\PHPUnit\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Davert", + "email": "davert.php@resend.cc" + } + ], + "description": "PHPUnit classes used by Codeception", + "time": "2022-05-23T05:56:13+00:00" + }, + { + "name": "codeception/stub", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Stub.git", + "reference": "eea518711d736eab838c1274593c4568ec06b23d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/eea518711d736eab838c1274593c4568ec06b23d", + "reference": "eea518711d736eab838c1274593c4568ec06b23d", + "shasum": "" + }, + "require": { + "codeception/phpunit-wrapper": "^6.6.1 | ^7.7.1 | ^8.0.3", + "phpunit/phpunit": ">=6.5 <9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codeception\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", + "time": "2019-08-10T16:20:53+00:00" + }, + { + "name": "codeception/util-universalframework", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/util-universalframework.git", + "reference": "cc381f364c6d24f9b9c7b70a4c724949725f491a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/util-universalframework/zipball/cc381f364c6d24f9b9c7b70a4c724949725f491a", + "reference": "cc381f364c6d24f9b9c7b70a4c724949725f491a", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gintautas Miselis" + } + ], + "description": "Mock framework module used in internal Codeception tests", + "homepage": "http://codeception.com/", + "time": "2019-09-22T06:06:49+00:00" + }, + { + "name": "dg/mysql-dump", + "version": "v1.5.1", + "source": { + "type": "git", + "url": "https://github.com/dg/MySQL-dump.git", + "reference": "e0e287b715b43293773a8b0edf8514f606e01780" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dg/MySQL-dump/zipball/e0e287b715b43293773a8b0edf8514f606e01780", + "reference": "e0e287b715b43293773a8b0edf8514f606e01780", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "http://davidgrudl.com" + } + ], + "description": "MySQL database dump.", + "homepage": "https://github.com/dg/MySQL-dump", + "keywords": [ + "mysql" + ], + "time": "2019-09-10T21:36:25+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^4.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "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": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "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%2Finflector", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:16:43+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "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.22" + }, + "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" + ], + "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-03-03T08:28:38+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.4.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1dd98b0564cb3f6bd16ce683cb755f94c10fbd82", + "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2022-06-20T22:16:13+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:56:57+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "13388f00956b1503577598873fffb5ae994b5737" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/13388f00956b1503577598873fffb5ae994b5737", + "reference": "13388f00956b1503577598873fffb5ae994b5737", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2022-06-20T21:43:11+00:00" + }, + { + "name": "illuminate/collections", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "705a4e1ef93cd492c45b9b3e7911cccc990a07f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/705a4e1ef93cd492c45b9b3e7911cccc990a07f4", + "reference": "705a4e1ef93cd492c45b9b3e7911cccc990a07f4", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^5.4)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "time": "2022-06-23T15:29:49+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "5e0fd287a1b22a6b346a9f7cd484d8cf0234585d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/5e0fd287a1b22a6b346a9f7cd484d8cf0234585d", + "reference": "5e0fd287a1b22a6b346a9f7cd484d8cf0234585d", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0", + "psr/container": "^1.0", + "psr/simple-cache": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "time": "2022-01-13T14:47:47+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "aed81891a6e046fdee72edd497f822190f61c162" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/aed81891a6e046fdee72edd497f822190f61c162", + "reference": "aed81891a6e046fdee72edd497f822190f61c162", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "time": "2021-11-16T13:57:03+00:00" + }, + { + "name": "illuminate/support", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "c3d643e77082786ae8a51502c757e9b1a3ee254e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/c3d643e77082786ae8a51502c757e9b1a3ee254e", + "reference": "c3d643e77082786ae8a51502c757e9b1a3ee254e", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^1.4|^2.0", + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "nesbot/carbon": "^2.53.1", + "php": "^7.3|^8.0", + "voku/portable-ascii": "^1.6.1" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "suggest": { + "illuminate/filesystem": "Required to use the composer class (^8.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^1.3|^2.0.2).", + "ramsey/uuid": "Required to use Str::uuid() (^4.2.2).", + "symfony/process": "Required to use the composer class (^5.4).", + "symfony/var-dumper": "Required to use the dd function (^5.4).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "time": "2022-06-27T13:26:30+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "5.2.12", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", + "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "time": "2022-04-13T08:02:27+00:00" + }, + { + "name": "lucatume/wp-browser", + "version": "3.1.6", + "source": { + "type": "git", + "url": "https://github.com/lucatume/wp-browser.git", + "reference": "5cefdab50d16f69447c48e5a8668d67d4892d6ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lucatume/wp-browser/zipball/5cefdab50d16f69447c48e5a8668d67d4892d6ef", + "reference": "5cefdab50d16f69447c48e5a8668d67d4892d6ef", + "shasum": "" + }, + "require": { + "antecedent/patchwork": "^2.0", + "bordoni/phpass": "^0.3", + "codeception/codeception": "^2.5 || ^3.0 || ^4.0", + "dg/mysql-dump": "^1.3", + "ext-fileinfo": "*", + "ext-json": "*", + "ext-pdo": "*", + "mikehaertl/php-shellcommand": "^1.6", + "mikemclin/laravel-wp-password": "~2.0.0", + "php": ">=5.6.0", + "vria/nodiacritic": "^0.1.2", + "wp-cli/wp-cli": ">=2.0 <3.0.0", + "zordius/lightncandy": "^1.2" + }, + "conflict": { + "codeception/module-asserts": ">=2.0", + "codeception/module-cli": ">=2.0", + "codeception/module-db": ">=2.0", + "codeception/module-filesystem": ">=2.0", + "codeception/module-phpbrowser": ">=2.0", + "codeception/module-webdriver": ">=2.0", + "codeception/util-universalframework": ">=2.0" + }, + "require-dev": { + "erusev/parsedown": "^1.7", + "ext-pcntl": "*", + "ext-sockets": "*", + "gumlet/php-image-resize": "^1.6", + "lucatume/codeception-snapshot-assertions": "^0.2", + "mikey179/vfsstream": "^1.6", + "victorjonsson/markdowndocs": "dev-master", + "vlucas/phpdotenv": "^3.0", + "wp-cli/wp-cli-bundle": "*" + }, + "suggest": { + "codeception/module-asserts:^1.0": "Codeception 4.0 compatibility.", + "codeception/module-cli:^1.0": "Codeception 4.0 compatibility; required by the WPCLI module.", + "codeception/module-db:^1.0": "Codeception 4.0 compatibility; required by the WPDb module, PHP 5.6 compatible version.", + "codeception/module-filesystem:^1.0": "Codeception 4.0 compatibility; required by the WPFilesystem module.", + "codeception/module-phpbrowser:^1.0": "Codeception 4.0 compatibility; required by the WPBrowser module.", + "codeception/module-webdriver:^1.0": "Codeception 4.0 compatibility; required by the WPWebDriver module.", + "codeception/util-universalframework:^1.0": "Codeception 4.0 compatibility; required by the WordPress framework module.", + "gumlet/php-image-resize": "To handle runtime image modification in the WPDb::haveAttachmentInDatabase method.", + "vlucas/phpdotenv:^4.0": "To manage more complex environment file based configuration of the suites." + }, + "type": "library", + "extra": { + "_hash": "484f861f69198089cab0e642f27e5653" + }, + "autoload": { + "files": [ + "src/tad/WPBrowser/utils.php", + "src/tad/WPBrowser/wp-polyfills.php" + ], + "psr-4": { + "tad\\": "src/tad", + "Codeception\\": "src/Codeception", + "lucatume\\WPBrowser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "theAverageDev (Luca Tumedei)", + "email": "luca@theaveragedev.com", + "homepage": "http://theaveragedev.com", + "role": "Developer" + } + ], + "description": "WordPress extension of the PhpBrowser class.", + "homepage": "http://github.com/lucatume/wp-browser", + "keywords": [ + "codeception", + "wordpress" + ], + "funding": [ + { + "url": "https://github.com/lucatume", + "type": "github" + } + ], + "time": "2022-04-28T06:45:08+00:00" + }, + { + "name": "mikehaertl/php-shellcommand", + "version": "1.6.4", + "source": { + "type": "git", + "url": "https://github.com/mikehaertl/php-shellcommand.git", + "reference": "3488d7803df1e8f1a343d3d0ca452d527ad8d5e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikehaertl/php-shellcommand/zipball/3488d7803df1e8f1a343d3d0ca452d527ad8d5e5", + "reference": "3488d7803df1e8f1a343d3d0ca452d527ad8d5e5", + "shasum": "" + }, + "require": { + "php": ">= 5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">4.0 <=9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "mikehaertl\\shellcommand\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Härtl", + "email": "haertl.mike@gmail.com" + } + ], + "description": "An object oriented interface to shell commands", + "keywords": [ + "shell" + ], + "time": "2021-03-17T06:54:33+00:00" + }, + { + "name": "mikemclin/laravel-wp-password", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/mikemclin/laravel-wp-password.git", + "reference": "5225c95f75aa0a5ad4040ec2074d1c8d7f10b5f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikemclin/laravel-wp-password/zipball/5225c95f75aa0a5ad4040ec2074d1c8d7f10b5f4", + "reference": "5225c95f75aa0a5ad4040ec2074d1c8d7f10b5f4", + "shasum": "" + }, + "require": { + "bordoni/phpass": "0.3.*", + "illuminate/support": ">=4.0.0", + "php": ">=5.3.0" + }, + "replace": { + "mikemclin/laravel-wp-password": "self.version" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "^2.2" + }, + "type": "laravel-package", + "extra": { + "laravel": { + "providers": [ + "MikeMcLin\\WpPassword\\WpPasswordProvider" + ], + "aliases": { + "WpPassword": "MikeMcLin\\WpPassword\\Facades\\WpPassword" + } + } + }, + "autoload": { + "psr-4": { + "MikeMcLin\\WpPassword\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike McLin", + "email": "mike@mikemclin.com", + "homepage": "http://mikemclin.net" + } + ], + "description": "Laravel package that checks and creates WordPress password hashes", + "homepage": "https://github.com/mikemclin/laravel-wp-password", + "keywords": [ + "hashing", + "laravel", + "password", + "wordpress" + ], + "time": "2021-09-30T13:48:57+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.2", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "e62b7c3849d22ec55f3ec425507bf7968193a6cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/e62b7c3849d22ec55f3ec425507bf7968193a6cb", + "reference": "e62b7c3849d22ec55f3ec425507bf7968193a6cb", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "time": "2022-08-23T13:07:01+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" + ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2022-03-03T13:19:32+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.61.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "bdf4f4fe3a3eac4de84dbec0738082a862c68ba6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bdf4f4fe3a3eac4de84dbec0738082a862c68ba6", + "reference": "bdf4f4fe3a3eac4de84dbec0738082a862c68ba6", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.0", + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "*", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2022-08-06T12:41:24+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.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)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.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", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "php-stubs/wordpress-stubs", + "version": "v6.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-stubs.git", + "reference": "e04781a84e364615a7b5f70fdc345c8253ca5b8f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/e04781a84e364615a7b5f70fdc345c8253ca5b8f", + "reference": "e04781a84e364615a7b5f70fdc345c8253ca5b8f", + "shasum": "" + }, + "replace": { + "giacocorsiglia/wordpress-stubs": "*" + }, + "require-dev": { + "nikic/php-parser": "< 4.12.0", + "php": "~7.3 || ~8.0", + "php-stubs/generator": "^0.8.1", + "phpdocumentor/reflection-docblock": "^5.3", + "phpstan/phpstan": "^1.2" + }, + "suggest": { + "paragonie/sodium_compat": "Pure PHP implementation of libsodium", + "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wordpress-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress" + ], + "time": "2022-07-15T11:10:45+00:00" + }, + { + "name": "php-webdriver/webdriver", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/php-webdriver/php-webdriver.git", + "reference": "b27ddf458d273c7d4602106fcaf978aa0b7fe15a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/b27ddf458d273c7d4602106fcaf978aa0b7fe15a", + "reference": "b27ddf458d273c7d4602106fcaf978aa0b7fe15a", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-zip": "*", + "php": "^5.6 || ~7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.12", + "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0" + }, + "replace": { + "facebook/webdriver": "*" + }, + "require-dev": { + "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0", + "php-coveralls/php-coveralls": "^2.4", + "php-mock/php-mock-phpunit": "^1.1 || ^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3.5", + "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0" + }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Exception/TimeoutException.php" + ], + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", + "homepage": "https://github.com/php-webdriver/php-webdriver", + "keywords": [ + "Chromedriver", + "geckodriver", + "php", + "selenium", + "webdriver" + ], + "time": "2022-05-03T12:16:34+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "77a32518733312af16a44300404e945338981de3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2022-03-15T21:29:03+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "c53312ecc575caf07b0e90dee43883fdf90ca67c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c53312ecc575caf07b0e90dee43883fdf90ca67c", + "reference": "c53312ecc575caf07b0e90dee43883fdf90ca67c", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-07-20T09:57:31+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3.x-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" + ], + "time": "2018-04-06T15:36:58+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.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" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.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", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "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": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "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": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "abandoned": true, + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "6.5.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.9", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5.x-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": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-02-01T05:22:47+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "5.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.11" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-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": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2018-08-09T05:50:03+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" + ], + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "rmccue/requests", + "version": "v1.8.1", + "source": { + "type": "git", + "url": "https://github.com/WordPress/Requests.git", + "reference": "82e6936366eac3af4d836c18b9d8c31028fe4cd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/Requests/zipball/82e6936366eac3af4d836c18b9d8c31028fe4cd5", + "reference": "82e6936366eac3af4d836c18b9d8c31028fe4cd5", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "php-parallel-lint/php-console-highlighter": "^0.5.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcompatibility/php-compatibility": "^9.0", + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5", + "requests/test-server": "dev-master", + "squizlabs/php_codesniffer": "^3.5", + "wp-coding-standards/wpcs": "^2.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Requests": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Ryan McCue", + "homepage": "http://ryanmccue.info" + } + ], + "description": "A HTTP library written in PHP, for human beings.", + "homepage": "http://github.com/WordPress/Requests", + "keywords": [ + "curl", + "fsockopen", + "http", + "idna", + "ipv6", + "iri", + "sockets" + ], + "time": "2021-06-04T09:56:25+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-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/", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:15:22+00:00" + }, + { + "name": "sebastian/comparator", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-02-01T13:46:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "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": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-08-03T08:09:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-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" + ], + "time": "2017-07-01T08:51:00+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", + "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-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": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-11-11T13:51:24+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "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": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-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/", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:40:27+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-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/", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:37:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-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", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:34:24+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-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", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "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": "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", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "softcreatr/jsonpath", + "version": "0.7.5", + "source": { + "type": "git", + "url": "https://github.com/SoftCreatR/JSONPath.git", + "reference": "008569bf80aa3584834f7890781576bc7b65afa7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SoftCreatR/JSONPath/zipball/008569bf80aa3584834f7890781576bc7b65afa7", + "reference": "008569bf80aa3584834f7890781576bc7b65afa7", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.1" + }, + "replace": { + "flow/jsonpath": "*" + }, + "require-dev": { + "phpunit/phpunit": ">=7.0", + "roave/security-advisories": "dev-master", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Flow\\JSONPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stephen Frank", + "email": "stephen@flowsa.com", + "homepage": "https://prismaticbytes.com", + "role": "Developer" + }, + { + "name": "Sascha Greuel", + "email": "hello@1-2.dev", + "homepage": "http://1-2.dev", + "role": "Developer" + } + ], + "description": "JSONPath implementation for parsing, searching and flattening arrays", + "funding": [ + { + "url": "https://github.com/softcreatr", + "type": "github" + } + ], + "time": "2021-06-02T22:15:26+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "081fe28a26b6bd671dea85ef3a4b5003f3c88027" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/081fe28a26b6bd671dea85ef3a4b5003f3c88027", + "reference": "081fe28a26b6bd671dea85ef3a4b5003f3c88027", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/dom-crawler": "^4.4|^5.0|^6.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "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": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", + "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-27T15:50:05+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "535846c7ee6bc4dd027ca0d93220601456734b10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/535846c7ee6bc4dd027ca0d93220601456734b10", + "reference": "535846c7ee6bc4dd027ca0d93220601456734b10", + "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" + ], + "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-22T10:42:43+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "c1681789f059ab756001052164726ae88512ae3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/c1681789f059ab756001052164726ae88512ae3d", + "reference": "c1681789f059ab756001052164726ae88512ae3d", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "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/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", + "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/dom-crawler", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "0b900ca5576ecd59e08c76127e616667cfe427a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/0b900ca5576ecd59e08c76127e616667cfe427a7", + "reference": "0b900ca5576ecd59e08c76127e616667cfe427a7", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "masterminds/html5": "<2.6" + }, + "require-dev": { + "masterminds/html5": "^2.6", + "symfony/css-selector": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "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 DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "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/event-dispatcher", + "version": "v5.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "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", + "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-05T16:45:39+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" + ], + "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/finder", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", + "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", + "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-29T07:37:50+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-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" + ], + "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-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "433d05519ce6990bf3530fba6957499d327395c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-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" + ], + "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-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-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" + ], + "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-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-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" + ], + "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-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-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" + ], + "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-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-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" + ], + "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-10T07:21:04+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", + "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" + ], + "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/string", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/5eb661e49ad389e4ae2b6e4df8d783a8a6548322", + "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322", + "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" + ], + "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-24T16:15:25+00:00" + }, + { + "name": "symfony/translation", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "7a1a8f6bbff269f434a83343a0a5d36a4f8cfa21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/7a1a8f6bbff269f434a83343a0a5d36a4f8cfa21", + "reference": "7a1a8f6bbff269f434a83343a0a5d36a4f8cfa21", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation-contracts": "^2.3" + }, + "conflict": { + "symfony/config": "<4.4", + "symfony/console": "<5.3", + "symfony/dependency-injection": "<5.0", + "symfony/http-kernel": "<5.0", + "symfony/twig-bundle": "<5.0", + "symfony/yaml": "<4.4" + }, + "provide": { + "symfony/translation-implementation": "2.3" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-kernel": "^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "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 to internationalize your application", + "homepage": "https://symfony.com", + "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/translation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-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\\Translation\\": "" + } + }, + "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 translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "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/yaml", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/05d4ea560f3402c6c116afd99fdc66e60eda227e", + "reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.3" + }, + "require-dev": { + "symfony/console": "^5.3|^6.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "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": "szepeviktor/phpstan-wordpress", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/szepeviktor/phpstan-wordpress.git", + "reference": "e487dc725845ac914681e148d0e8ac479ad887ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/e487dc725845ac914681e148d0e8ac479ad887ec", + "reference": "e487dc725845ac914681e148d0e8ac479ad887ec", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0", + "phpstan/phpstan": "^1.6", + "symfony/polyfill-php73": "^1.12.0" + }, + "require-dev": { + "composer/composer": "^2.1.14", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "phpunit/phpunit": "^8 || ^9", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "SzepeViktor\\PHPStan\\WordPress\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress extensions for PHPStan", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "static analysis", + "wordpress" + ], + "funding": [ + { + "url": "https://www.paypal.me/szepeviktor", + "type": "custom" + } + ], + "time": "2022-06-21T10:51:00+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", + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "87337c91b9dfacee02452244ee14ab3c43bc485a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/87337c91b9dfacee02452244ee14ab3c43bc485a", + "reference": "87337c91b9dfacee02452244ee14ab3c43bc485a", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2022-01-24T18:55:24+00:00" + }, + { + "name": "vria/nodiacritic", + "version": "0.1.2", + "source": { + "type": "git", + "url": "https://github.com/vria/nodiacritic.git", + "reference": "3efeb60fb2586fe3ce8ff0f3c122d380717b8b07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vria/nodiacritic/zipball/3efeb60fb2586fe3ce8ff0f3c122d380717b8b07", + "reference": "3efeb60fb2586fe3ce8ff0f3c122d380717b8b07", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "VRia\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0" + ], + "authors": [ + { + "name": "Riabchenko Vlad", + "email": "contact@vria.eu", + "homepage": "http://vria.eu" + } + ], + "description": "Tiny helper function that removes all diacritical signs from characters", + "homepage": "https://github.com/vria/nodiacritic", + "keywords": [ + "accent", + "diacritic", + "filter", + "string", + "text" + ], + "time": "2016-09-17T22:03:11+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "wp-cli/mustangostang-spyc", + "version": "0.6.3", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/spyc.git", + "reference": "6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/spyc/zipball/6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7", + "reference": "6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "autoload": { + "files": [ + "includes/functions.php" + ], + "psr-4": { + "Mustangostang\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP (WP-CLI fork)", + "homepage": "https://github.com/mustangostang/spyc/", + "time": "2017-04-25T11:26:20+00:00" + }, + { + "name": "wp-cli/php-cli-tools", + "version": "v0.11.14", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/php-cli-tools.git", + "reference": "f8f340e4a87687549d046e2da516242f7f36c934" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/f8f340e4a87687549d046e2da516242f7f36c934", + "reference": "f8f340e4a87687549d046e2da516242f7f36c934", + "shasum": "" + }, + "require": { + "php": ">= 5.3.0" + }, + "type": "library", + "autoload": { + "files": [ + "lib/cli/cli.php" + ], + "psr-0": { + "cli": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@handbuilt.co", + "role": "Maintainer" + }, + { + "name": "James Logsdon", + "email": "jlogsdon@php.net", + "role": "Developer" + } + ], + "description": "Console utilities for PHP", + "homepage": "http://github.com/wp-cli/php-cli-tools", + "keywords": [ + "cli", + "console" + ], + "time": "2022-07-04T21:44:34+00:00" + }, + { + "name": "wp-cli/wp-cli", + "version": "v2.6.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/wp-cli.git", + "reference": "dee13c2baf6bf972484a63f8b8dab48f7220f095" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/dee13c2baf6bf972484a63f8b8dab48f7220f095", + "reference": "dee13c2baf6bf972484a63f8b8dab48f7220f095", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "mustache/mustache": "^2.14.1", + "php": "^5.6 || ^7.0 || ^8.0", + "rmccue/requests": "^1.8", + "symfony/finder": ">2.7", + "wp-cli/mustangostang-spyc": "^0.6.3", + "wp-cli/php-cli-tools": "~0.11.2" + }, + "require-dev": { + "roave/security-advisories": "dev-latest", + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.2 || ^2", + "wp-cli/extension-command": "^1.1 || ^2", + "wp-cli/package-command": "^1 || ^2", + "wp-cli/wp-cli-tests": "^3.1.3" + }, + "suggest": { + "ext-readline": "Include for a better --prompt implementation", + "ext-zip": "Needed to support extraction of ZIP archives when doing downloads or updates" + }, + "bin": [ + "bin/wp", + "bin/wp.bat" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "WP_CLI\\": "php/" + }, + "classmap": [ + "php/class-wp-cli.php", + "php/class-wp-cli-command.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WP-CLI framework", + "homepage": "https://wp-cli.org", + "keywords": [ + "cli", + "wordpress" + ], + "time": "2022-01-25T16:31:27+00:00" + }, + { + "name": "zordius/lightncandy", + "version": "v1.2.6", + "source": { + "type": "git", + "url": "https://github.com/zordius/lightncandy.git", + "reference": "b451f73e8b5c73e62e365997ba3c993a0376b72a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zordius/lightncandy/zipball/b451f73e8b5c73e62e365997ba3c993a0376b72a", + "reference": "b451f73e8b5c73e62e365997ba3c993a0376b72a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": ">=7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.5-dev" + } + }, + "autoload": { + "psr-4": { + "LightnCandy\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Zordius Chen", + "email": "zordius@gmail.com" + } + ], + "description": "An extremely fast PHP implementation of handlebars ( http://handlebarsjs.com/ ) and mustache ( http://mustache.github.io/ ).", + "homepage": "https://github.com/zordius/lightncandy", + "keywords": [ + "handlebars", + "logicless", + "mustache", + "php", + "template" + ], + "time": "2021-07-11T04:52:41+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "1.1.0" +} diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..98a5aea --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,28 @@ +# Configuration for PHPStan +# https://phpstan.org/config-reference + +includes: + # @see https://github.com/phpstan/phpstan-src/blob/master/conf/bleedingEdge.neon + - phar://phpstan.phar/conf/bleedingEdge.neon + # Include this extension + - vendor/szepeviktor/phpstan-wordpress/extension.neon + +parameters: + parallel: + jobSize: 10 + maximumNumberOfProcesses: 32 + minimumNumberOfJobsPerProcess: 2 + level: 5 + inferPrivatePropertyTypeFromConstructor: true + reportUnmatchedIgnoredErrors: false + checkGenericClassInNonGenericObjectType: false + + # Paths to be analyzed. + paths: + - %currentWorkingDirectory%/src + + ignoreErrors: + # Uses func_get_args() + - '#^Function add_query_arg invoked with [123] parameters?, 0 required\.$#' + # Uses func_get_args() + - '#^Function apply_filters(_ref_array)? invoked with [34567] parameters, 2 required\.$#' diff --git a/src/DB/DB.php b/src/DB/DB.php new file mode 100644 index 0000000..882a94e --- /dev/null +++ b/src/DB/DB.php @@ -0,0 +1,295 @@ +getVar( 'stellarwp_db_initialized', false ) ) { + return; + } + + $container->register( Database\Provider::class ); + $container->setVar( 'stellarwp_db_initialized', true ); + } + + /** + * Runs the dbDelta function and returns a WP_Error with any errors that occurred during the process + * + * @see dbDelta() for parameter and return details + * + * @since 2.9.2 + * + * @param $delta + * + * @return array + * @throws DatabaseQueryException + */ + public static function delta( $delta ) { + return self::runQueryWithErrorChecking( + function () use ( $delta ) { + return dbDelta( $delta ); + } + ); + } + + /** + * A convenience method for the $wpdb->prepare method + * + * @see WPDB::prepare() for usage details + * + * @since 2.9.6 + * + * @param string $query + * @param mixed ...$args + * + * @return false|mixed + */ + public static function prepare( $query, ...$args ) { + global $wpdb; + + return $wpdb->prepare( $query, ...$args ); + } + + /** + * Magic method which calls the static method on the $wpdb while performing error checking + * + * @since 2.22.0 add givewp_db_pre_query action + * @since 2.9.6 + * + * @param $name + * @param $arguments + * + * @return mixed + * @throws DatabaseQueryException + */ + public static function __callStatic( $name, $arguments ) { + return self::runQueryWithErrorChecking( + static function () use ( $name, $arguments ) { + global $wpdb; + + if ( in_array( $name, [ 'get_row', 'get_col', 'get_results', 'query' ], true) ) { + /** + * Allow for hooking just before query execution. + * + * @since 1.0.0 + * + * @param string $argument + */ + do_action( 'stellarwp_db_pre_query', current( $arguments ) ); + } + + return call_user_func_array( [ $wpdb, $name ], $arguments ); + } + ); + } + + /** + * Get last insert ID + * + * @since 2.10.0 + * @return int + */ + public static function last_insert_id() { + global $wpdb; + + return $wpdb->insert_id; + } + + /** + * Prefix given table name with $wpdb->prefix + * + * @param string $tableName + * + * @return string + */ + public static function prefix( $tableName ) { + global $wpdb; + + return $wpdb->prefix . $tableName; + } + + /** + * Create QueryBuilder instance + * + * @param string $table + * @param null|string $alias + * + * @return QueryBuilder + */ + public static function table( $table, $alias = null ) { + $builder = new QueryBuilder(); + $builder->from( $table, $alias ); + + return $builder; + } + + /** + * Runs a transaction. If the callable works then the transaction is committed. If the callable throws an exception + * then the transaction is rolled back. + * + * @since 2.19.6 + * + * @param callable $callback + * + * @return void + * @throws Exception + */ + public static function transaction( callable $callback ) { + self::beginTransaction(); + + try { + $callback(); + } catch (Exception $e) { + self::rollback(); + throw $e; + } + + self::commit(); + } + + /** + * Manually starts a transaction + * + * @since 2.19.6 + * + * @return void + */ + public static function beginTransaction() { + global $wpdb; + $wpdb->query( 'START TRANSACTION' ); + } + + /** + * Manually rolls back a transaction + * + * @since 2.19.6 + * + * @return void + */ + public static function rollback() { + global $wpdb; + $wpdb->query( 'ROLLBACK' ); + } + + /** + * Manually commits a transaction + * + * @since 2.19.6 + * + * @return void + */ + public static function commit() { + global $wpdb; + $wpdb->query( 'COMMIT' ); + } + + /** + * Used as a flag to tell QueryBuilder not to process the provided SQL + * If $args are provided, we will assume that dev wants to use DB::prepare method with raw SQL + * + * @param string $sql + * @param array ...$args + * + * @return RawSQL + */ + public static function raw( $sql, ...$args ) { + return new RawSQL( $sql, ...$args ); + } + + /** + * Runs a query callable and checks to see if any unique SQL errors occurred when it was run + * + * @since 2.9.2 + * + * @param Callable $queryCaller + * + * @return mixed + * @throws DatabaseQueryException + */ + private static function runQueryWithErrorChecking( $queryCaller ) { + global $wpdb, $EZSQL_ERROR; + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + + $errorCount = is_array( $EZSQL_ERROR ) ? count( $EZSQL_ERROR ) : 0; + $hasShowErrors = $wpdb->hide_errors(); + + $output = $queryCaller(); + + if ( $hasShowErrors ) { + $wpdb->show_errors(); + } + + $wpError = self::getQueryErrors( $errorCount ); + + if ( ! empty( $wpError->errors ) ) { + throw new DatabaseQueryException( $wpdb->last_query, $wpError->errors ); + } + + return $output; + } + + /** + * Retrieves the SQL errors stored by WordPress + * + * @since 2.9.2 + * + * @param int $initialCount + * + * @return WP_Error + */ + private static function getQueryErrors( $initialCount = 0 ) { + global $EZSQL_ERROR; + + $wpError = new WP_Error(); + + if ( is_array( $EZSQL_ERROR ) ) { + for ( $index = $initialCount, $indexMax = count( $EZSQL_ERROR ); $index < $indexMax; $index++ ) { + $error = $EZSQL_ERROR[ $index ]; + + if ( empty( $error['error_str'] ) || empty( $error['query'] ) || 0 === strpos( + $error['query'], + 'DESCRIBE ' + ) ) { + continue; + } + + $wpError->add( 'db_delta_error', $error['error_str'] ); + } + } + + return $wpError; + } +} diff --git a/src/DB/Database/Actions/EnableBigSqlSelects.php b/src/DB/Database/Actions/EnableBigSqlSelects.php new file mode 100644 index 0000000..bf7c7d6 --- /dev/null +++ b/src/DB/Database/Actions/EnableBigSqlSelects.php @@ -0,0 +1,32 @@ +query( 'SET SESSION SQL_BIG_SELECTS=1;' ); + + $bigSelects = true; + } + } +} diff --git a/src/DB/Database/Exceptions/DatabaseQueryException.php b/src/DB/Database/Exceptions/DatabaseQueryException.php new file mode 100644 index 0000000..ac8f5de --- /dev/null +++ b/src/DB/Database/Exceptions/DatabaseQueryException.php @@ -0,0 +1,56 @@ +query = $query; + $this->queryErrors = $queryErrors; + + parent::__construct( $message, $code, $previous ); + } + + /** + * Returns the query errors + * + * @since 1.0.0 + * + * @return string[] + */ + public function getQueryErrors(): array { + return $this->queryErrors; + } + + public function getQuery(): string { + return $this->query; + } +} diff --git a/src/DB/Database/Provider.php b/src/DB/Database/Provider.php new file mode 100644 index 0000000..09e1b8a --- /dev/null +++ b/src/DB/Database/Provider.php @@ -0,0 +1,29 @@ +container->singleton( static::class, $this ); + $this->container->singleton( Actions\EnableBigSqlSelects::class, Actions\EnableBigSqlSelects::class ); + $this->register_hooks(); + } + + /** + * Registers all hooks. + * + * @since 1.0.0 + */ + private function register_hooks() : void { + add_action( 'stellarwp_db_pre_query', App::callback( Actions\EnableBigSqlSelects::class, 'set_var' ) ); + } +} diff --git a/src/DB/Provider.php b/src/DB/Provider.php new file mode 100644 index 0000000..e69de29 diff --git a/src/DB/QueryBuilder/Clauses/From.php b/src/DB/QueryBuilder/Clauses/From.php new file mode 100644 index 0000000..607c88f --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/From.php @@ -0,0 +1,29 @@ +table = QueryBuilder::prefixTable( $table ); + $this->alias = trim( $alias ); + } +} diff --git a/src/DB/QueryBuilder/Clauses/Having.php b/src/DB/QueryBuilder/Clauses/Having.php new file mode 100644 index 0000000..d914e4b --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/Having.php @@ -0,0 +1,121 @@ +column = trim( $column ); + $this->comparisonOperator = $this->getComparisonOperator( $comparisonOperator ); + $this->value = $value; + $this->logicalOperator = $logicalOperator ? $this->getLogicalOperator( $logicalOperator ) : ''; + $this->mathFunction = $this->getMathFunction( $mathFunction ); + } + + /** + * @param string $logicalOperator + * + * @return string + */ + private function getLogicalOperator( $logicalOperator ) { + $operators = [ + Operator::_AND, + Operator::_OR + ]; + + $logicalOperator = strtoupper( $logicalOperator ); + + if ( ! in_array( $logicalOperator, $operators, true ) ) { + throw new InvalidArgumentException( + sprintf( + 'Unsupported logical operator %s. Please use one of the supported operators (%s)', + $logicalOperator, + implode( ',', $operators ) + ) + ); + } + + return $logicalOperator; + } + + /** + * @param string $comparisonOperator + * + * @return string + */ + private function getComparisonOperator( $comparisonOperator ) { + $operators = [ + '<', + '<=', + '>', + '>=', + '<>', + '!=', + '=' + ]; + + if ( ! in_array( $comparisonOperator, $operators, true ) ) { + throw new InvalidArgumentException( + sprintf( + 'Unsupported comparison operator %s. Please use one of the supported operators (%s)', + $comparisonOperator, + implode( ',', $operators ) + ) + ); + } + + return $comparisonOperator; + } + + + /** + * @param string $mathFunction + * + * @return string + */ + private function getMathFunction( $mathFunction ) { + if ( array_key_exists( $mathFunction, Math::getTypes() ) ) { + return $mathFunction; + } + + return null; + } +} diff --git a/src/DB/QueryBuilder/Clauses/Join.php b/src/DB/QueryBuilder/Clauses/Join.php new file mode 100644 index 0000000..137a70c --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/Join.php @@ -0,0 +1,59 @@ +table = QueryBuilder::prefixTable( $table ); + $this->joinType = $this->getJoinType( $joinType ); + $this->alias = trim( $alias ); + } + + /** + * @param string $type + * + * @return string + */ + private function getJoinType( $type ) { + $type = strtoupper( $type ); + + if ( array_key_exists( $type, JoinType::getTypes() ) ) { + return $type; + } + + throw new InvalidArgumentException( + sprintf( + 'Join type %s is not supported. Please provide one of the supported join types (%s)', + $type, + implode( ',', JoinType::getTypes() ) + ) + ); + } +} diff --git a/src/DB/QueryBuilder/Clauses/JoinCondition.php b/src/DB/QueryBuilder/Clauses/JoinCondition.php new file mode 100644 index 0000000..b5f020e --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/JoinCondition.php @@ -0,0 +1,72 @@ +logicalOperator = $this->getLogicalOperator( $logicalOperator ); + $this->column1 = trim( $column1 ); + $this->column2 = trim( $column2 ); + $this->quote = $quote; + } + + /** + * @param string $operator + * + * @return string + */ + private function getLogicalOperator( $operator ) { + $operator = strtoupper( $operator ); + + $supportedOperators = [ + Operator::ON, + Operator::_AND, + Operator::_OR + ]; + + if ( ! in_array( $operator, $supportedOperators, true ) ) { + throw new InvalidArgumentException( + sprintf( + 'Unsupported logical operator %s. Please provide one of the supported operators (%s)', + $operator, + implode( ',', $supportedOperators ) + ) + ); + } + + return $operator; + } +} diff --git a/src/DB/QueryBuilder/Clauses/MetaTable.php b/src/DB/QueryBuilder/Clauses/MetaTable.php new file mode 100644 index 0000000..49a617c --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/MetaTable.php @@ -0,0 +1,36 @@ +tableName = QueryBuilder::prefixTable( $table ); + $this->keyColumnName = trim( $metaKeyColumnName ); + $this->valueColumnName = trim( $metaValueColumnName ); + } +} diff --git a/src/DB/QueryBuilder/Clauses/OrderBy.php b/src/DB/QueryBuilder/Clauses/OrderBy.php new file mode 100644 index 0000000..e228e91 --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/OrderBy.php @@ -0,0 +1,51 @@ +column = trim( $column ); + $this->direction = $this->getSortDirection( $direction ); + } + + /** + * @param string $direction + * + * @return string + */ + private function getSortDirection( $direction ) { + $direction = strtoupper( $direction ); + $directions = ['ASC', 'DESC']; + + if ( ! in_array( $direction, $directions, true ) ) { + throw new InvalidArgumentException( + sprintf( + 'Unsupported sort direction %s. Please use one of the (%s)', + $direction, + implode( ',', $directions ) + ) + ); + } + + return $direction; + } +} diff --git a/src/DB/QueryBuilder/Clauses/RawSQL.php b/src/DB/QueryBuilder/Clauses/RawSQL.php new file mode 100644 index 0000000..27650f5 --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/RawSQL.php @@ -0,0 +1,23 @@ +sql = $args ? DB::prepare( $sql, $args ) : $sql; + } +} diff --git a/src/DB/QueryBuilder/Clauses/Select.php b/src/DB/QueryBuilder/Clauses/Select.php new file mode 100644 index 0000000..500838f --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/Select.php @@ -0,0 +1,27 @@ +column = trim( $column ); + $this->alias = trim( $alias ); + } +} diff --git a/src/DB/QueryBuilder/Clauses/Union.php b/src/DB/QueryBuilder/Clauses/Union.php new file mode 100644 index 0000000..8d5f663 --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/Union.php @@ -0,0 +1,29 @@ +builder = $builder; + $this->all = $all; + } +} diff --git a/src/DB/QueryBuilder/Clauses/Where.php b/src/DB/QueryBuilder/Clauses/Where.php new file mode 100644 index 0000000..d113f2c --- /dev/null +++ b/src/DB/QueryBuilder/Clauses/Where.php @@ -0,0 +1,112 @@ +column = trim( $column ); + $this->value = $value; + $this->comparisonOperator = $this->getComparisonOperator( $comparisonOperator ); + $this->logicalOperator = $logicalOperator ? $this->getLogicalOperator( $logicalOperator ) : ''; + } + + /** + * @param string $comparisonOperator + * + * @return string + */ + private function getComparisonOperator( $comparisonOperator ) { + $operators = [ + '<', + '<=', + '>', + '>=', + '<>', + '!=', + '=', + Operator::LIKE, + Operator::NOTLIKE, + Operator::IN, + Operator::NOTIN, + Operator::BETWEEN, + Operator::NOTBETWEEN, + Operator::ISNULL, + Operator::NOTNULL + ]; + + if ( ! in_array( $comparisonOperator, $operators, true ) ) { + throw new InvalidArgumentException( + sprintf( + 'Unsupported comparison operator %s. Please use one of the supported operators (%s)', + $comparisonOperator, + implode( ',', $operators ) + ) + ); + } + + return $comparisonOperator; + } + + /** + * @param string $logicalOperator + * + * @return string + */ + private function getLogicalOperator( $logicalOperator ) { + $operators = [ + Operator::_AND, + Operator::_OR + ]; + + $logicalOperator = strtoupper( $logicalOperator ); + + if ( ! in_array( $logicalOperator, $operators, true ) ) { + throw new InvalidArgumentException( + sprintf( + 'Unsupported logical operator %s. Please use one of the supported operators (%s)', + $logicalOperator, + implode( ',', $operators ) + ) + ); + } + + return $logicalOperator; + } +} diff --git a/src/DB/QueryBuilder/Concerns/Aggregate.php b/src/DB/QueryBuilder/Concerns/Aggregate.php new file mode 100644 index 0000000..7c58b37 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/Aggregate.php @@ -0,0 +1,82 @@ +selects[] = new RawSQL( 'SELECT COUNT(%1s) AS count', $column ); + + return +$this->get()->count; + } + + /** + * Returns the total sum in a set of values + * + * @since 2.19.0 + * @param string $column + * + * @return int|float + */ + public function sum( $column ) { + $this->selects[] = new RawSQL( 'SELECT SUM(%1s) AS sum', $column ); + + return +$this->get()->sum; + } + + /** + * Get the average value in a set of values + * + * @since 2.19.0 + * @param string $column + * + * @return int|float + */ + public function avg( $column ) { + $this->selects[] = new RawSQL( 'SELECT AVG(%1s) AS avg', $column ); + + return +$this->get()->avg; + } + + /** + * Returns the minimum value in a set of values + * + * @since 2.19.0 + * @param string $column + * + * @return int|float + */ + public function min( $column ) { + $this->selects[] = new RawSQL( 'SELECT MIN(%1s) AS min', $column ); + + return +$this->get()->min; + } + + /** + * Returns the maximum value in a set of values + * + * @since 2.19.0 + * @param string $column + * + * @return int|float + */ + public function max( $column ) { + $this->selects[] = new RawSQL( 'SELECT MAX(%1s) AS max', $column ); + + return +$this->get()->max; + } +} diff --git a/src/DB/QueryBuilder/Concerns/CRUD.php b/src/DB/QueryBuilder/Concerns/CRUD.php new file mode 100644 index 0000000..4ca1fc0 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/CRUD.php @@ -0,0 +1,115 @@ +getTable(), + $data, + $format + ); + } + + /** + * @see https://developer.wordpress.org/reference/classes/wpdb/update/ + * + * @since 2.19.0 + * + * @param null $format + * + * @param array $data + * @return false|int + * + */ + public function update( $data, $format = null ) { + return DB::update( + $this->getTable(), + $data, + $this->getWhere(), + $format, + null + ); + } + + /** + * @since 2.19.0 + * + * @return false|int + * + * @see https://developer.wordpress.org/reference/classes/wpdb/delete/ + */ + public function delete() { + return DB::delete( + $this->getTable(), + $this->getWhere(), + null + ); + } + + /** + * Get results + * + * @since 2.19.0 + * + * @param string ARRAY_A|ARRAY_N|OBJECT|OBJECT_K $output + * + * @return array|object|null + */ + public function getAll( $output = OBJECT ) { + return DB::get_results( $this->getSQL(), $output ); + } + + /** + * Get row + * + * @since 2.19.0 + * + * @param string ARRAY_A|ARRAY_N|OBJECT|OBJECT_K $output + * + * @return array|object|null + */ + public function get( $output = OBJECT ) { + return DB::get_row( $this->getSQL(), $output ); + } + + /** + * @since 2.19.0 + * + * @return string + */ + private function getTable() { + return $this->froms[0]->table; + } + + /** + * @since 2.19.0 + * + * @return array[] + */ + private function getWhere() { + $wheres = []; + + foreach ( $this->wheres as $where ) { + $wheres[ $where->column ] = $where->value; + } + + return $wheres; + } +} diff --git a/src/DB/QueryBuilder/Concerns/FromClause.php b/src/DB/QueryBuilder/Concerns/FromClause.php new file mode 100644 index 0000000..c3eb80d --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/FromClause.php @@ -0,0 +1,55 @@ +froms[] = new From( $table, $alias ); + + return $this; + } + + /** + * @return array|string[] + */ + protected function getFromSQL() { + if ( empty( $this->froms ) ) { + return []; + } + + return [ + 'FROM ' . implode( + ', ', + array_map( function ( From $from ) { + if ( $from->alias ) { + return DB::prepare( + '%1s AS %2s', + $from->table, + $from->alias + ); + } + + return DB::prepare( '%1s', $from->table ); + }, $this->froms ) + ) + ]; + } +} diff --git a/src/DB/QueryBuilder/Concerns/GroupByStatement.php b/src/DB/QueryBuilder/Concerns/GroupByStatement.php new file mode 100644 index 0000000..e0293b6 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/GroupByStatement.php @@ -0,0 +1,32 @@ +groupByColumns, true ) ) { + $this->groupByColumns[] = DB::prepare( '%1s', $tableColumn ); + } + + return $this; + } + + protected function getGroupBySQL() { + return ! empty( $this->groupByColumns ) + ? [ 'GROUP BY ' . implode( ',', $this->groupByColumns ) ] + : []; + } +} diff --git a/src/DB/QueryBuilder/Concerns/HavingClause.php b/src/DB/QueryBuilder/Concerns/HavingClause.php new file mode 100644 index 0000000..ad03695 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/HavingClause.php @@ -0,0 +1,296 @@ +havings[] = new Having( + $column, + $comparisonOperator, + $value, + empty( $this->havings ) ? null : Operator::_AND, + $mathFunction + ); + + return $this; + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string $value + * @param null|string $mathFunction \StellarWP\DB\QueryBuilder\Types\Math + * + * @return $this + */ + public function orHaving( $column, $comparisonOperator, $value, $mathFunction = null ) { + $this->havings[] = new Having( + $column, + $comparisonOperator, + $value, + empty( $this->havings ) ? null : Operator::_OR, + $mathFunction + ); + + return $this; + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function havingCount( $column, $comparisonOperator, $value ) { + return $this->having( + $column, + $comparisonOperator, + $value, + Math::COUNT + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function orHavingCount( $column, $comparisonOperator, $value ) { + return $this->orHaving( + $column, + $comparisonOperator, + $value, + Math::COUNT + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function havingMin( $column, $comparisonOperator, $value ) { + return $this->having( + $column, + $comparisonOperator, + $value, + Math::MIN + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function orHavingMin( $column, $comparisonOperator, $value ) { + return $this->orHaving( + $column, + $comparisonOperator, + $value, + Math::MIN + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function havingMax( $column, $comparisonOperator, $value ) { + return $this->having( + $column, + $comparisonOperator, + $value, + Math::MAX + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function orHavingMax( $column, $comparisonOperator, $value ) { + return $this->orHaving( + $column, + $comparisonOperator, + $value, + Math::MAX + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function havingAvg( $column, $comparisonOperator, $value ) { + return $this->having( + $column, + $comparisonOperator, + $value, + Math::AVG + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function orHavingAvg( $column, $comparisonOperator, $value ) { + return $this->orHaving( + $column, + $comparisonOperator, + $value, + Math::AVG + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function havingSum( $column, $comparisonOperator, $value ) { + return $this->having( + $column, + $comparisonOperator, + $value, + Math::SUM + ); + } + + /** + * @param string $column + * @param string $comparisonOperator + * @param string|int $value + * + * @return $this + */ + public function orHavingSum( $column, $comparisonOperator, $value ) { + return $this->orHaving( + $column, + $comparisonOperator, + $value, + Math::SUM + ); + } + + /** + * Add raw SQL HAVING clause + * + * @param string $sql + * @param ...$args + * + * @return $this + */ + public function havingRaw( $sql, ...$args ) { + $this->havings[] = new RawSQL( $sql, $args ); + + return $this; + } + + /** + * @return string[] + */ + protected function getHavingSQL() { + if ( empty( $this->havings ) ) { + return []; + } + + $havings = []; + + foreach ( $this->havings as $i => $having ) { + if ( $having instanceof RawSQL ) { + if ( $i === 0 ) { + // If the first element is an instance of RawSQL + // then we don't need the starting HAVING keyword because we assume that the dev will include that in RawSQL + $this->includeHavingKeyword = false; + } + $havings[] = $having->sql; + continue; + } + + $havings[] = $this->buildHavingSQL( $having ); + } + + if ( $this->includeHavingKeyword ) { + return array_merge( ['HAVING'], $havings ); + } + + return $havings; + } + + /** + * @param Having $having + * + * @return string + */ + private function buildHavingSQL( Having $having ) { + if ( $having->mathFunction ) { + return DB::prepare( + "%1s %2s(%3s) %4s %s", + $having->logicalOperator, + $having->mathFunction, + $having->column, + $having->comparisonOperator, + $having->value + ); + } + + return DB::prepare( + "%1s %s %3s %s", + $having->logicalOperator, + $having->column, + $having->comparisonOperator, + $having->value + ); + } +} diff --git a/src/DB/QueryBuilder/Concerns/JoinClause.php b/src/DB/QueryBuilder/Concerns/JoinClause.php new file mode 100644 index 0000000..d6913c6 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/JoinClause.php @@ -0,0 +1,160 @@ +joins[] = $callback; + + return $this; + } + + /** + * @param string|RawSQL $table + * @param string $column1 + * @param string $column2 + * @param string|null $alias + * + * @return $this + */ + public function leftJoin( $table, $column1, $column2, $alias = null ) { + $this->join( + function ( JoinQueryBuilder $builder ) use ( $table, $column1, $column2, $alias ) { + $builder + ->leftJoin( $table, $alias ) + ->on( $column1, $column2 ); + } + ); + + return $this; + } + + /** + * @param string|RawSQL $table + * @param string $column1 + * @param string $column2 + * @param string|null $alias + * + * @return $this + */ + public function innerJoin( $table, $column1, $column2, $alias = null ) { + $this->join( + function ( JoinQueryBuilder $builder ) use ( $table, $column1, $column2, $alias ) { + $builder + ->innerJoin( $table, $alias ) + ->on( $column1, $column2 ); + } + ); + + return $this; + } + + /** + * @param string|RawSQL $table + * @param string $column1 + * @param string $column2 + * @param string|null $alias + * + * @return $this + */ + public function rightJoin( $table, $column1, $column2, $alias = null ) { + $this->join( + function ( JoinQueryBuilder $builder ) use ( $table, $column1, $column2, $alias ) { + $builder + ->rightJoin( $table, $alias ) + ->on( $column1, $column2 ); + } + ); + + return $this; + } + + + /** + * Add raw SQL JOIN clause + * + * @param string $sql + * @param ...$args + * + * @return $this; + */ + public function joinRaw( $sql, ...$args ) { + $this->joins[] = new RawSQL( $sql, $args ); + + return $this; + } + + + /** + * @return string[] + */ + protected function getJoinSQL() { + return array_map(function ( $callback ) { + if ( $callback instanceof RawSQL ) { + return $callback->sql; + } + + $builder = new JoinQueryBuilder(); + + call_user_func( $callback, $builder ); + + $joins = array_map( function ( $join ) { + if ( $join instanceof RawSQL ) { + return $join->sql; + } + + if ( $join instanceof Join ) { + if ( $join->alias ) { + return DB::prepare( + '%1s JOIN %2s %3s', + $join->joinType, + $join->table, + $join->alias + ); + } + + return DB::prepare( + '%1s JOIN %2s', + $join->joinType, + $join->table + ); + } + + // JoinCondition + return DB::prepare( + $join->quote + ? ' %1s %2s = %s' + : ' %1s %2s = %3s', + $join->logicalOperator, + $join->column1, + $join->column2 + ); + }, $builder->getDefinedJoins() ); + + return implode( ' ', $joins ); + }, $this->joins ); + } +} diff --git a/src/DB/QueryBuilder/Concerns/LimitStatement.php b/src/DB/QueryBuilder/Concerns/LimitStatement.php new file mode 100644 index 0000000..44fda3c --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/LimitStatement.php @@ -0,0 +1,30 @@ +limit = (int) $limit; + + return $this; + } + + protected function getLimitSQL() { + return $this->limit + ? [ "LIMIT {$this->limit}" ] + : []; + } +} diff --git a/src/DB/QueryBuilder/Concerns/MetaQuery.php b/src/DB/QueryBuilder/Concerns/MetaQuery.php new file mode 100644 index 0000000..2f88b8b --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/MetaQuery.php @@ -0,0 +1,135 @@ +metaTablesConfigs[] = new MetaTable( + $table, + $metaKeyColumn, + $metaValueColumn + ); + + return $this; + } + + /** + * @param string|RawSQL $table + * + * @return MetaTable + */ + protected function getMetaTable( $table ) { + $tableName = QueryBuilder::prefixTable( $table ); + + foreach ( $this->metaTablesConfigs as $metaTable ) { + if ( $metaTable->tableName === $tableName ) { + return $metaTable; + } + } + + return new MetaTable( + $table, + $this->defaultMetaKeyColumn, + $this->defaultMetaValueColumn + ); + } + + /** + * Select meta columns + * + * @since 2.21.1 optimize group concat functionality + * @since 2.19.6 add group concat functionality + * @since 2.19.0 + * + * @param string|RawSQL $table + * @param string $foreignKey + * @param string $primaryKey + * @param array $columns + * + * @return $this + */ + public function attachMeta( $table, $foreignKey, $primaryKey, ...$columns ) { + $metaTable = $this->getMetaTable( $table ); + + foreach ( $columns as $i => $definition ) { + if ( is_array( $definition ) ) { + list( $column, $columnAlias, $concat ) = array_pad( $definition, 3, false ); + } else { + $column = $definition; + $columnAlias = $concat = false; + } + + // Set dynamic alias + $tableAlias = sprintf('%s_%s_%d', ( $table instanceof RawSQL ) ? $table->sql : $table, 'attach_meta', $i); + + // Check if we have meta columns that dev wants to group concat + if ( $concat ) { + /** + * Include foreign key to prevent errors if sql_mode is only_full_group_by + * + * @see https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html + */ + $this->groupBy( $foreignKey ); + + // Group concat same key values into faux array + // @example key => ["value1", "value2"] + $this->selectRaw( + "CONCAT('[',GROUP_CONCAT(DISTINCT CONCAT('\"',%1s,'\"')),']') AS %2s", + $tableAlias . '.' . $metaTable->valueColumnName, + $columnAlias ?: $column + ); + } else { + $this->select( [ "{$tableAlias}.{$metaTable->valueColumnName}", $columnAlias ?: $column ] ); + } + + $this->join( + function ( JoinQueryBuilder $builder ) use ( + $table, + $foreignKey, + $primaryKey, + $tableAlias, + $column, + $metaTable + ) { + $builder + ->leftJoin( $table, $tableAlias ) + ->on( $foreignKey, "{$tableAlias}.{$primaryKey}" ) + ->andOn( "{$tableAlias}.{$metaTable->keyColumnName}", $column, true ); + } + ); + } + + return $this; + } +} diff --git a/src/DB/QueryBuilder/Concerns/OffsetStatement.php b/src/DB/QueryBuilder/Concerns/OffsetStatement.php new file mode 100644 index 0000000..97c53f9 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/OffsetStatement.php @@ -0,0 +1,30 @@ +offset = (int) $offset; + + return $this; + } + + protected function getOffsetSQL() { + return $this->limit && $this->offset + ? [ "OFFSET {$this->offset}" ] + : []; + } +} diff --git a/src/DB/QueryBuilder/Concerns/OrderByStatement.php b/src/DB/QueryBuilder/Concerns/OrderByStatement.php new file mode 100644 index 0000000..a2437c2 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/OrderByStatement.php @@ -0,0 +1,47 @@ +orderBys[] = new OrderBy( $column, $direction ); + + return $this; + } + + /** + * @return array|string[] + */ + protected function getOrderBySQL() { + if ( empty( $this->orderBys ) ) { + return []; + } + + $orderBys = implode( + ', ', + array_map( function ( OrderBy $order ) { + return DB::prepare( '%1s %2s', $order->column, $order->direction ); + }, $this->orderBys ) + ); + + + return [ 'ORDER BY ' . $orderBys ]; + } +} diff --git a/src/DB/QueryBuilder/Concerns/SelectStatement.php b/src/DB/QueryBuilder/Concerns/SelectStatement.php new file mode 100644 index 0000000..d9820dd --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/SelectStatement.php @@ -0,0 +1,122 @@ +selects = array_merge( $this->selects, $selects ); + + return $this; + } + + /** + * Add raw SQL SELECT statement + * + * @param string $sql + * @param ...$args + */ + public function selectRaw( $sql, ...$args ) { + $this->selects[] = new RawSQL( $sql, $args ); + + return $this; + } + + + /** + * Select distinct + * + * @return $this + */ + public function distinct() { + $this->distinct = true; + + return $this; + } + + /** + * @return string[] + */ + protected function getSelectSQL() { + // Select all by default + if ( empty( $this->selects ) ) { + $this->select( '*' ); + } + + $selects = []; + + foreach ( $this->selects as $i => $select ) { + if ( $select instanceof RawSQL ) { + if ( $i === 0 ) { + // If the first element is an instance of RawSQL + // then we don't need the starting SELECT keyword because we assume that the dev will include that in RawSQL + $this->includeSelectKeyword = false; + } + $selects[] = $select->sql; + continue; + } + + if ( $select->alias ) { + $selects[] = DB::prepare( + '%1s AS %2s', + $select->column, + $select->alias + ); + + continue; + } + + $selects[] = DB::prepare( '%1s', $select->column ); + } + + $selectStatements = implode( ', ', $selects ); + + if ( $this->includeSelectKeyword ) { + $keyword = 'SELECT '; + + if ( $this->distinct ) { + $keyword .= 'DISTINCT '; + } + + return [ $keyword . $selectStatements ]; + } + + return [ $selectStatements ]; + } +} diff --git a/src/DB/QueryBuilder/Concerns/TablePrefix.php b/src/DB/QueryBuilder/Concerns/TablePrefix.php new file mode 100644 index 0000000..458f2f2 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/TablePrefix.php @@ -0,0 +1,35 @@ + $wpdb->users, + 'usermeta' => $wpdb->usermeta, + ]; + + if ( $table instanceof RawSQL ) { + return $table->sql; + } + + if ( array_key_exists( $table, $sharedTables ) ) { + return $sharedTables[ $table ]; + } + + return $wpdb->prefix . $table; + } +} diff --git a/src/DB/QueryBuilder/Concerns/UnionOperator.php b/src/DB/QueryBuilder/Concerns/UnionOperator.php new file mode 100644 index 0000000..6b0d364 --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/UnionOperator.php @@ -0,0 +1,55 @@ +unions = array_map( function ( QueryBuilder $builder ) { + return new Union( $builder ); + }, $union ); + + return $this; + } + + /** + * @param QueryBuilder $union + * + * @return $this + */ + public function unionAll( ...$union ) { + $this->unions = array_map( function ( QueryBuilder $builder ) { + return new Union( $builder, true ); + }, $union ); + + return $this; + } + + /** + * @return array|string[] + */ + protected function getUnionSQL() { + if ( empty( $this->unions ) ) { + return []; + } + + return array_map( function ( Union $union ) { + return ( $union->all ? 'UNION ALL ' : 'UNION ' ) . $union->builder->getSQL(); + }, $this->unions ); + } +} diff --git a/src/DB/QueryBuilder/Concerns/WhereClause.php b/src/DB/QueryBuilder/Concerns/WhereClause.php new file mode 100644 index 0000000..ab947da --- /dev/null +++ b/src/DB/QueryBuilder/Concerns/WhereClause.php @@ -0,0 +1,488 @@ +getSQL(); + array_shift( $wheres ); + + $this->wheres[] = sprintf( + "%s (%s)", + empty( $this->wheres ) ? null : $logicalOperator, + implode( ' ', $wheres ) + ); + } // If the value is a Closure instance, we will assume the developer is performing an entire sub-select within the query + elseif ( $value instanceof Closure ) { + $builder = new QueryBuilder(); + call_user_func( $value, $builder ); + + $this->wheres[] = sprintf( + "%s %s %s (%s)", + empty( $this->wheres ) ? null : $logicalOperator, + $column, + $comparisonOperator, + $builder->getSQL() + ); + } // Standard WHERE clause + else { + $this->wheres[] = new Where( + $column, + $value, + $comparisonOperator, + empty( $this->wheres ) ? null : $logicalOperator + ); + } + + return $this; + } + + /** + * @param string|Closure $column The closure will receive a StellarWP\DB\QueryBuilder\WhereQueryBuilder instance + * @param string|Closure|array|null $value The closure will receive a StellarWP\DB\QueryBuilder\QueryBuilder instance + * @param string $comparisonOperator + * + * @return $this + */ + public function where( $column, $value = null, $comparisonOperator = '=' ) { + return $this->setWhere( + $column, + $value, + $comparisonOperator, + Operator::_AND + ); + } + + /** + * @param string|Closure $column + * @param string|Closure|array|null $value + * @param string $comparisonOperator + * + * @return $this + */ + public function orWhere( $column, $value = null, $comparisonOperator = '=' ) { + return $this->setWhere( + $column, + $value, + $comparisonOperator, + Operator::_OR + ); + } + + /** + * @param string $column + * @param array|Closure $value + * + * @return $this + */ + public function whereIn( $column, $value ) { + return $this->where( + $column, + $value, + Operator::IN + ); + } + + /** + * @param string $column + * @param array|Closure $value + * + * @return $this + */ + public function orWhereIn( $column, $value ) { + return $this->orWhere( + $column, + $value, + Operator::IN + ); + } + + /** + * @param string $column + * @param array|Closure $value + * + * @return $this + */ + public function whereNotIn( $column, $value ) { + return $this->where( + $column, + $value, + Operator::NOTIN + ); + } + + /** + * @param string $column + * @param array|Closure $value + * + * @return $this + */ + public function orWhereNotIn( $column, $value ) { + return $this->orWhere( + $column, + $value, + Operator::NOTIN + ); + } + + /** + * @param string $column + * @param string|int $min + * @param string|int $max + * + * @return $this + */ + public function whereBetween( $column, $min, $max ) { + return $this->where( + $column, + [ $min, $max ], + Operator::BETWEEN + ); + } + + /** + * @param string $column + * @param string|int $min + * @param string|int $max + * + * @return $this + */ + public function whereNotBetween( $column, $min, $max ) { + return $this->where( + $column, + [ $min, $max ], + Operator::NOTBETWEEN + ); + } + + /** + * @param string $column + * @param string|int $min + * @param string|int $max + * + * @return $this + */ + public function orWhereBetween( $column, $min, $max ) { + return $this->orWhere( + $column, + [ $min, $max ], + Operator::BETWEEN + ); + } + + /** + * @param string $column + * @param string|int $min + * @param string|int $max + * + * @return $this + */ + public function orWhereNotBetween( $column, $min, $max ) { + return $this->orWhere( + $column, + [ $min, $max ], + Operator::NOTBETWEEN + ); + } + + /** + * @param string $column + * @param string $value + * + * @return $this + */ + public function whereLike( $column, $value ) { + return $this->where( + $column, + $value, + Operator::LIKE + ); + } + + /** + * @param string $column + * @param string $value + * + * @return $this + */ + public function whereNotLike( $column, $value ) { + return $this->where( + $column, + $value, + Operator::NOTLIKE + ); + } + + /** + * @param string $column + * @param string $value + * + * @return $this + */ + public function orWhereLike( $column, $value ) { + return $this->orWhere( + $column, + $value, + Operator::LIKE + ); + } + + /** + * @param string $column + * @param string $value + * + * @return $this + */ + public function orWhereNotLike( $column, $value ) { + return $this->orWhere( + $column, + $value, + Operator::NOTLIKE + ); + } + + /** + * @param string $column + * + * @return $this + */ + public function whereIsNull( $column ) { + return $this->where( + $column, + null, + Operator::ISNULL + ); + } + + /** + * @param string $column + * + * @return $this + */ + public function orWhereIsNull( $column ) { + return $this->orWhere( + $column, + null, + Operator::ISNULL + ); + } + + /** + * @param string $column + * + * @return $this + */ + public function whereIsNotNull( $column ) { + return $this->where( + $column, + null, + Operator::NOTNULL + ); + } + + /** + * @param string $column + * + * @return $this + */ + public function orWhereIsNotNull( $column ) { + return $this->orWhere( + $column, + null, + Operator::NOTNULL + ); + } + + + /** + * @param Closure $callback The closure will receive a StellarWP\DB\QueryBuilder\QueryBuilder instance + * + * @return QueryBuilder|WhereQueryBuilder + */ + public function whereExists( $callback ) { + return $this->where( + null, + $callback, + Operator::EXISTS + ); + } + + /** + * @param Closure $callback The closure will receive a StellarWP\DB\QueryBuilder\QueryBuilder instance + * + * @return QueryBuilder|WhereQueryBuilder + */ + public function whereNotExists( $callback ) { + return $this->where( + null, + $callback, + Operator::NOTEXISTS + ); + } + + /** + * Add raw SQL WHERE clause + * + * @param $sql + * @param ...$args + * + * @return $this + */ + public function whereRaw( $sql, ...$args ) { + $this->wheres[] = new RawSQL( $sql, $args ); + + return $this; + } + + /** + * @return string[] + */ + protected function getWhereSQL() { + // Bailout + if ( empty( $this->wheres ) ) { + return []; + } + + $wheres = []; + + foreach ( $this->wheres as $i => $where ) { + if ( $where instanceof RawSQL ) { + if ( $i === 0 ) { + // If the first element is an instance of RawSQL + // then we don't need the starting WHERE keyword because we assume that the dev will include that in RawSQL + $this->includeWhereKeyword = false; + } + $wheres[] = $where->sql; + continue; + } + + if ( $where instanceof Where ) { + $wheres[] = $this->buildWhereSQL( $where ); + continue; + } + + // If the variable $where is not an instance of the Where class + // it means the SQL is already generated by the Query Builder, so we just return that + $wheres[] = $where; + } + + + if ( $this->includeWhereKeyword ) { + return array_merge( ['WHERE'], $wheres ); + } + + return $wheres; + } + + /** + * @param Where $where + * + * @return string + */ + private function buildWhereSQL( Where $where ) { + switch ( $where->comparisonOperator ) { + // Handle membership conditions + case Operator::IN: + case Operator::NOTIN: + + return DB::prepare( + "%1s %2s %3s", + $where->logicalOperator, + $where->column, + $where->comparisonOperator + ) . ' (' . implode( + ',', + array_map( function ( $where ) { + return DB::prepare( '%s', $where ); + }, $where->value ) + ) . ')'; + + // Handle BETWEEN conditions + case Operator::BETWEEN: + case Operator::NOTBETWEEN: + list( $min, $max ) = $where->value; + + return DB::prepare( + "%1s %2s %3s %s AND %s", + $where->logicalOperator, + $where->column, + $where->comparisonOperator, + $min, + $max + ); + + // Handle LIKE conditions + case Operator::LIKE: + case Operator::NOTLIKE: + return DB::prepare( + "%1s %2s %3s %s", + $where->logicalOperator, + $where->column, + $where->comparisonOperator, + strpos( $where->value, '%' ) !== false + ? $where->value + : sprintf( '%%%s%%', $where->value ) + ); + + // Handle NULL conditions + case Operator::ISNULL: + case Operator::NOTNULL: + return DB::prepare( + "%1s %2s %3s", + $where->logicalOperator, + $where->column, + $where->comparisonOperator + ); + + // Standard WHERE clause + default: + return DB::prepare( + "%1s %2s %3s %s", + $where->logicalOperator, + $where->column, + $where->comparisonOperator, + $where->value + ); + } + } +} diff --git a/src/DB/QueryBuilder/JoinQueryBuilder.php b/src/DB/QueryBuilder/JoinQueryBuilder.php new file mode 100644 index 0000000..81c8c9c --- /dev/null +++ b/src/DB/QueryBuilder/JoinQueryBuilder.php @@ -0,0 +1,166 @@ +join( + JoinType::LEFT, + $table, + $alias + ); + } + + /** + * @param string|RawSQL $table + * @param null|string $alias + * + * @return $this + */ + public function rightJoin( $table, $alias = null ) { + return $this->join( + JoinType::RIGHT, + $table, + $alias + ); + } + + /** + * @param string|RawSQL $table + * @param null|string $alias + * + * @return $this + */ + public function innerJoin( $table, $alias = null ) { + return $this->join( + JoinType::INNER, + $table, + $alias + ); + } + + /** + * @param string $column1 + * @param string $column2 + * @param bool $quote + * + * @return $this + */ + public function on( $column1, $column2, $quote = false ) { + return $this->joinCondition( + Operator::ON, + $column1, + $column2, + $quote + ); + } + + /** + * @param string $column1 + * @param string $column2 + * @param bool $quote + * + * @return $this + */ + public function andOn( $column1, $column2, $quote = null ) { + return $this->joinCondition( + Operator::_AND, + $column1, + $column2, + $quote + ); + } + + /** + * @param string $column1 + * @param string $column2 + * @param bool $quote + * + * @return $this + */ + public function orOn( $column1, $column2, $quote = null ) { + return $this->joinCondition( + Operator::_OR, + $column1, + $column2, + $quote + ); + } + + /** + * Add raw SQL JOIN clause + * + * @param string $sql + * @param ...$args + */ + public function joinRaw( $sql, ...$args ) { + $this->joins[] = new RawSQL( $sql, $args ); + } + + /** + * Add Join + * + * @param string $joinType + * @param string|RawSQL $table + * @param string $alias + * + * @return $this + */ + private function join( $joinType, $table, $alias ) { + $this->joins[] = new Join( + $joinType, + $table, + $alias + ); + + return $this; + } + + /** + * Add JoinCondition + * + * @param string $operator + * @param string $column1 + * @param string $column2 + * @param bool $quote + * + * @return $this + */ + private function joinCondition( $operator, $column1, $column2, $quote ) { + $this->joins[] = new JoinCondition( + $operator, + $column1, + $column2, + $quote + ); + + return $this; + } + + /** + * @return Join[]|JoinCondition[] + */ + public function getDefinedJoins() { + return $this->joins; + } +} diff --git a/src/DB/QueryBuilder/QueryBuilder.php b/src/DB/QueryBuilder/QueryBuilder.php new file mode 100644 index 0000000..89e9b84 --- /dev/null +++ b/src/DB/QueryBuilder/QueryBuilder.php @@ -0,0 +1,63 @@ +getSelectSQL(), + $this->getFromSQL(), + $this->getJoinSQL(), + $this->getWhereSQL(), + $this->getGroupBySQL(), + $this->getHavingSQL(), + $this->getOrderBySQL(), + $this->getLimitSQL(), + $this->getOffsetSQL(), + $this->getUnionSQL() + ); + + // Trim double spaces added by DB::prepare + return str_replace( + [ ' ', ' ' ], + ' ', + implode( ' ', $sql ) + ); + } +} diff --git a/src/DB/QueryBuilder/Types/JoinType.php b/src/DB/QueryBuilder/Types/JoinType.php new file mode 100644 index 0000000..89d5406 --- /dev/null +++ b/src/DB/QueryBuilder/Types/JoinType.php @@ -0,0 +1,12 @@ +getConstants(); + } +} diff --git a/src/DB/QueryBuilder/WhereQueryBuilder.php b/src/DB/QueryBuilder/WhereQueryBuilder.php new file mode 100644 index 0000000..55ae9c2 --- /dev/null +++ b/src/DB/QueryBuilder/WhereQueryBuilder.php @@ -0,0 +1,19 @@ +getWhereSQL(); + } +} diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/tests/_bootstrap.php @@ -0,0 +1 @@ + 'Query Builder Aggregate test 0', + ], + [ + 'post_title' => 'Query Builder Aggregate test 1', + ], + [ + 'post_title' => 'Query Builder Aggregate test 2', + ] + ]; + + foreach ($data as $row) { + DB::table('posts')->insert($row); + } + + $postsCount = DB::table('posts')->count(); + + $this->assertEquals(3, $postsCount); + + $nonExistentPostTypeCount = DB::table('posts') + ->where('post_type', 'dummy') + ->count(); + + $this->assertEquals(0, $nonExistentPostTypeCount); + } + + + /** + * @since 2.19.0 + * + * @return void + */ + public function testCountByColumnShouldReturnTheTotalNumberOfRecords() { + $data = [ + [ + 'donation_id' => 1, + 'meta_key' => 'donation_amount', + 'meta_value' => 10, + ], + [ + 'donation_id' => 2, + 'meta_key' => 'donation_amount', + 'meta_value' => 20, + ], + [ + 'donation_id' => 3, + 'meta_key' => 'donation_amount', + 'meta_value' => null, + ] + ]; + + foreach ($data as $row) { + DB::table('give_donationmeta')->insert($row); + } + + $postsCount = DB::table('give_donationmeta')->count('meta_key'); + + $this->assertEquals(3, $postsCount); + + $postsCountWithNullColumn = DB::table('give_donationmeta')->count('meta_value'); + + $this->assertEquals(2, $postsCountWithNullColumn); + } + + /** + * @since 2.19.0 + * + * @return void + */ + public function testSumShouldReturnTheSumOfColumns() { + $data = [ + [ + 'donation_id' => 1, + 'meta_key' => 'donation_amount', + 'meta_value' => 10, + ], + [ + 'donation_id' => 2, + 'meta_key' => 'donation_amount', + 'meta_value' => 20, + ], + [ + 'donation_id' => 3, + 'meta_key' => 'donation_amount', + 'meta_value' => 30, + ] + ]; + + foreach ($data as $row) { + DB::table('give_donationmeta')->insert($row); + } + + $totalSum = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->sum('meta_value'); + + $this->assertEquals(60, $totalSum); + } + + + /** + * @since 2.19.0 + * + * @return void + */ + public function testAvgShouldReturnTheAvgColumnValue() { + $data = [ + [ + 'donation_id' => 1, + 'meta_key' => 'donation_amount', + 'meta_value' => 10, + ], + [ + 'donation_id' => 2, + 'meta_key' => 'donation_amount', + 'meta_value' => 20, + ], + [ + 'donation_id' => 3, + 'meta_key' => 'donation_amount', + 'meta_value' => 30, + ] + ]; + + foreach ($data as $row) { + DB::table('give_donationmeta')->insert($row); + } + + $avgValue = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->avg('meta_value'); + + $this->assertEquals(20, $avgValue); + } + + /** + * @since 2.19.0 + * + * @return void + */ + public function testMinShouldReturnTheMinimumColumnValue() { + $data = [ + [ + 'donation_id' => 1, + 'meta_key' => 'donation_amount', + 'meta_value' => 10, + ], + [ + 'donation_id' => 2, + 'meta_key' => 'donation_amount', + 'meta_value' => 20, + ], + [ + 'donation_id' => 3, + 'meta_key' => 'donation_amount', + 'meta_value' => 30, + ] + ]; + + foreach ($data as $row) { + DB::table('give_donationmeta')->insert($row); + } + + $minValue = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->min('meta_value'); + + $this->assertEquals(10, $minValue); + } + + /** + * @since 2.19.0 + * + * @return void + */ + public function testMaxShouldReturnTheMaximumColumnValue() { + $data = [ + [ + 'donation_id' => 1, + 'meta_key' => 'donation_amount', + 'meta_value' => 10, + ], + [ + 'donation_id' => 2, + 'meta_key' => 'donation_amount', + 'meta_value' => 20, + ], + [ + 'donation_id' => 3, + 'meta_key' => 'donation_amount', + 'meta_value' => 30, + ] + ]; + + foreach ($data as $row) { + DB::table('give_donationmeta')->insert($row); + } + + $maxValue = DB::table('give_donationmeta') + ->where('meta_key', 'donation_amount') + ->max('meta_value'); + + $this->assertEquals(30, $maxValue); + } +} diff --git a/tests/wpunit/QueryBuilder/AttachMetaTest.php b/tests/wpunit/QueryBuilder/AttachMetaTest.php new file mode 100644 index 0000000..509f007 --- /dev/null +++ b/tests/wpunit/QueryBuilder/AttachMetaTest.php @@ -0,0 +1,109 @@ +from(DB::raw('wp_posts'), 'posts') + ->select( + ['posts.ID', 'id'], + ['posts.post_date', 'createdAt'] + ) + ->attachMeta( + DB::raw('wp_give_donationmeta'), + 'posts.ID', + 'donation_id', + ['_give_payment_total', 'amount'] + ) + ->leftJoin(DB::raw('wp_give_donationmeta'), 'posts.ID', 'donationMeta.donation_id', 'donationMeta') + ->where('posts.post_type', 'give_payment') + ->where('posts.post_status', 'give_subscription') + ->where('donationMeta.meta_key', 'subscription_id') + ->where('donationMeta.meta_value', 1) + ->orderBy('posts.post_date', 'DESC'); + + $this->assertContains( + "SELECT posts.ID AS id, posts.post_date AS createdAt, wp_give_donationmeta_attach_meta_0.meta_value AS amount FROM wp_posts AS posts LEFT JOIN wp_give_donationmeta wp_give_donationmeta_attach_meta_0 ON posts.ID = wp_give_donationmeta_attach_meta_0.donation_id AND wp_give_donationmeta_attach_meta_0.meta_key = '_give_payment_total' LEFT JOIN wp_give_donationmeta donationMeta ON posts.ID = donationMeta.donation_id WHERE posts.post_type = 'give_payment' AND posts.post_status = 'give_subscription' AND donationMeta.meta_key = 'subscription_id' AND donationMeta.meta_value = '1' ORDER BY posts.post_date DESC", + $builder->getSQL() + ); + } + + /** + * @since 2.19.6 + */ + public function testAttachMetaGroupConcatQuery() + { + $builder = new QueryBuilder(); + + $builder + ->from(DB::raw('wp_give_donors')) + ->select( + 'id', + 'email', + 'name' + ) + ->attachMeta( + DB::raw('wp_give_donormeta'), + 'id', + 'donor_id', + ['additional_email', 'additionalEmails', true] + ); + + $this->assertContains( + "SELECT id, email, name, CONCAT('[',GROUP_CONCAT(DISTINCT CONCAT('\"',wp_give_donormeta_attach_meta_0.meta_value,'\"')),']') AS additionalEmails FROM wp_give_donors LEFT JOIN wp_give_donormeta wp_give_donormeta_attach_meta_0 ON id = wp_give_donormeta_attach_meta_0.donor_id AND wp_give_donormeta_attach_meta_0.meta_key = 'additional_email' GROUP BY id", + $builder->getSQL() + ); + } + + /** + * @since 2.19.0 + */ + public function testConfigureMeta() + { + $builder = new QueryBuilder(); + + $builder + ->from(DB::raw('wp_posts'), 'posts') + ->select( + ['posts.ID', 'id'], + ['posts.post_date', 'createdAt'] + ) + ->configureMetaTable( + DB::raw('wp_give_donationmeta'), + 'custom_meta_key', + 'custom_meta_value' + ) + ->attachMeta( + DB::raw('wp_give_donationmeta'), + 'posts.ID', + 'donation_id', + ['_give_payment_total', 'amount'] + ) + ->leftJoin(DB::raw('wp_give_donationmeta'), 'posts.ID', 'donationMeta.donation_id', 'donationMeta') + ->where('posts.post_type', 'give_payment') + ->where('posts.post_status', 'give_subscription') + ->where('donationMeta.custom_meta_key', 'subscription_id') + ->where('donationMeta.custom_meta_value', 1); + + $this->assertContains( + "SELECT posts.ID AS id, posts.post_date AS createdAt, wp_give_donationmeta_attach_meta_0.custom_meta_value AS amount FROM wp_posts AS posts LEFT JOIN wp_give_donationmeta wp_give_donationmeta_attach_meta_0 ON posts.ID = wp_give_donationmeta_attach_meta_0.donation_id AND wp_give_donationmeta_attach_meta_0.custom_meta_key = '_give_payment_total' LEFT JOIN wp_give_donationmeta donationMeta ON posts.ID = donationMeta.donation_id WHERE posts.post_type = 'give_payment' AND posts.post_status = 'give_subscription' AND donationMeta.custom_meta_key = 'subscription_id' AND donationMeta.custom_meta_value = '1'", + $builder->getSQL() + ); + } +} diff --git a/tests/wpunit/QueryBuilder/CRUDTest.php b/tests/wpunit/QueryBuilder/CRUDTest.php new file mode 100644 index 0000000..9f19144 --- /dev/null +++ b/tests/wpunit/QueryBuilder/CRUDTest.php @@ -0,0 +1,132 @@ + 'Query Builder CRUD test', + 'post_type' => 'crud_test', + 'post_content' => 'Hello World!', + ]; + + DB::table('posts')->insert($data); + + $id = DB::last_insert_id(); + + $post = DB::table('posts') + ->select('post_title', 'post_type', 'post_content') + ->where('ID', $id) + ->get(); + + $this->assertEquals($data['post_title'], $post->post_title); + $this->assertEquals($data['post_type'], $post->post_type); + $this->assertEquals($data['post_content'], $post->post_content); + } + + /** + * @since 2.19.0 + * + * @return void + */ + public function testUpdateShouldUpdateRowValuesInDatabase() + { + $data = [ + 'post_title' => 'Query Builder CRUD test', + 'post_type' => 'crud_test', + 'post_content' => 'Hello World!', + ]; + + DB::table('posts')->insert($data); + + $id = DB::last_insert_id(); + + $updated = [ + 'post_title' => 'Query Builder CRUD test - UPDATED', + 'post_type' => 'crud_test-updated', + 'post_content' => 'Hello World! - UPDATED', + ]; + + DB::table('posts') + ->where('ID', $id) + ->update($updated); + + $post = DB::table('posts') + ->select('ID', 'post_title', 'post_type', 'post_content') + ->where('ID', $id) + ->get(); + + $this->assertEquals($id, $post->ID); + $this->assertEquals($updated['post_title'], $post->post_title); + $this->assertEquals($updated['post_type'], $post->post_type); + $this->assertEquals($updated['post_content'], $post->post_content); + } + + /** + * @since 2.19.0 + * + * @return void + */ + public function testDeleteShouldDeleteRowInDatabase() + { + $data = [ + 'post_title' => 'Query Builder CRUD test', + 'post_type' => 'crud_test', + 'post_content' => 'Hello World!', + ]; + + DB::table('posts')->insert($data); + + $id = DB::last_insert_id(); + + $post = DB::table('posts') + ->where('ID', $id) + ->get(); + + $this->assertNotNull($post); + + DB::table('posts') + ->where('ID', $id) + ->delete(); + + $post = DB::table('posts') + ->where('ID', $id) + ->get(); + + $this->assertNull($post); + } + +} diff --git a/tests/wpunit/QueryBuilder/FromTest.php b/tests/wpunit/QueryBuilder/FromTest.php new file mode 100644 index 0000000..283f3c7 --- /dev/null +++ b/tests/wpunit/QueryBuilder/FromTest.php @@ -0,0 +1,52 @@ +select('*') + ->from(DB::raw('posts')); + + $this->assertContains( + 'SELECT * FROM posts', + $builder->getSQL() + ); + } + + + public function testFromAlias() + { + $builder = new QueryBuilder(); + $builder + ->select('*') + ->from(DB::raw('posts'), 'donations'); + + $this->assertContains( + 'SELECT * FROM posts AS donations', + $builder->getSQL() + ); + } + + public function testMultipleFrom() + { + $builder = new QueryBuilder(); + $builder + ->select('*') + ->from(DB::raw('posts')) + ->from(DB::raw('postmeta')); + + $this->assertContains( + 'SELECT * FROM posts, postmeta', + $builder->getSQL() + ); + } + +} diff --git a/tests/wpunit/QueryBuilder/GroupByTest.php b/tests/wpunit/QueryBuilder/GroupByTest.php new file mode 100644 index 0000000..c153ba8 --- /dev/null +++ b/tests/wpunit/QueryBuilder/GroupByTest.php @@ -0,0 +1,25 @@ +select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->groupBy('ID'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' GROUP BY ID", + $builder->getSQL() + ); + } +} diff --git a/tests/wpunit/QueryBuilder/HavingTest.php b/tests/wpunit/QueryBuilder/HavingTest.php new file mode 100644 index 0000000..60be1f8 --- /dev/null +++ b/tests/wpunit/QueryBuilder/HavingTest.php @@ -0,0 +1,259 @@ +select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('id') + ->having('id', '>', 10); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY id HAVING 'id' > '10'", + $builder->getSQL() + ); + } + + public function testHavingCount() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingCount('ID', '>', 10); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING COUNT( ID) > '10'", + $builder->getSQL() + ); + } + + + public function testHavingSum() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingSum('post_count', '>', 1000); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING SUM(post_count) > '1000'", + $builder->getSQL() + ); + } + + + public function testHavingSumAnd() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingSum('post_count', '>', 1000) + ->havingSum('post_count', '<', 5000); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING SUM(post_count) > '1000' AND SUM(post_count) < '5000'", + $builder->getSQL() + ); + } + + + public function testOrHaving() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingSum('post_count', '>', 1000) + ->orHavingSum('post_count', '<', 100); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING SUM(post_count) > '1000' OR SUM(post_count) < '100'", + $builder->getSQL() + ); + } + + + public function testHavingMin() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingMin('post_count', '>', 1000); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING MIN(post_count) > '1000'", + $builder->getSQL() + ); + } + + + public function testOrHavingMin() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingMin('post_count', '>', 1000) + ->orHavingMin('post_count', '<', 100); // Doesn't make sense, but hey... + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING MIN(post_count) > '1000' OR MIN(post_count) < '100'", + $builder->getSQL() + ); + } + + + public function testHavingMax() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingMax('post_count', '>', 1000); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING MAX(post_count) > '1000'", + $builder->getSQL() + ); + } + + + public function testOrHavingMax() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingMax('post_count', '>', 1000) + ->orHavingMax('post_count', '<', 100); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING MAX(post_count) > '1000' OR MAX(post_count) < '100'", + $builder->getSQL() + ); + } + + + public function testHavingAvg() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingAvg('post_count', '>', 1000); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING AVG(post_count) > '1000'", + $builder->getSQL() + ); + } + + + public function testOrHavingAvg() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5) + ->groupBy('ID') + ->havingAvg('post_count', '<', 1000) + ->orHavingAvg('post_count', '>', 10000); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent = '5' GROUP BY ID HAVING AVG(post_count) < '1000' OR AVG(post_count) > '10000'", + $builder->getSQL() + ); + } + + public function testHavingRaw() + { + $builder = new QueryBuilder(); + + $builder + ->select('ID') + ->from(DB::raw('give_donations')) + ->groupBy('id') + ->havingRaw('HAVING COUNT(id) > %d', 1000); + + $this->assertContains( + "SELECT ID FROM give_donations GROUP BY id HAVING COUNT(id) > 1000", + $builder->getSQL() + ); + } + + + public function testHavingRawChain() + { + $builder = new QueryBuilder(); + + $builder + ->select('ID') + ->from(DB::raw('give_donations')) + ->groupBy('id') + ->havingRaw('HAVING COUNT(id) > %d', 1000) + ->orHavingAvg('id', '<', 400); + + $this->assertContains( + "SELECT ID FROM give_donations GROUP BY id HAVING COUNT(id) > 1000 OR AVG( id) < '400'", + $builder->getSQL() + ); + } + + + public function testReturnExceptionWhenBadComparisonArgumentIsUsed() { + $this->expectException( InvalidArgumentException::class ); + + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->groupBy('ID') + ->having('id', 'EQUALS TO', 10); + } +} diff --git a/tests/wpunit/QueryBuilder/JoinTest.php b/tests/wpunit/QueryBuilder/JoinTest.php new file mode 100644 index 0000000..2811e9f --- /dev/null +++ b/tests/wpunit/QueryBuilder/JoinTest.php @@ -0,0 +1,116 @@ +select('donationsTable.*', 'metaTable.*') + ->from(DB::raw('posts'), 'donationsTable') + ->leftJoin(DB::raw('give_donationmeta'), 'donationsTable.ID', 'metaTable.donation_id', 'metaTable'); + + $this->assertContains( + 'SELECT donationsTable.*, metaTable.* FROM posts AS donationsTable LEFT JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id', + $builder->getSQL() + ); + } + + + public function testRightJoin() + { + $builder = new QueryBuilder(); + + $builder + ->select('donationsTable.*', 'metaTable.*') + ->from(DB::raw('posts'), 'donationsTable') + ->rightJoin(DB::raw('give_donationmeta'), 'donationsTable.ID', 'metaTable.donation_id', 'metaTable'); + + $this->assertContains( + 'SELECT donationsTable.*, metaTable.* FROM posts AS donationsTable RIGHT JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id', + $builder->getSQL() + ); + } + + public function testInnerJoin() + { + $builder = new QueryBuilder(); + + $builder + ->select('donationsTable.*', 'metaTable.*') + ->from(DB::raw('posts'), 'donationsTable') + ->innerJoin(DB::raw('give_donationmeta'), 'donationsTable.ID', 'metaTable.donation_id', 'metaTable'); + + $this->assertContains( + 'SELECT donationsTable.*, metaTable.* FROM posts AS donationsTable INNER JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id', + $builder->getSQL() + ); + } + + + public function testJoin() + { + $builder = new QueryBuilder(); + + $builder + ->select('donationsTable.*', 'metaTable.*') + ->from(DB::raw('posts'), 'donationsTable') + ->join(function (JoinQueryBuilder $builder) { + $builder + ->leftJoin(DB::raw('give_donationmeta'), 'metaTable') + ->on('donationsTable.ID', 'metaTable.donation_id'); + }); + + $this->assertContains( + 'SELECT donationsTable.*, metaTable.* FROM posts AS donationsTable LEFT JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id', + $builder->getSQL() + ); + } + + + public function testAdvancedJoin() + { + $builder = new QueryBuilder(); + + $builder + ->select('donationsTable.*', 'metaTable.*') + ->from(DB::raw('posts'), 'donationsTable') + ->join(function (JoinQueryBuilder $builder) { + $builder + ->leftJoin(DB::raw('give_donationmeta'), 'metaTable') + ->on('donationsTable.ID', 'metaTable.donation_id') + ->andOn('metaTable.meta_key', '_give_donor_billing_first_name', true) + ->orOn('metaTable.meta_key', '_give_donor_billing_last_name', true); + }); + + $this->assertContains( + "SELECT donationsTable.*, metaTable.* FROM posts AS donationsTable LEFT JOIN give_donationmeta metaTable ON donationsTable.ID = metaTable.donation_id AND metaTable.meta_key = '_give_donor_billing_first_name' OR metaTable.meta_key = '_give_donor_billing_last_name'", + $builder->getSQL() + ); + } + + + public function testJoinRaw() + { + $builder = new QueryBuilder(); + + $builder + ->select('ID') + ->from(DB::raw('give_donations')) + ->joinRaw('LEFT JOIN posts ON post_id = give_donations.id'); + + $this->assertContains( + "SELECT ID FROM give_donations LEFT JOIN posts ON post_id = give_donations.id", + $builder->getSQL() + ); + } + +} diff --git a/tests/wpunit/QueryBuilder/LimitAndOffsetTest.php b/tests/wpunit/QueryBuilder/LimitAndOffsetTest.php new file mode 100644 index 0000000..9f431cc --- /dev/null +++ b/tests/wpunit/QueryBuilder/LimitAndOffsetTest.php @@ -0,0 +1,44 @@ +select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->limit(5); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' LIMIT 5", + $builder->getSQL() + ); + } + + + public function testOffset() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->limit(5) + ->offset(10); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' LIMIT 5 OFFSET 10", + $builder->getSQL() + ); + } + +} diff --git a/tests/wpunit/QueryBuilder/OrderByTest.php b/tests/wpunit/QueryBuilder/OrderByTest.php new file mode 100644 index 0000000..15f079b --- /dev/null +++ b/tests/wpunit/QueryBuilder/OrderByTest.php @@ -0,0 +1,56 @@ +select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->orderBy('ID', 'DESC'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' ORDER BY ID DESC", + $builder->getSQL() + ); + } + + + public function testOrderByAsc() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->orderBy('ID', 'ASC'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' ORDER BY ID ASC", + $builder->getSQL() + ); + } + + + public function testReturnExceptionWhenBadOrderByDirectionArgumentIsUsed() { + $this->expectException( InvalidArgumentException::class ); + + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->orderBy('ID', 'BANANAS'); + } +} diff --git a/tests/wpunit/QueryBuilder/SelectTest.php b/tests/wpunit/QueryBuilder/SelectTest.php new file mode 100644 index 0000000..6735506 --- /dev/null +++ b/tests/wpunit/QueryBuilder/SelectTest.php @@ -0,0 +1,91 @@ +select('ID') + ->from(DB::raw('posts')); + + $this->assertContains( + 'SELECT ID FROM posts', + $builder->getSQL() + ); + } + + public function testSelectAll() + { + $builder = new QueryBuilder(); + $builder->from(DB::raw('posts')); + + $this->assertContains( + 'SELECT * FROM posts', + $builder->getSQL() + ); + } + + + public function testSelectAlias() + { + $builder = new QueryBuilder(); + $builder + ->select(['ID', 'posts_id']) + ->from(DB::raw('posts')); + + + $this->assertContains( + 'SELECT ID AS posts_id FROM posts', + $builder->getSQL() + ); + } + + public function testSelectDistinct() + { + $builder = new QueryBuilder(); + $builder + ->select('ID', 'post_author') + ->from(DB::raw('posts')) + ->distinct(); + + $this->assertContains( + 'SELECT DISTINCT ID, post_author FROM posts', + $builder->getSQL() + ); + } + + public function testSelectRaw() + { + $builder = new QueryBuilder(); + + $builder->selectRaw('SELECT * FROM posts WHERE post_status = %s', 'give_subscription'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'give_subscription'", + $builder->getSQL() + ); + } + + public function testSelectRawChain() + { + $builder = new QueryBuilder(); + + $builder + ->select('ID', 'post_title') + ->selectRaw('(SELECT COUNT(ID) FROM posts WHERE post_status = %s) as post_count', 'give_subscription') + ->from(DB::raw('posts')); + + $this->assertContains( + "SELECT ID, post_title, (SELECT COUNT(ID) FROM posts WHERE post_status = 'give_subscription') as post_count FROM posts", + $builder->getSQL() + ); + } + +} diff --git a/tests/wpunit/QueryBuilder/UnionTest.php b/tests/wpunit/QueryBuilder/UnionTest.php new file mode 100644 index 0000000..0664fb6 --- /dev/null +++ b/tests/wpunit/QueryBuilder/UnionTest.php @@ -0,0 +1,58 @@ +select('ID') + ->from(DB::raw('give_donations')); + + $builder2 + ->select('ID') + ->from(DB::raw('give_subscriptions')) + ->where('ID', 100, '>') + ->union($builder1); + + $this->assertContains( + "SELECT ID FROM give_subscriptions WHERE ID > '100' UNION SELECT ID FROM give_donations", + $builder2->getSQL() + ); + } + + + public function testUnionAll() + { + $builder1 = new QueryBuilder(); + $builder2 = new QueryBuilder(); + $builder3 = new QueryBuilder(); + + $builder1 + ->select('ID') + ->from(DB::raw('give_donations')); + + $builder2 + ->select('ID') + ->from(DB::raw('give_subscriptions')) + ->where('ID', 100, '>'); + + $builder3 + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->unionAll($builder1, $builder2); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' UNION ALL SELECT ID FROM give_donations UNION ALL SELECT ID FROM give_subscriptions WHERE ID > '100'", + $builder3->getSQL() + ); + } +} diff --git a/tests/wpunit/QueryBuilder/WhereTest.php b/tests/wpunit/QueryBuilder/WhereTest.php new file mode 100644 index 0000000..cf86534 --- /dev/null +++ b/tests/wpunit/QueryBuilder/WhereTest.php @@ -0,0 +1,495 @@ +select('*') + ->from(DB::raw('posts')) + ->where('ID', 5); + + $this->assertContains( + "SELECT * FROM posts WHERE ID = '5'", + $builder->getSQL() + ); + } + + public function testAndWhere() + { + $builder = new QueryBuilder(); + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->where('post_title', 'Donation Form'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' AND post_title = 'Donation Form'", + $builder->getSQL() + ); + } + + public function testOrWhere() + { + $builder = new QueryBuilder(); + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published') + ->orWhere('post_title', 'Donation Form'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'published' OR post_title = 'Donation Form'", + $builder->getSQL() + ); + } + + public function testNestedWhere() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'subscription') + ->where(function (WhereQueryBuilder $builder) { + $builder + ->where('post_status', 'give_subscription') + ->orWhere('post_status', 'give_donation'); + }); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'subscription' AND ( post_status = 'give_subscription' OR post_status = 'give_donation')", + $builder->getSQL() + ); + } + + + public function testSubSelectQuery() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'subscription') + ->whereIn('ID', function (QueryBuilder $builder) { + $builder + ->select(['meta_value', 'donation_id']) + ->from(DB::raw('give_donationmeta')) + ->where('meta_key', 'donation_id'); + }); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'subscription' AND ID IN (SELECT meta_value AS donation_id FROM give_donationmeta WHERE meta_key = 'donation_id')", + $builder->getSQL() + ); + } + + public function testWhereIn() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'subscription') + ->whereIn('ID', [1, 2, 3]); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'subscription' AND ID IN ('1','2','3')", + $builder->getSQL() + ); + } + + public function testWhereNotIn() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'subscription') + ->whereNotIn('ID', [1, 2, 3]); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'subscription' AND ID NOT IN ('1','2','3')", + $builder->getSQL() + ); + } + + + public function testOrWhereIn() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'subscription') + ->orWhereIn('ID', [1, 2, 3]); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'subscription' OR ID IN ('1','2','3')", + $builder->getSQL() + ); + } + + public function testOrWhereNotIn() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'subscription') + ->orWhereNotIn('ID', [1, 2, 3]); + + $this->assertContains( + "SELECT * FROM posts WHERE post_status = 'subscription' OR ID NOT IN ('1','2','3')", + $builder->getSQL() + ); + } + + + public function testWhereBetween() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereBetween('ID', 0, 100); + + $this->assertContains( + "SELECT * FROM posts WHERE ID BETWEEN '0' AND '100'", + $builder->getSQL() + ); + } + + + public function testWhereBetweenDates() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereBetween('post_date', '2021-11-22', '2022-11-22'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_date BETWEEN '2021-11-22' AND '2022-11-22'", + $builder->getSQL() + ); + } + + + public function testWhereNotBetween() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereNotBetween('ID', 0, 100); + + $this->assertContains( + "SELECT * FROM posts WHERE ID NOT BETWEEN '0' AND '100'", + $builder->getSQL() + ); + } + + + public function testOrWhereBetween() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('ID', 222) + ->orWhereBetween('ID', 0, 100); + + $this->assertContains( + "SELECT * FROM posts WHERE ID = '222' OR ID BETWEEN '0' AND '100'", + $builder->getSQL() + ); + } + + + public function testOrWhereNotBetween() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('ID', 222) + ->orWhereNotBetween('ID', 0, 100); + + $this->assertContains( + "SELECT * FROM posts WHERE ID = '222' OR ID NOT BETWEEN '0' AND '100'", + $builder->getSQL() + ); + } + + + public function testWhereLike() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereLike('post_title', 'Donation'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_title LIKE '%Donation%'", + DB::remove_placeholder_escape($builder->getSQL()) + ); + } + + public function testWhereLikeWithWildCardPositionLeft() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereLike('post_title', '%Donation'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_title LIKE '%Donation'", + DB::remove_placeholder_escape($builder->getSQL()) + ); + } + + + public function testWhereLikeWithWildCardPositionRight() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereLike('post_title', 'Donation%'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_title LIKE 'Donation%'", + DB::remove_placeholder_escape($builder->getSQL()) + ); + } + + + public function testWhereNotLike() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereNotLike('post_title', 'Donation'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_title NOT LIKE '%Donation%'", + DB::remove_placeholder_escape($builder->getSQL()) + ); + } + + + public function testOrWhereLike() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereLike('post_title', 'Form') + ->orWhereLike('post_title', 'Donation'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_title LIKE '%Form%' OR post_title LIKE '%Donation%'", + DB::remove_placeholder_escape($builder->getSQL()) + ); + } + + + public function testOrWhereNotLike() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereLike('post_title', 'Form') + ->orWhereNotLike('post_title', 'Donation'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_title LIKE '%Form%' OR post_title NOT LIKE '%Donation%'", + DB::remove_placeholder_escape($builder->getSQL()) + ); + } + + + public function testWhereIsNull() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereIsNull('post_parent'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent IS NULL", + $builder->getSQL() + ); + } + + + public function testWhereIsNotNull() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereIsNotNull('post_parent'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent IS NOT NULL", + $builder->getSQL() + ); + } + + + public function testOrWhereIsNull() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5, '>') + ->orWhereIsNull('post_parent'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent > '5' OR post_parent IS NULL", + $builder->getSQL() + ); + } + + + public function testOrWhereIsNotNull() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_parent', 5, '>') + ->orWhereIsNotNull('post_parent'); + + $this->assertContains( + "SELECT * FROM posts WHERE post_parent > '5' OR post_parent IS NOT NULL", + $builder->getSQL() + ); + } + + + public function testWhereExists() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereExists(function (QueryBuilder $builder) { + $builder + ->select(['meta_value', 'donation_id']) + ->from(DB::raw('give_donationmeta')) + ->where('meta_key', 'donation_id'); + }); + + $this->assertContains( + "SELECT * FROM posts WHERE EXISTS (SELECT meta_value AS donation_id FROM give_donationmeta WHERE meta_key = 'donation_id')", + $builder->getSQL() + ); + } + + + public function testWhereNotExists() + { + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->whereNotExists(function (QueryBuilder $builder) { + $builder + ->select(['meta_value', 'donation_id']) + ->from(DB::raw('give_donationmeta')) + ->where('meta_key', 'donation_id'); + }); + + $this->assertContains( + "SELECT * FROM posts WHERE NOT EXISTS (SELECT meta_value AS donation_id FROM give_donationmeta WHERE meta_key = 'donation_id')", + $builder->getSQL() + ); + } + + + public function testWhereRaw() + { + $builder = new QueryBuilder(); + + $builder + ->select('ID') + ->from(DB::raw('give_donations')) + ->whereRaw('WHERE post_id = %d AND post_title = %s', 5, 'Donation'); + + $this->assertContains( + "SELECT ID FROM give_donations WHERE post_id = 5 AND post_title = 'Donation'", + $builder->getSQL() + ); + } + + + public function testWhereRawChain() + { + $builder = new QueryBuilder(); + + $builder + ->select('ID') + ->from(DB::raw('give_donations')) + ->whereRaw('WHERE post_id = %d AND post_title = %s', 5, 'Donation') + ->orWhere('post_title', 'Form'); + + $this->assertContains( + "SELECT ID FROM give_donations WHERE post_id = 5 AND post_title = 'Donation' OR post_title = 'Form'", + $builder->getSQL() + ); + } + + + public function testReturnExceptionWhenBadComparisonArgumentIsUsed() { + $this->expectException( InvalidArgumentException::class ); + + $builder = new QueryBuilder(); + + $builder + ->select('*') + ->from(DB::raw('posts')) + ->where('post_status', 'published', 'EQUALS TO'); + } + +} diff --git a/tests/wpunit/_bootstrap.php b/tests/wpunit/_bootstrap.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/tests/wpunit/_bootstrap.php @@ -0,0 +1 @@ +