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 @@
+<?php
+
+namespace StellarWP\DB;
+
+use Exception;
+use lucatume\DI52\App;
+use StellarWP\DB\Database\Exceptions\DatabaseQueryException;
+use StellarWP\DB\QueryBuilder\Clauses\RawSQL;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use WP_Error;
+
+/**
+ * Class DB
+ *
+ * A static decorator for the $wpdb class and decorator function which does SQL error checking when performing queries.
+ * If a SQL error occurs a DatabaseQueryException is thrown.
+ *
+ * @method static int|bool query(string $query)
+ * @method static int|false insert(string $table, array $data, array|string $format)
+ * @method static int|false delete(string $table, array $where, array|string $where_format)
+ * @method static int|false update(string $table, array $data, array $where, array|string $format, array|string $where_format)
+ * @method static int|false replace(string $table, array $data, array|string $format)
+ * @method static null|string get_var(string $query = null, int $x = 0, int $y = 0)
+ * @method static array|object|null|void get_row(string $query = null, string $output = OBJECT, int $y = 0)
+ * @method static array get_col(string $query = null, int $x = 0)
+ * @method static array|object|null get_results(string $query = null, string $output = OBJECT)
+ * @method static string get_charset_collate()
+ * @method static string esc_like(string $text)
+ * @method static string remove_placeholder_escape(string $text)
+ */
+class DB {
+	/**
+	 * Initializes the service provider.
+	 *
+	 * @since 1.0.0
+	 */
+	public static function init(): void {
+		$container = App::container();
+
+		if ( $container->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 @@
+<?php
+
+declare( strict_types=1 );
+
+namespace StellarWP\DB\Database\Actions;
+
+class EnableBigSqlSelects {
+	/**
+	 * @since 1.0.0
+	 *
+	 * Enables mysql big selects for the session using a session system variable.
+	 *
+	 * This is necessary for hosts that have an arbitrary MAX_JOIN_SIZE limit, which prevents more complex queries from
+	 * running properly. Setting SQL_BIG_SELECTS ignores this limit. This is also done by WooCommerce, supporting the
+	 * idea that this is a viable option. There also doesn't seem to be a way for hosts to prevent this.
+	 *
+	 * @see https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_big_selects
+	 * @see https://dev.mysql.com/doc/refman/5.7/en/system-variable-privileges.html
+	 *
+	 */
+	public function set_var() {
+		static $bigSelects = false;
+
+		if ( ! $bigSelects ) {
+			global $wpdb;
+
+			$wpdb->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 @@
+<?php
+
+namespace StellarWP\DB\Database\Exceptions;
+
+use Throwable;
+
+/**
+ * Class DatabaseQueryException
+ *
+ * An exception for when errors occurred within the database while performing a query, which stores the SQL errors the
+ * database returned
+ *
+ * @since 1.0.0
+ */
+class DatabaseQueryException extends \Exception {
+	/**
+	 * @var string[]
+	 */
+	private $queryErrors;
+
+	/**
+	 * @var string
+	 */
+	private $query;
+
+	/**
+	 * @since 1.0.0
+	 */
+	public function __construct(
+		string $query,
+		array $queryErrors,
+		string $message = 'Database Query',
+		$code = 0,
+		Throwable $previous = null
+	) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\Database;
+
+use lucatume\DI52\App;
+use lucatume\DI52\ServiceProvider;
+
+class Provider extends ServiceProvider {
+
+	/**
+	 * Binds and sets up implementations.
+	 *
+	 * @since 1.0.0
+	 */
+	public function register() {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Clauses;
+
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+
+/**
+ * @since 2.19.0
+ */
+class From {
+	/**
+	 * @var string|RawSQL
+	 */
+	public $table;
+
+	/**
+	 * @var string
+	 */
+	public $alias;
+
+	/**
+	 * @param  string|RawSQL  $table
+	 * @param  string|null  $alias
+	 */
+	public function __construct( $table, $alias = null ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Clauses;
+
+use StellarWP\DB\QueryBuilder\Types\Math;
+use StellarWP\DB\QueryBuilder\Types\Operator;
+use InvalidArgumentException;
+
+/**
+ * @since 2.19.0
+ */
+class Having {
+	/**
+	 * @var string
+	 */
+	public $column;
+
+	/**
+	 * @var string
+	 */
+	public $comparisonOperator;
+
+	/**
+	 * @var string|int
+	 */
+	public $value;
+
+	/**
+	 * @var string
+	 */
+	public $logicalOperator;
+
+	/**
+	 * @var string|null
+	 */
+	public $mathFunction;
+
+	/**
+	 * @param  string  $column
+	 * @param  string  $comparisonOperator
+	 * @param  string|int  $value
+	 * @param  string|null  $logicalOperator
+	 * @param  string  $mathFunction
+	 */
+	public function __construct( $column, $comparisonOperator, $value, $logicalOperator, $mathFunction = null ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Clauses;
+
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use StellarWP\DB\QueryBuilder\Types\JoinType;
+use InvalidArgumentException;
+
+/**
+ * @since 2.19.0
+ */
+class Join {
+	/**
+	 * @var string
+	 */
+	public $table;
+
+	/**
+	 * @var string
+	 */
+	public $joinType;
+
+	/**
+	 * @var string|null
+	 */
+	public $alias;
+
+	/**
+	 * @param  string  $table
+	 * @param  string  $joinType  \StellarWP\DB\QueryBuilder\Types\JoinType
+	 * @param  string|null  $alias
+	 */
+	public function __construct( $joinType, $table, $alias = null ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Clauses;
+
+use StellarWP\DB\QueryBuilder\Types\Operator;
+use InvalidArgumentException;
+
+/**
+ * @since 2.19.0
+ */
+class JoinCondition {
+	/**
+	 * @var string
+	 */
+	public $logicalOperator;
+
+	/**
+	 * @var string
+	 */
+	public $column1;
+
+	/**
+	 * @var mixed
+	 */
+	public $column2;
+
+	/**
+	 * @var bool
+	 */
+	public $quote;
+
+
+	/**
+	 * @param  string  $logicalOperator
+	 * @param  string  $column1
+	 * @param  string  $column2
+	 * @param  bool  $quote
+	 */
+	public function __construct( $logicalOperator, $column1, $column2, $quote = false ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Clauses;
+
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+
+/**
+ * @since 2.19.0
+ */
+class MetaTable {
+	/**
+	 * @var string
+	 */
+	public $tableName;
+
+	/**
+	 * @var string
+	 */
+	public $keyColumnName;
+
+	/**
+	 * @var string
+	 */
+	public $valueColumnName;
+
+	/**
+	 * @param  string  $table
+	 * @param  string  $metaKeyColumnName
+	 * @param  string  $metaValueColumnName
+	 */
+	public function __construct( $table, $metaKeyColumnName, $metaValueColumnName ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Clauses;
+
+use InvalidArgumentException;
+
+/**
+ * @since 2.19.0
+ */
+class OrderBy {
+	/**
+	 * @var string
+	 */
+	public $column;
+
+	/**
+	 * @var string
+	 */
+	public $direction;
+
+	/**
+	 * @param $column
+	 * @param $direction
+	 */
+	public function __construct( $column, $direction ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Clauses;
+
+use StellarWP\DB\DB;
+
+/**
+ * @since 2.19.0
+ */
+class RawSQL {
+	/**
+	 * @var string
+	 */
+	public $sql;
+
+	/**
+	 * @param  string  $sql
+	 * @param  null  $args
+	 */
+	public function __construct( $sql, $args = null ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Clauses;
+
+/**
+ * @since 2.19.0
+ */
+class Select {
+	/**
+	 * @var string
+	 */
+	public $column;
+
+	/**
+	 * @var string
+	 */
+	public $alias;
+
+	/**
+	 * @param  string  $column
+	 * @param  string|null  $alias
+	 */
+	public function __construct( $column, $alias = null ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Clauses;
+
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+
+/**
+ * @since 2.19.0
+ */
+class Union {
+	/**
+	 * @var QueryBuilder
+	 */
+	public $builder;
+
+	/**
+	 * @var bool
+	 */
+	public $all = false;
+
+	/**
+	 * @param  QueryBuilder  $builder
+	 * @param  bool  $all
+	 */
+	public function __construct( $builder, $all = false ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Clauses;
+
+use StellarWP\DB\QueryBuilder\Types\Operator;
+use InvalidArgumentException;
+
+/**
+ * @since 2.19.0
+ */
+class Where {
+	/**
+	 * @var string
+	 */
+	public $column;
+
+	/**
+	 * @var mixed
+	 */
+	public $value;
+
+	/**
+	 * @var string
+	 */
+	public $comparisonOperator;
+
+	/**
+	 * @var string
+	 */
+	public $logicalOperator;
+
+	/**
+	 * @var string|null
+	 */
+	public $type;
+
+	/**
+	 * @param  string  $column
+	 * @param  string  $value
+	 * @param  string  $comparisonOperator
+	 * @param  string|null  $logicalOperator
+	 */
+	public function __construct( $column, $value, $comparisonOperator, $logicalOperator ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+use StellarWP\DB\QueryBuilder\Clauses\RawSQL;
+
+/**
+ * @since 2.19.0
+ */
+trait Aggregate {
+	/**
+	 * Returns the number of rows returned by a query
+	 *
+	 * @since 2.19.0
+	 * @param  null|string  $column
+	 *
+	 * @return int
+	 */
+	public function count( $column = null ) {
+		$column = ( ! $column || $column === '*' ) ? '1' : trim( $column );
+
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+use StellarWP\DB\DB;
+
+/**
+ * @since 2.19.0
+ */
+trait CRUD {
+	/**
+	 * @see https://developer.wordpress.org/reference/classes/wpdb/insert/
+	 *
+	 * @since 2.19.0
+	 *
+	 * @param  array|string  $format
+	 *
+	 * @param  array  $data
+	 * @return false|int
+	 *
+	 */
+	public function insert( $data, $format = null ) {
+		return DB::insert(
+			$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\Clauses\From;
+use StellarWP\DB\QueryBuilder\Clauses\RawSQL;
+
+/**
+ * @since 2.19.0
+ */
+trait FromClause {
+	/**
+	 * @var From[]
+	 */
+	protected $froms = [];
+
+	/**
+	 * @param  string|RawSQL  $table
+	 * @param  string|null  $alias
+	 *
+	 * @return $this
+	 */
+	public function from( $table, $alias = null ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+use StellarWP\DB\DB;
+
+/**
+ * @since 2.19.0
+ */
+trait GroupByStatement {
+	/**
+	 * @var string
+	 */
+	protected $groupByColumns = [];
+
+	/**
+	 * @return $this
+	 */
+	public function groupBy( $tableColumn ) {
+		if ( ! in_array( $tableColumn, $this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\Clauses\Having;
+use StellarWP\DB\QueryBuilder\Clauses\RawSQL;
+use StellarWP\DB\QueryBuilder\Types\Math;
+use StellarWP\DB\QueryBuilder\Types\Operator;
+
+/**
+ * @since 2.19.0
+ */
+trait HavingClause {
+	/**
+	 * @var Having[]|RawSQL[]
+	 */
+	protected $havings = [];
+
+	/**
+	 * @var bool
+	 */
+	private $includeHavingKeyword = true;
+
+	/**
+	 * @param  string  $column
+	 * @param  string  $comparisonOperator
+	 * @param  string  $value
+	 * @param  null|string  $mathFunction  \StellarWP\DB\QueryBuilder\Types\Math
+	 *
+	 * @return $this
+	 *
+	 */
+	public function having( $column, $comparisonOperator, $value, $mathFunction = null ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+use Closure;
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\JoinQueryBuilder;
+use StellarWP\DB\QueryBuilder\Clauses\Join;
+use StellarWP\DB\QueryBuilder\Clauses\RawSQL;
+
+/**
+ * @since 2.19.0
+ */
+trait JoinClause {
+
+	/**
+	 * @var Closure[]|RawSQL[]
+	 */
+	protected $joins = [];
+
+	/**
+	 * Method used to build advanced JOIN queries, Check README.md for more info.
+	 * If you need to perform only simple JOINs with only one JOIN condition, then you don't need this method.
+	 *
+	 * @param  Closure  $callback  The closure will receive a StellarWP\DB\QueryBuilder\JoinQueryBuilder instance
+	 *
+	 * @return $this
+	 */
+	public function join( $callback ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+/**
+ * @since 2.19.0
+ */
+trait LimitStatement {
+	/**
+	 * @var int
+	 */
+	protected $limit;
+
+	/**
+	 * @param  int  $limit
+	 *
+	 * @return $this
+	 */
+	public function limit( $limit ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+use StellarWP\DB\QueryBuilder\Clauses\MetaTable;
+use StellarWP\DB\QueryBuilder\Clauses\RawSQL;
+use StellarWP\DB\QueryBuilder\JoinQueryBuilder;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+
+/**
+ * @since 2.19.0
+ */
+trait MetaQuery {
+
+	/**
+	 * @var MetaTable[]
+	 */
+	private $metaTablesConfigs = [];
+
+	/**
+	 * @var string
+	 */
+	private $defaultMetaKeyColumn = 'meta_key';
+
+	/**
+	 * @var string
+	 */
+	private $defaultMetaValueColumn = 'meta_value';
+
+	/**
+	 * @param string|RawSQL $table
+	 * @param string $metaKeyColumn
+	 * @param string $metaValueColumn
+	 *
+	 * @return $this
+	 */
+	public function configureMetaTable( $table, $metaKeyColumn, $metaValueColumn ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+/**
+ * @since 2.19.0
+ */
+trait OffsetStatement {
+	/**
+	 * @var int
+	 */
+	protected $offset;
+
+	/**
+	 * @param  int  $offset
+	 *
+	 * @return $this
+	 */
+	public function offset( $offset ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\Clauses\OrderBy;
+
+/**
+ * @since 2.19.0
+ */
+trait OrderByStatement {
+	/**
+	 * @var OrderBy[]
+	 */
+	protected $orderBys = [];
+
+	/**
+	 * @param  string  $column
+	 * @param  string  $direction  ASC|DESC
+	 *
+	 * @return $this
+	 */
+	public function orderBy( $column, $direction = 'ASC' ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\Clauses\RawSQL;
+use StellarWP\DB\QueryBuilder\Clauses\Select;
+
+/**
+ * @since 2.19.0
+ */
+trait SelectStatement {
+	/**
+	 * @var Select[]|RawSQL[]
+	 */
+	protected $selects = [];
+
+	/**
+	 * @var bool
+	 */
+	protected $distinct = false;
+
+	/**
+	 * @var bool
+	 */
+	private $includeSelectKeyword = true;
+
+	/**
+	 * @param  array  $columns
+	 *
+	 * @return $this
+	 */
+	public function select( ...$columns ) {
+		$selects = array_map(function ( $select ) {
+			if ( is_array( $select ) ) {
+				list( $column, $alias ) = $select;
+
+				return new Select( $column, $alias );
+			}
+
+			return new Select( $select );
+		}, $columns );
+
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+use StellarWP\DB\QueryBuilder\Clauses\RawSQL;
+
+/**
+ * @since 2.19.0
+ */
+trait TablePrefix {
+	/**
+	 * @param  string  $table
+	 *
+	 * @return string
+	 */
+	public static function prefixTable( $table ) {
+		global $wpdb;
+
+		//  Shared tables in  multisite environment
+		$sharedTables = [
+			'users'	=> $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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+use StellarWP\DB\QueryBuilder\Clauses\Union;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+
+/**
+ * @since 2.19.0
+ */
+trait UnionOperator {
+	/**
+	 * @var int
+	 */
+	protected $unions = [];
+
+	/**
+	 * @param  QueryBuilder  $union
+	 *
+	 * @return $this
+	 */
+	public function union( ...$union ) {
+		$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Concerns;
+
+use Closure;
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\Clauses\RawSQL;
+use StellarWP\DB\QueryBuilder\Clauses\Where;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use StellarWP\DB\QueryBuilder\Types\Operator;
+use StellarWP\DB\QueryBuilder\WhereQueryBuilder;
+
+
+/**
+ * @since 2.19.0
+ */
+trait WhereClause {
+
+	/**
+	 * @var Where[]|RawSQL[]|string[]
+	 */
+	protected $wheres = [];
+
+
+	/**
+	 * @var bool
+	 */
+	private $includeWhereKeyword = true;
+
+	/**
+	 * @param  string|Closure|null  $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
+	 * @param  string  $logicalOperator
+	 *
+	 * @return $this
+	 */
+	private function setWhere( $column, $value, $comparisonOperator, $logicalOperator ) {
+		// If the columns is a Closure instance, we will assume the developer
+		// wants to begin a nested where statement which is wrapped in parentheses.
+		if ( $column instanceof Closure && is_null( $value ) ) {
+			$builder = new WhereQueryBuilder();
+			call_user_func( $column, $builder );
+
+			// Since this is a nested where statement, we have to remove the starting WHERE keyword
+			// which is the first returned array element from the getWhereSQL method
+			$wheres = $builder->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\QueryBuilder\Clauses\Join;
+use StellarWP\DB\QueryBuilder\Clauses\JoinCondition;
+use StellarWP\DB\QueryBuilder\Clauses\RawSQL;
+use StellarWP\DB\QueryBuilder\Types\JoinType;
+use StellarWP\DB\QueryBuilder\Types\Operator;
+
+/**
+ * @since 2.19.0
+ */
+class JoinQueryBuilder {
+	/**
+	 * @var Join[]|JoinCondition[]|RawSQL[]
+	 */
+	private $joins = [];
+
+	/**
+	 * @param  string|RawSQL  $table
+	 * @param  null|string  $alias
+	 *
+	 * @return $this
+	 */
+	public function leftJoin( $table, $alias = null ) {
+		return $this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\QueryBuilder\Concerns\Aggregate;
+use StellarWP\DB\QueryBuilder\Concerns\CRUD;
+use StellarWP\DB\QueryBuilder\Concerns\FromClause;
+use StellarWP\DB\QueryBuilder\Concerns\GroupByStatement;
+use StellarWP\DB\QueryBuilder\Concerns\HavingClause;
+use StellarWP\DB\QueryBuilder\Concerns\JoinClause;
+use StellarWP\DB\QueryBuilder\Concerns\LimitStatement;
+use StellarWP\DB\QueryBuilder\Concerns\MetaQuery;
+use StellarWP\DB\QueryBuilder\Concerns\OffsetStatement;
+use StellarWP\DB\QueryBuilder\Concerns\OrderByStatement;
+use StellarWP\DB\QueryBuilder\Concerns\SelectStatement;
+use StellarWP\DB\QueryBuilder\Concerns\TablePrefix;
+use StellarWP\DB\QueryBuilder\Concerns\UnionOperator;
+use StellarWP\DB\QueryBuilder\Concerns\WhereClause;
+
+/**
+ * @since 2.19.0
+ */
+class QueryBuilder {
+	use Aggregate;
+	use CRUD;
+	use FromClause;
+	use GroupByStatement;
+	use HavingClause;
+	use JoinClause;
+	use LimitStatement;
+	use MetaQuery;
+	use OffsetStatement;
+	use OrderByStatement;
+	use SelectStatement;
+	use TablePrefix;
+	use UnionOperator;
+	use WhereClause;
+
+	/**
+	 * @return string
+	 */
+	public function getSQL() {
+		$sql = array_merge(
+			$this->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Types;
+
+/**
+ * @since 2.19.0
+ */
+class JoinType extends Type {
+    const INNER = 'INNER';
+    const LEFT = 'LEFT';
+    const RIGHT = 'RIGHT';
+}
diff --git a/src/DB/QueryBuilder/Types/Math.php b/src/DB/QueryBuilder/Types/Math.php
new file mode 100644
index 0000000..3044207
--- /dev/null
+++ b/src/DB/QueryBuilder/Types/Math.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Types;
+
+/**
+ * @since 2.19.0
+ */
+class Math extends Type {
+	const SUM = 'SUM';
+	const MIN = 'MIN';
+	const MAX = 'MAX';
+	const COUNT = 'COUNT';
+	const AVG = 'AVG';
+}
diff --git a/src/DB/QueryBuilder/Types/Operator.php b/src/DB/QueryBuilder/Types/Operator.php
new file mode 100644
index 0000000..343fdd0
--- /dev/null
+++ b/src/DB/QueryBuilder/Types/Operator.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Types;
+
+/**
+ * @since 2.19.0
+ */
+class Operator extends Type {
+	// _AND and _OR constants are prefixed with underscore to be compatible with PHP 5.6
+	const _AND = 'AND';
+	const _OR = 'OR';
+	const ON = 'ON';
+	const BETWEEN = 'BETWEEN';
+	const NOTBETWEEN = 'NOT BETWEEN';
+	const EXISTS = 'EXISTS';
+	const NOTEXISTS = 'NOT EXISTS';
+	const IN = 'IN';
+	const NOTIN = 'NOT IN';
+	const LIKE = 'LIKE';
+	const NOTLIKE = 'NOT LIKE';
+	const NOT = 'NOT';
+	const ISNULL = 'IS NULL';
+	const NOTNULL = 'IS NOT NULL';
+}
diff --git a/src/DB/QueryBuilder/Types/Type.php b/src/DB/QueryBuilder/Types/Type.php
new file mode 100644
index 0000000..a7fb72d
--- /dev/null
+++ b/src/DB/QueryBuilder/Types/Type.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder\Types;
+
+use ReflectionClass;
+
+/**
+ * @since 2.19.0
+ */
+abstract class Type {
+	/**
+	 * Get Defined Types
+	 *
+	 * @return array
+	 */
+	public static function getTypes() {
+		return ( new ReflectionClass( static::class ) )->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 @@
+<?php
+
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\QueryBuilder\Concerns\WhereClause;
+
+/**
+ * @since 2.19.0
+ */
+class WhereQueryBuilder {
+	use WhereClause;
+
+	/**
+	 * @return string[]
+	 */
+	public function getSQL() {
+		return $this->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 @@
+<?php
diff --git a/tests/_support/Helper/DBTestCase.php b/tests/_support/Helper/DBTestCase.php
new file mode 100644
index 0000000..6613b7b
--- /dev/null
+++ b/tests/_support/Helper/DBTestCase.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace StellarWP\DB\Tests;
+
+use lucatume\DI52\App;
+use StellarWP\DB\DB;
+
+class DBTestCase extends \Codeception\Test\Unit {
+	protected $backupGlobals = false;
+
+	public function setUp() {
+		// before
+		parent::setUp();
+
+		DB::init();
+	}
+}
+
diff --git a/tests/wpunit.suite.dist.yml b/tests/wpunit.suite.dist.yml
new file mode 100644
index 0000000..c1b264b
--- /dev/null
+++ b/tests/wpunit.suite.dist.yml
@@ -0,0 +1,17 @@
+# Codeception Test Suite Configuration
+
+# suite for WordPress functional tests.
+# Emulate web requests and make application process them.
+class_name: WpunitTester
+bootstrap: _bootstrap.php
+modules:
+    enabled:
+        - WPLoader
+        - WPQueries
+    config:
+        WPLoader:
+            wpRootFolder: %WP_ROOT_FOLDER%
+            dbName: %WP_TEST_DB_NAME%
+            dbHost: %WP_TEST_DB_HOST%
+            dbUser: %WP_TEST_DB_USER%
+            dbPassword: %WP_TEST_DB_PASSWORD%
diff --git a/tests/wpunit/QueryBuilder/AggregateTest.php b/tests/wpunit/QueryBuilder/AggregateTest.php
new file mode 100644
index 0000000..b9a63a6
--- /dev/null
+++ b/tests/wpunit/QueryBuilder/AggregateTest.php
@@ -0,0 +1,243 @@
+<?php
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\Tests\DBTestCase;
+
+/**
+ * @since 2.19.0
+ *
+ * @covers Aggregate
+ */
+final class AggregateTest extends DBTestCase
+{
+	/**
+	 * Truncate posts and give_donationmeta table to avoid duplicate records
+	 *
+	 * @since 2.19.0
+	 *
+	 * @return void
+	 */
+	public function tearDown() {
+		parent::tearDown();
+
+		$posts        = DB::prefix('posts');
+		$donationMeta = DB::prefix('give_donationmeta');
+
+		DB::query("TRUNCATE TABLE $posts");
+		DB::query("TRUNCATE TABLE $donationMeta");
+	}
+
+	/**
+	 *
+	 * @since 2.19.0
+	 *
+	 * @return void
+	 */
+	public function testCountShouldReturnTheTotalNumberOfRecords() {
+		$data = [
+			[
+				'post_title' => '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 @@
+<?php
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use StellarWP\DB\Tests\DBTestCase;
+
+/**
+ * @since 2.19.0
+ *
+ * @covers AttachMeta
+ */
+final class AttachMetaTest extends DBTestCase
+{
+    /**
+     * @since 2.19.0
+     */
+    public function testAttachMeta()
+    {
+        $builder = new QueryBuilder();
+
+        $builder
+            ->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 @@
+<?php
+namespace StellarWP\DB\QueryBuilder;
+
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\DB;
+use Give\Framework\QueryBuilder\Concerns\CRUD;
+use StellarWP\DB\Tests\DBTestCase;
+
+/**
+ * @since 2.19.0
+ *
+ * @covers CRUD
+ */
+final class CRUDTest extends DBTestCase
+{
+    /**
+     * Truncate posts table to avoid duplicate records
+     *
+     * @since 2.19.0
+     *
+     * @return void
+     */
+    public function tearDown()
+    {
+        parent::tearDown();
+
+        $posts = DB::prefix('posts');
+
+        DB::query("TRUNCATE TABLE $posts");
+    }
+
+    /**
+     * @since 2.19.0
+     *
+     * @return void
+     */
+    public function testInsertShouldAddRowToDatabase()
+    {
+        $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')
+            ->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 @@
+<?php
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use StellarWP\DB\Tests\DBTestCase;
+
+final class FromTest extends DBTestCase
+{
+
+    public function testFrom()
+    {
+        $builder = new QueryBuilder();
+        $builder
+            ->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 @@
+<?php
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use StellarWP\DB\Tests\DBTestCase;
+
+final class GroupByTest extends DBTestCase
+{
+    public function testGroupBy()
+    {
+        $builder = new QueryBuilder();
+
+        $builder
+            ->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 @@
+<?php
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use InvalidArgumentException;
+use StellarWP\DB\Tests\DBTestCase;
+
+final class HavingTest extends DBTestCase
+{
+
+    public function testHaving()
+    {
+        $builder = new QueryBuilder();
+
+        $builder
+            ->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 @@
+<?php
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\DB;
+use Give\Framework\QueryBuilder\JoinQueryBuilder;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use StellarWP\DB\Tests\DBTestCase;
+
+final class JoinTest extends DBTestCase
+{
+
+    public function testLeftJoin()
+    {
+        $builder = new QueryBuilder();
+
+        $builder
+            ->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 @@
+<?php
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use StellarWP\DB\Tests\DBTestCase;
+
+final class LimitAndOffsetTest extends DBTestCase
+{
+    public function testLimit()
+    {
+        $builder = new QueryBuilder();
+
+        $builder
+            ->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 @@
+<?php
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use InvalidArgumentException;
+use StellarWP\DB\Tests\DBTestCase;
+
+final class OrderByTest extends DBTestCase
+{
+    public function testOrderByDesc()
+    {
+        $builder = new QueryBuilder();
+
+        $builder
+            ->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 @@
+<?php
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use StellarWP\DB\Tests\DBTestCase;
+
+final class SelectTest extends DBTestCase
+{
+
+    public function testSelect()
+    {
+        $builder = new QueryBuilder();
+        $builder
+            ->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 @@
+<?php
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use StellarWP\DB\Tests\DBTestCase;
+
+final class UnionTest extends DBTestCase
+{
+    public function testUnion()
+    {
+        $builder1 = new QueryBuilder();
+        $builder2 = new QueryBuilder();
+
+        $builder1
+            ->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 @@
+<?php
+namespace StellarWP\DB\QueryBuilder;
+
+use StellarWP\DB\DB;
+use StellarWP\DB\QueryBuilder\QueryBuilder;
+use Give\Framework\QueryBuilder\WhereQueryBuilder;
+use InvalidArgumentException;
+use StellarWP\DB\Tests\DBTestCase;
+
+final class WhereTest extends DBTestCase
+{
+    public function testWhere()
+    {
+        $builder = new QueryBuilder();
+        $builder
+            ->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 @@
+<?php