From 18c93480e80ae094c0356f00f5f1768fbbdb371f Mon Sep 17 00:00:00 2001
From: Marcin Kolanko <marcin@inspirelabs.pl>
Date: Mon, 16 Dec 2019 08:15:19 +0000
Subject: [PATCH] init

---
 .gitattributes                                |  15 ++
 .gitignore                                    |   5 +
 .gitlab-ci.yml                                |   9 +
 changelog.txt                                 |   2 +
 composer.json                                 |  34 +++
 phpunit-integration.xml                       |  28 +++
 phpunit-unit.xml                              |  21 ++
 src/AbstractForm.php                          |  66 ++++++
 src/ConditionalFormInterface.php              |  15 ++
 src/FormsCollection.php                       | 118 ++++++++++
 src/old/Curl/CurlClient.php                   | 202 ++++++++++++++++++
 src/old/Curl/CurlExceptionFactory.php         |  37 ++++
 src/old/Curl/Exception/CurlException.php      |  15 ++
 .../Curl/Exception/CurlTimedOutException.php  |   8 +
 src/old/HttpClient.php                        |  58 +++++
 src/old/HttpClientFactory.php                 |  17 ++
 src/old/HttpClientOptions.php                 |  11 +
 src/old/HttpClientRequestException.php        |  12 ++
 src/old/HttpClientResponse.php                |  59 +++++
 tests/docker-compose.yaml                     | 172 +++++++++++++++
 tests/integration/bootstrap.php               |  28 +++
 tests/unit/Curl/TestCurlClient.php            |  52 +++++
 tests/unit/TestHttpClientFactory.php          |  42 ++++
 tests/unit/bootstrap.php                      |   9 +
 24 files changed, 1035 insertions(+)
 create mode 100644 .gitattributes
 create mode 100644 .gitignore
 create mode 100644 .gitlab-ci.yml
 create mode 100644 changelog.txt
 create mode 100644 composer.json
 create mode 100644 phpunit-integration.xml
 create mode 100644 phpunit-unit.xml
 create mode 100644 src/AbstractForm.php
 create mode 100644 src/ConditionalFormInterface.php
 create mode 100644 src/FormsCollection.php
 create mode 100644 src/old/Curl/CurlClient.php
 create mode 100644 src/old/Curl/CurlExceptionFactory.php
 create mode 100644 src/old/Curl/Exception/CurlException.php
 create mode 100644 src/old/Curl/Exception/CurlTimedOutException.php
 create mode 100644 src/old/HttpClient.php
 create mode 100644 src/old/HttpClientFactory.php
 create mode 100644 src/old/HttpClientOptions.php
 create mode 100644 src/old/HttpClientRequestException.php
 create mode 100644 src/old/HttpClientResponse.php
 create mode 100644 tests/docker-compose.yaml
 create mode 100644 tests/integration/bootstrap.php
 create mode 100644 tests/unit/Curl/TestCurlClient.php
 create mode 100644 tests/unit/TestHttpClientFactory.php
 create mode 100644 tests/unit/bootstrap.php

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..a612c0c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,15 @@
+tests/ export-ignore
+vendor/ export-ignore
+.editorconfig export-ignore
+.gitattributes export-ignore
+.gitignore export-ignore
+.git/ export-ignore
+.gitlab-ci.yml export-ignore
+.idea export-ignore
+apigen.neon export-ignore
+build-coverage/ export-ignore
+docs/ export-ignore
+LICENSE.md export-ignore
+phpcs.xml.dist export-ignore
+phpunit-integration.xml export-ignore
+phpunit-unit.xml export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4b86223
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+/vendor/
+.idea
+composer.lock
+build-coverage
+swagger
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..38447e9
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,9 @@
+variables:
+  DISABLE_FUNCTIONAL: 1
+  DISABLE_ACCEPTANCE: 1
+  DISABLE_PHP_5_5: 1
+  DISABLE_CODECEPTION: 1
+  DISABLE_INTEGRATION_TESTS: 1
+  IS_LIBRARY: 1
+
+include: 'https://gitlab.com/wpdesk/gitlab-ci/raw/master/gitlab-ci-1.2.yml'
diff --git a/changelog.txt b/changelog.txt
new file mode 100644
index 0000000..5b5d82b
--- /dev/null
+++ b/changelog.txt
@@ -0,0 +1,2 @@
+= 1.0 - 2019-12-16 =
+* First release
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..7959928
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,34 @@
+{
+  "name": "wpdesk/wp-forms",
+  "authors": [
+    {
+      "name": "Marcin",
+      "email": "marcin@wpdesk.pl"
+    }
+  ],
+  "require": {
+    "php": ">=5.6",
+    "ext-curl": "*",
+    "ext-json": "*"
+  },
+  "require-dev": {
+    "phpunit/phpunit": "<7",
+    "wp-coding-standards/wpcs": "^0.14.1",
+    "squizlabs/php_codesniffer": "^3.0.2",
+    "mockery/mockery": "*",
+    "10up/wp_mock": "*"
+  },
+  "autoload": {
+    "psr-4": {
+      "WPDesk\\Forms\\": "src/"
+    }
+  },
+  "autoload-dev": {
+  },
+  "scripts": {
+    "phpunit-unit": "phpunit --configuration phpunit-unit.xml --coverage-text --colors=never",
+    "phpunit-unit-fast": "phpunit --configuration phpunit-unit.xml --no-coverage",
+    "phpunit-integration": "phpunit --configuration phpunit-integration.xml --coverage-text --colors=never",
+    "phpunit-integration-fast": "phpunit --configuration phpunit-integration.xml --no-coverage"
+  }
+}
diff --git a/phpunit-integration.xml b/phpunit-integration.xml
new file mode 100644
index 0000000..4a342ab
--- /dev/null
+++ b/phpunit-integration.xml
@@ -0,0 +1,28 @@
+<phpunit bootstrap="tests/integration/bootstrap.php"
+         backupGlobals="false"
+     >
+    <testsuites>
+        <testsuite>
+            <directory prefix="Test" suffix=".php">./tests/integration</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory suffix=".php">src</directory>
+        </whitelist>
+    </filter>
+
+    <logging>
+        <log type="junit" target="build-coverage/report.junit.xml"/>
+        <log type="coverage-html" target="build-coverage/coverage" charset="UTF-8" yui="true" highlight="true"/>
+        <log type="coverage-text" target="build-coverage/coverage.txt"/>
+        <log type="coverage-clover" target="build-coverage/clover.xml"/>
+    </logging>
+
+    <php>
+        <env name="WP_DEVELOP_DIR" value="/tmp/wordpress-develop"/>
+        <env name="WC_DEVELOP_DIR" value="/tmp/woocommerce"/>
+    </php>
+
+</phpunit>
\ No newline at end of file
diff --git a/phpunit-unit.xml b/phpunit-unit.xml
new file mode 100644
index 0000000..31e5c9f
--- /dev/null
+++ b/phpunit-unit.xml
@@ -0,0 +1,21 @@
+<phpunit bootstrap="tests/unit/bootstrap.php">
+    <testsuites>
+        <testsuite>
+            <directory prefix="Test" suffix=".php">./tests/unit/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory suffix=".php">src</directory>
+        </whitelist>
+    </filter>
+
+    <logging>
+        <log type="junit" target="build-coverage/report.junit.xml"/>
+        <log type="coverage-html" target="build-coverage/coverage" charset="UTF-8" yui="true" highlight="true"/>
+        <log type="coverage-text" target="build-coverage/coverage.txt"/>
+        <log type="coverage-clover" target="build-coverage/clover.xml"/>
+    </logging>
+
+</phpunit>
diff --git a/src/AbstractForm.php b/src/AbstractForm.php
new file mode 100644
index 0000000..2edfa3a
--- /dev/null
+++ b/src/AbstractForm.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace WPDesk\Forms;
+
+
+/**
+ * Abstraction layer for forms.
+ *
+ * @package WPDesk\Forms
+ */
+abstract class AbstractForm {
+
+	/**
+	 * Unique form_id.
+	 *
+	 * @var string
+	 */
+	protected $form_id = 'form';
+
+	/**
+	 * Updated data.
+	 *
+	 * @var array
+	 */
+	protected $updated_data = array();
+
+
+	/**
+	 * Create form data and return an associative array.
+	 *
+	 * @return array
+	 */
+	abstract protected function create_form_data();
+
+	/**
+	 * Add array to update data.
+	 *
+	 * @param array $new_data new data to update.
+	 */
+	public function update_form_data( array $new_data = array() ) {
+		$this->updated_data = $new_data;
+	}
+
+	/**
+	 * Merge created and updated data and return associative array. Add to all keys form prefix.
+	 *
+	 * @return array
+	 */
+	public function get_form_data() {
+		return array_merge(
+			$this->create_form_data(),
+			$this->updated_data
+		);
+	}
+
+	/**
+	 * return form Id
+	 *
+	 * @return string
+	 */
+	public function get_form_id() {
+		return $this->form_id;
+	}
+
+}
+
diff --git a/src/ConditionalFormInterface.php b/src/ConditionalFormInterface.php
new file mode 100644
index 0000000..7f94a46
--- /dev/null
+++ b/src/ConditionalFormInterface.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace WPDesk\Forms;
+
+/**
+ * Interface ConditionalFormInterface
+ *
+ * @package WPDesk\Forms
+ */
+interface ConditionalFormInterface
+{
+
+	public function is_active();
+}
+
diff --git a/src/FormsCollection.php b/src/FormsCollection.php
new file mode 100644
index 0000000..2943c71
--- /dev/null
+++ b/src/FormsCollection.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace WPDesk\Forms;
+
+/**
+ * FormsCollection class store AbstractForm instances and merges forms data from all collections
+ *
+ * @package WPDesk\Forms
+ */
+class FormsCollection {
+
+
+	/**
+	 * AbstractForm array collection.
+	 *
+	 * @var AbstractForm[]
+	 */
+	protected $forms = array();
+
+	/**
+	 * Add forms. All keys in this array must be unique, otherwise add_form will throw exception.
+	 *
+	 * @param AbstractForm[] $forms
+	 */
+	public function add_forms( array $forms = array() ) {
+		foreach ( $forms as $form_object ) {
+			$this->add_form( $form_object );
+		}
+	}
+
+	/**
+	 * Add form. If key is not unique throw exception.
+	 *
+	 * @param AbstractForm $form
+	 *
+	 * @throws \OutOfBoundsException
+	 */
+	public function add_form( AbstractForm $form ) {
+		if ( ! $this->is_form_exists( $form->get_form_id() ) ) {
+			$this->forms[ $form->get_form_id() ] = $form;
+		} else {
+			throw new \OutOfBoundsException( 'Form with this key already exists' );
+		}
+	}
+
+	/**
+	 * Is form exists. Checks if key exists in the array of forms and return bool.
+	 *
+	 * @param string $form_id
+	 *
+	 * @return bool
+	 */
+	public function is_form_exists( $form_id ) {
+		return isset( $this->forms[ (string) $form_id ] );
+	}
+
+	/**
+	 * Get form.
+	 *
+	 * @param string $form_id
+	 *
+	 * @return AbstractForm
+	 * @throws \OutOfRangeException
+	 */
+	public function get_form( $form_id ) {
+		if ( $this->is_form_exists( $form_id ) ) {
+			return $this->forms[ (string) $form_id ];
+		}
+
+		throw new \OutOfRangeException( 'Form with this key not exists' );
+	}
+
+	/**
+	 * Get forms data. This method merge all arrays from forms and return associative array for woocommerce form_fields.
+	 *
+	 * @param bool $prefixed if true add form_id as prefix to form keys
+	 *
+	 * @return array
+	 */
+	public function get_forms_data( $prefixed = false) {
+
+		$forms_data = array();
+
+		foreach ( $this->forms as $form ) {
+			if ( $form instanceof ConditionalFormInterface ) {
+				if ( ! $form->is_active() ) {
+					continue;
+				}
+			}
+
+			if( $prefixed ){
+				$forms_data = array_merge( $forms_data, $this->get_prefixed_array( $form->get_form_data(), $form->get_form_id() ) );
+			}else {
+				$forms_data = array_merge( $forms_data, $form->get_form_data() );
+			}
+		}
+
+		return $forms_data;
+	}
+
+	/**
+	 * Get prefixed array returns array with prefixed form_id
+	 *
+	 * @param array  $array.
+	 * @param string $form_id as prefix for array.
+	 *
+	 * @return array
+	 */
+	private function get_prefixed_array( array $array, $form_id ) {
+		return array_combine(
+			array_map( function ( $k ) use ($form_id) {
+				return $form_id . '_' . $k;
+			}, array_keys( $array ) ),
+			$array
+		);
+	}
+
+}
diff --git a/src/old/Curl/CurlClient.php b/src/old/Curl/CurlClient.php
new file mode 100644
index 0000000..00c9686
--- /dev/null
+++ b/src/old/Curl/CurlClient.php
@@ -0,0 +1,202 @@
+<?php
+
+namespace WPDesk\HttpClient\Curl;
+
+use WPDesk\HttpClient\HttpClient;
+use WPDesk\HttpClient\HttpClientResponse;
+use WPDesk\HttpClient\HttpClientRequestException;
+
+class CurlClient implements HttpClient {
+	/** @var resource */
+	private $curlResource;
+	/** @var  string|null|boolean */
+	private $rawResponse;
+	/** @var int */
+	private $httpResponseCode;
+	/** @var array */
+	private $headers = array();
+
+	/**
+	 * @param string $url
+	 * @param string $body
+	 * @param array  $headers
+	 * @param int    $timeOut
+	 *
+	 * @return HttpClientResponse
+	 * @throws HttpClientRequestException
+	 */
+	public function get( $url, $body, array $headers, $timeOut ) {
+		return $this->send( $url, 'GET', $body, $headers, $timeOut );
+	}
+
+	/**
+	 * @param string $url
+	 * @param string $method
+	 * @param string $body
+	 * @param array  $headers
+	 * @param int    $timeOut
+	 *
+	 * @return HttpClientResponse
+	 * @throws HttpClientRequestException
+	 */
+	public function send( $url, $method, $body, array $headers, $timeOut ) {
+		$this->initResource();
+		try {
+			$this->prepareConnection( $url, $method, $body, $headers, $timeOut );
+			$this->sendRequest();
+			$this->throwExceptionIfError();
+			$this->closeConnection();
+
+			return $this->prepareResponse();
+		} catch ( \Exception $e ) {
+			$this->closeConnection();
+			if ( $e instanceof HttpClientRequestException ) {
+				throw $e;
+			}
+			throw CurlExceptionFactory::createDefaultException( $e->getMessage(), $e->getCode(), $e );
+		}
+	}
+
+	private function initResource() {
+		$this->curlResource = \curl_init();
+	}
+
+	/**
+	 * Opens a new curl connection.
+	 *
+	 * @param string $url     The endpoint to send the request to.
+	 * @param string $method  The request method.
+	 * @param string $body    The body of the request.
+	 * @param array  $headers The request headers.
+	 * @param int    $timeOut The timeout in seconds for the request.
+	 */
+	private function prepareConnection( $url, $method, $body, array $headers, $timeOut ) {
+
+		$options = [
+			\CURLOPT_CUSTOMREQUEST  => $method,
+			\CURLOPT_HTTPHEADER     => $this->compileRequestHeaders( $headers ),
+			\CURLOPT_URL            => $url,
+			\CURLOPT_CONNECTTIMEOUT => 10,
+			\CURLOPT_TIMEOUT        => $timeOut,
+			\CURLOPT_RETURNTRANSFER => \true,
+			// Return response as string
+			\CURLOPT_HEADER         => \false,
+			// Enable header processing
+			\CURLOPT_SSL_VERIFYHOST => 2,
+			\CURLOPT_SSL_VERIFYPEER => \true,
+			\CURLOPT_HEADERFUNCTION => function ( $curl, $header ) {
+				$len             = strlen( $header );
+				$this->headers[] = trim( $header );
+
+				return $len;
+			}
+		];
+		if ( $method !== "GET" ) {
+			$options[ \CURLOPT_POSTFIELDS ] = $body;
+		}
+		\curl_setopt_array( $this->curlResource, $options );
+	}
+
+	/**
+	 * Compiles the request headers into a curl-friendly format.
+	 *
+	 * @param array $headers The request headers.
+	 *
+	 * @return array
+	 */
+	private function compileRequestHeaders( array $headers ) {
+		$return = [];
+		foreach ( $headers as $key => $value ) {
+			$return[] = $key . ': ' . $value;
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Send the request and get the raw response from curl
+	 */
+	private function sendRequest() {
+		$this->rawResponse      = \curl_exec( $this->curlResource );
+		$this->httpResponseCode = $this->getHttpResponseCode();
+	}
+
+	/** @return int */
+	private function getHttpResponseCode() {
+		return \intval( \curl_getinfo( $this->curlResource, \CURLINFO_HTTP_CODE ) );
+	}
+
+	private function throwExceptionIfError() {
+		$errorNumber = \curl_errno( $this->curlResource );
+		if ( $errorNumber === 0 ) {
+			return;
+		}
+		$errorMessage = \curl_error( $this->curlResource );
+		throw CurlExceptionFactory::createCurlException( $errorMessage, $errorNumber );
+	}
+
+	/**
+	 * Closes an existing curl connection
+	 */
+	private function closeConnection() {
+		\curl_close( $this->curlResource );
+		$this->curlResource = null;
+	}
+
+	private function prepareResponse() {
+		list( $rawHeaders, $rawBody ) = $this->extractResponseHeadersAndBody();
+
+		return new HttpClientResponse( $rawHeaders, $rawBody, $this->httpResponseCode );
+	}
+
+	/**
+	 * Extracts the headers and the body into a two-part array
+	 *
+	 * @return array
+	 */
+	private function extractResponseHeadersAndBody() {
+		$rawBody    = \trim( $this->rawResponse );
+		$rawHeaders = \trim( implode( "\r\n", $this->headers ) );
+
+		return [ $rawHeaders, $rawBody ];
+	}
+
+	/**
+	 * @param string $url
+	 * @param string $body
+	 * @param array  $headers
+	 * @param int    $timeOut
+	 *
+	 * @return HttpClientResponse
+	 * @throws HttpClientRequestException
+	 */
+	public function post( $url, $body, array $headers, $timeOut ) {
+		return $this->send( $url, 'POST', $body, $headers, $timeOut );
+	}
+
+	/**
+	 * @param string $url
+	 * @param string $body
+	 * @param array  $headers
+	 * @param int    $timeOut
+	 *
+	 * @return HttpClientResponse
+	 * @throws HttpClientRequestException
+	 */
+	public function delete( $url, $body, array $headers, $timeOut ) {
+		return $this->send( $url, 'DELETE', $body, $headers, $timeOut );
+	}
+
+	/**
+	 * @param string $url
+	 * @param string $body
+	 * @param array  $headers
+	 * @param int    $timeOut
+	 *
+	 * @return HttpClientResponse
+	 * @throws HttpClientRequestException
+	 */
+	public function put( $url, $body, array $headers, $timeOut ) {
+		return $this->send( $url, 'PUT', $body, $headers, $timeOut );
+	}
+}
diff --git a/src/old/Curl/CurlExceptionFactory.php b/src/old/Curl/CurlExceptionFactory.php
new file mode 100644
index 0000000..8cba48a
--- /dev/null
+++ b/src/old/Curl/CurlExceptionFactory.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace WPDesk\HttpClient\Curl;
+
+use WPDesk\HttpClient\Curl\Exception\CurlException;
+use WPDesk\HttpClient\Curl\Exception\CurlTimedOutException;
+
+class CurlExceptionFactory
+{
+    /**
+     * Convert curl code to appropriate exception class.
+     *
+     * @param int $curlCode Code from https://curl.haxx.se/libcurl/c/libcurl-errors.html
+     * @param string $curlMessage
+     * @return CurlException
+     */
+    public static function createCurlException($curlCode, $curlMessage) {
+        switch ($curlCode) {
+            case CURLE_OPERATION_TIMEDOUT:
+                return new CurlTimedOutException($curlMessage, $curlCode);
+            default:
+                return self::createDefaultException($curlMessage, $curlCode);
+        }
+    }
+
+    /**
+     * Creates default Curl exception
+     *
+     * @param $code
+     * @param $message
+     * @param \Exception|null $prev
+     * @return CurlException
+     */
+    public static function createDefaultException($code, $message, \Exception $prev = null) {
+        return new CurlException('Default exception: ' . $message, $code, $prev);
+    }
+}
\ No newline at end of file
diff --git a/src/old/Curl/Exception/CurlException.php b/src/old/Curl/Exception/CurlException.php
new file mode 100644
index 0000000..b911492
--- /dev/null
+++ b/src/old/Curl/Exception/CurlException.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace WPDesk\HttpClient\Curl\Exception;
+
+use WPDesk\HttpClient\HttpClientRequestException;
+
+/**
+ * Base class for all curl exceptions.
+ *
+ * @package WPDesk\HttpClient\Curl\Exception
+ */
+class CurlException extends \RuntimeException implements HttpClientRequestException
+{
+
+}
\ No newline at end of file
diff --git a/src/old/Curl/Exception/CurlTimedOutException.php b/src/old/Curl/Exception/CurlTimedOutException.php
new file mode 100644
index 0000000..dd1dbfd
--- /dev/null
+++ b/src/old/Curl/Exception/CurlTimedOutException.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace WPDesk\HttpClient\Curl\Exception;
+
+class CurlTimedOutException extends CurlException
+{
+
+}
\ No newline at end of file
diff --git a/src/old/HttpClient.php b/src/old/HttpClient.php
new file mode 100644
index 0000000..42bdae5
--- /dev/null
+++ b/src/old/HttpClient.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace WPDesk\HttpClient;
+
+
+interface HttpClient
+{
+    /**
+     * @param string $url
+     * @param string $body
+     * @param array $headers
+     * @param int $timeOut
+     * @throw HttpClientRequestException
+     * @return HttpClientResponse
+     */
+    public function put($url, $body, array $headers, $timeOut);
+
+    /**
+     * @param string $url
+     * @param string $body
+     * @param array $headers
+     * @param int $timeOut
+     * @throw HttpClientRequestException
+     * @return HttpClientResponse
+     */
+    public function get($url, $body, array $headers, $timeOut);
+
+    /**
+     * @param string $url
+     * @param string $body
+     * @param array $headers
+     * @param int $timeOut
+     * @throw HttpClientRequestException
+     * @return HttpClientResponse
+     */
+    public function post($url, $body, array $headers, $timeOut);
+
+    /**
+     * @param string $url
+     * @param string $body
+     * @param array $headers
+     * @param int $timeOut
+     * @throw HttpClientRequestException
+     * @return HttpClientResponse
+     */
+    public function delete($url, $body, array $headers, $timeOut);
+
+    /**
+     * @param string $url
+     * @param string $method
+     * @param string $body
+     * @param array $headers
+     * @param int $timeOut
+     * @throw HttpClientRequestException
+     * @return HttpClientResponse
+     */
+    public function send($url, $method, $body, array $headers, $timeOut);
+}
\ No newline at end of file
diff --git a/src/old/HttpClientFactory.php b/src/old/HttpClientFactory.php
new file mode 100644
index 0000000..f2acdae
--- /dev/null
+++ b/src/old/HttpClientFactory.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace WPDesk\HttpClient;
+
+
+class HttpClientFactory
+{
+    /**
+     * @param HttpClientOptions $options
+     * @return HttpClient
+     */
+    public function createClient(HttpClientOptions $options)
+    {
+        $className = $options->getHttpClientClass();
+        return new $className;
+    }
+}
\ No newline at end of file
diff --git a/src/old/HttpClientOptions.php b/src/old/HttpClientOptions.php
new file mode 100644
index 0000000..c58d70a
--- /dev/null
+++ b/src/old/HttpClientOptions.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace WPDesk\HttpClient;
+
+interface HttpClientOptions
+{
+    /**
+     * @return string
+     */
+    public function getHttpClientClass();
+}
\ No newline at end of file
diff --git a/src/old/HttpClientRequestException.php b/src/old/HttpClientRequestException.php
new file mode 100644
index 0000000..50cb789
--- /dev/null
+++ b/src/old/HttpClientRequestException.php
@@ -0,0 +1,12 @@
+<?php
+namespace WPDesk\HttpClient;
+
+/**
+ * Interface for all exceptions that can be thrown by HttpClient
+ *
+ * @package WPDesk\HttpClient
+ */
+interface HttpClientRequestException
+{
+
+}
\ No newline at end of file
diff --git a/src/old/HttpClientResponse.php b/src/old/HttpClientResponse.php
new file mode 100644
index 0000000..292e059
--- /dev/null
+++ b/src/old/HttpClientResponse.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace WPDesk\HttpClient;
+
+
+class HttpClientResponse
+{
+    /** @var  string */
+    private $headers;
+
+    /** @var  string */
+    private $body;
+
+    /** @var int */
+    private $code;
+
+    /**
+     * HttpClientResponse constructor.
+     * @param string $headers
+     * @param string $body
+     * @param int $code
+     */
+    public function __construct($headers, $body, $code)
+    {
+        $this->headers = $headers;
+        $this->body = $body;
+        $this->code = $code;
+    }
+
+    /**
+     * @return array
+     */
+    public function getHeaders()
+    {
+        $headers = array();
+        $headers_rows = explode("\r\n", $this->headers);
+        foreach ($headers_rows as $headers_row) {
+            $header = explode(": ", $headers_row);
+            $headers[$header[0]] = isset($header[1])?$header[1]:$header[0];
+        }
+        return $headers;
+    }
+
+    /**
+     * @return string
+     */
+    public function getBody()
+    {
+        return $this->body;
+    }
+
+    /**
+     * @return int
+     */
+    public function getResponseCode()
+    {
+        return $this->code;
+    }
+}
\ No newline at end of file
diff --git a/tests/docker-compose.yaml b/tests/docker-compose.yaml
new file mode 100644
index 0000000..2a86b03
--- /dev/null
+++ b/tests/docker-compose.yaml
@@ -0,0 +1,172 @@
+version: '2.0'
+
+services:
+
+  wordpress:
+    image: wpdesknet/phpunit-woocommerce:0-0
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql0
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql0
+
+  wordpress-0-1:
+    image: wpdesknet/phpunit-woocommerce:0-1
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql1
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql1
+
+  wordpress-0-2:
+    image: wpdesknet/phpunit-woocommerce:0-2
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql2
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql2
+
+  wordpress-0-3:
+    image: wpdesknet/phpunit-woocommerce:0-3
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql3
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql3
+
+  wordpress-0-4:
+    image: wpdesknet/phpunit-woocommerce:0-4
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql4
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql4
+
+  wordpress-0-5:
+    image: wpdesknet/phpunit-woocommerce:0-5
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql5
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql5
+
+  wordpress-1-0:
+    image: wpdesknet/phpunit-woocommerce:1-0
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql0
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql0
+
+  wordpress-2-0:
+    image: wpdesknet/phpunit-woocommerce:2-0
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql0
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql0
+
+  wordpress-3-0:
+    image: wpdesknet/phpunit-woocommerce:3-0
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql0
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql0
+
+  wordpress-4-0:
+    image: wpdesknet/phpunit-woocommerce:4-0
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql0
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql0
+
+  mysql0:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
+      MYSQL_DATABASE: wptest
+      MYSQL_USER: mysql
+      MYSQL_PASSWORD: mysql
+
+  mysql1:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
+      MYSQL_DATABASE: wptest
+      MYSQL_USER: mysql
+      MYSQL_PASSWORD: mysql
+
+  mysql2:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
+      MYSQL_DATABASE: wptest
+      MYSQL_USER: mysql
+      MYSQL_PASSWORD: mysql
+
+  mysql3:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
+      MYSQL_DATABASE: wptest
+      MYSQL_USER: mysql
+      MYSQL_PASSWORD: mysql
+
+  mysql4:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
+      MYSQL_DATABASE: wptest
+      MYSQL_USER: mysql
+      MYSQL_PASSWORD: mysql
+
+  mysql5:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
+      MYSQL_DATABASE: wptest
+      MYSQL_USER: mysql
+      MYSQL_PASSWORD: mysql
+
diff --git a/tests/integration/bootstrap.php b/tests/integration/bootstrap.php
new file mode 100644
index 0000000..a422fd9
--- /dev/null
+++ b/tests/integration/bootstrap.php
@@ -0,0 +1,28 @@
+<?php
+
+ini_set('error_reporting', E_ALL); // or error_reporting(E_ALL);
+ini_set('display_errors', '1');
+ini_set('display_startup_errors', '1');
+
+require_once __DIR__ . '/../../vendor/autoload.php';
+
+// disable xdebug backtrace
+if ( function_exists( 'xdebug_disable' ) ) {
+	xdebug_disable();
+}
+
+if ( getenv( 'PLUGIN_PATH' ) !== false ) {
+	define( 'PLUGIN_PATH', getenv( 'PLUGIN_PATH' ) );
+} else {
+	define( 'PLUGIN_PATH', __DIR__ . DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR );
+}
+
+require_once( getenv( 'WP_DEVELOP_DIR' ) . '/tests/phpunit/includes/functions.php' );
+
+tests_add_filter( 'muplugins_loaded', function () {
+}, 100 );
+
+putenv('WP_TESTS_DIR=' . getenv( 'WP_DEVELOP_DIR' ) . '/tests/phpunit');
+require_once( getenv( 'WC_DEVELOP_DIR' ) . '/tests/bootstrap.php' );
+
+do_action('plugins_loaded');
\ No newline at end of file
diff --git a/tests/unit/Curl/TestCurlClient.php b/tests/unit/Curl/TestCurlClient.php
new file mode 100644
index 0000000..5ab0faf
--- /dev/null
+++ b/tests/unit/Curl/TestCurlClient.php
@@ -0,0 +1,52 @@
+<?php
+
+
+
+class TestCurlClient extends \PHPUnit\Framework\TestCase
+{
+
+    /**
+     * Test get method.
+     */
+    public function testGet()
+    {
+        $client = new \WPDesk\HttpClient\Curl\CurlClient();
+        $response = $client->get('https://www.google.com', '', array(), 15);
+        $this->assertInstanceOf(\WPDesk\HttpClient\HttpClientResponse::class, $response);
+        $this->assertEquals(200, $response->getResponseCode());
+    }
+
+    /**
+     * Test post method.
+     */
+    public function testPost()
+    {
+        $client = new \WPDesk\HttpClient\Curl\CurlClient();
+        $response = $client->post('https://www.google.com', '', array(), 15);
+        $this->assertInstanceOf(\WPDesk\HttpClient\HttpClientResponse::class, $response);
+        $this->assertEquals(405, $response->getResponseCode());
+    }
+
+    /**
+     * Test put method.
+     */
+    public function testPut()
+    {
+        $client = new \WPDesk\HttpClient\Curl\CurlClient();
+        $response = $client->put('https://www.google.com', '', array(), 15);
+        $this->assertInstanceOf(\WPDesk\HttpClient\HttpClientResponse::class, $response);
+        $this->assertEquals(405, $response->getResponseCode());
+    }
+
+    /**
+     * Test delete method.
+     */
+    public function testDelete()
+    {
+        $client = new \WPDesk\HttpClient\Curl\CurlClient();
+        $response = $client->put('https://www.google.com', '', array(), 15);
+        $this->assertInstanceOf(\WPDesk\HttpClient\HttpClientResponse::class, $response);
+        $this->assertEquals(405, $response->getResponseCode());
+    }
+
+}
\ No newline at end of file
diff --git a/tests/unit/TestHttpClientFactory.php b/tests/unit/TestHttpClientFactory.php
new file mode 100644
index 0000000..9c0fe7f
--- /dev/null
+++ b/tests/unit/TestHttpClientFactory.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Tests;
+
+use WPDesk\Forms\AbstractForm;
+use WPDesk\Forms\ConditionalFormInterface;
+use WPDesk\Forms\FormsCollection;
+
+class TestHttpClientFactory extends \PHPUnit\Framework\TestCase
+{
+
+	private $anonymousClass;
+
+	protected function setUp(){
+		// Create a new instance from the Abstract Class
+		$this->anonymousClass = new class extends AbstractForm {
+			// Just a sample public function that returns this anonymous instance
+			protected function create_form_data()
+			{
+				return array('test' => true);
+			}
+		};
+
+
+	}
+
+    /**
+     * Test createClient method.
+     */
+    public function testCreateClient()
+    {
+        $options = Mockery::mock(\WPDesk\HttpClient\HttpClientOptions::class);
+        $options->shouldReceive('getHttpClientClass')
+            ->withAnyArgs()
+            ->andReturn(\WPDesk\HttpClient\Curl\CurlClient::class);
+        $factory = new \WPDesk\HttpClient\HttpClientFactory();
+        $client = $factory->createClient($options);
+        $this->assertInstanceOf(\WPDesk\HttpClient\Curl\CurlClient::class, $client);
+    }
+
+
+}
\ No newline at end of file
diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php
new file mode 100644
index 0000000..76b8109
--- /dev/null
+++ b/tests/unit/bootstrap.php
@@ -0,0 +1,9 @@
+<?php
+/**
+ * PHPUnit bootstrap file
+ */
+
+require_once __DIR__ . '/../../vendor/autoload.php';
+
+WP_Mock::setUsePatchwork( true );
+WP_Mock::bootstrap();
-- 
GitLab