├── .ddev └── config.yaml ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE.md ├── README.md ├── composer.json ├── ecs.php ├── examples └── simple-email-form │ ├── controller.php │ └── index.php ├── phpunit.xml.dist ├── rector.php ├── src ├── Cookie.php ├── HttpHeader.php ├── Mautic.php └── Mautic │ ├── Config.php │ ├── Contact.php │ ├── Cookie.php │ └── Form.php └── tests ├── CookieTest.php ├── HttpHeaderTest.php ├── Mautic ├── ContactTest.php ├── CookieTest.php └── FormTest.php └── MauticTest.php /.ddev/config.yaml: -------------------------------------------------------------------------------- 1 | name: mautic-form-submit 2 | type: php 3 | docroot: "examples/simple-email-form/" 4 | php_version: "8.1" 5 | webserver_type: nginx-fpm 6 | xdebug_enabled: false 7 | additional_hostnames: [] 8 | additional_fqdns: [] 9 | use_dns_when_possible: true 10 | composer_version: "2" 11 | web_environment: [] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | composer.lock 3 | vendor 4 | .idea 5 | .phpunit.result.cache -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [tests/*] 3 | 4 | checks: 5 | php: 6 | remove_extra_empty_lines: true 7 | remove_php_closing_tag: true 8 | remove_trailing_whitespace: true 9 | fix_use_statements: 10 | remove_unused: true 11 | preserve_multiple: false 12 | preserve_blanklines: true 13 | order_alphabetically: true 14 | fix_php_opening_tag: true 15 | fix_linefeed: true 16 | fix_line_ending: true 17 | fix_identation_4spaces: true 18 | fix_doc_comments: true 19 | 20 | tools: 21 | external_code_coverage: 22 | timeout: 600 23 | runs: 2 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | - 7.1 7 | 8 | # This triggers builds to run on the new TravisCI infrastructure. 9 | # See: http://docs.travis-ci.com/user/workers/container-based-infrastructure/ 10 | sudo: false 11 | 12 | cache: 13 | directories: 14 | - $HOME/.composer/cache 15 | 16 | before_install: 17 | # turn off XDebug 18 | - phpenv config-rm xdebug.ini || return 19 | 20 | # install dependencies in parallel 21 | - travis_retry composer global require hirak/prestissimo 22 | 23 | # install PHPSTAN globally for PHP 7+ 24 | - if [[ $TRAVIS_PHP_VERSION != '5.6' ]]; then composer global require phpstan/phpstan-shim; fi 25 | 26 | before_script: 27 | - travis_retry composer self-update 28 | - travis_retry composer install --no-interaction 29 | - COMPOSER_PROCESS_TIMEOUT=0 composer test-server > /dev/null 2>&1 & 30 | 31 | script: 32 | # Run code standards analysis 33 | - vendor/bin/phpcs --standard=psr2 src/ 34 | 35 | # Run PHPUnit with test coverage report 36 | - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover 37 | 38 | # Run PHPSTAN analysis for PHP 7+ 39 | - if [[ $TRAVIS_PHP_VERSION != '5.6' ]]; then ~/.composer/vendor/phpstan/phpstan-shim/phpstan.phar analyse src tests -l 5; fi 40 | 41 | after_script: 42 | - if [[ $TRAVIS_PHP_VERSION != '7.1' ]]; then php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover; fi 43 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2017, Matthew Allan (matthew.james.allan@gmail.com) and the JSON Reference contributors 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP library for submitting Mautic Form from 3rd party app 2 | 3 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/escopecz/mautic-form-submit/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/escopecz/mautic-form-submit/?branch=master) 4 | [![Build Status](https://scrutinizer-ci.com/g/escopecz/mautic-form-submit/badges/build.png?b=master)](https://scrutinizer-ci.com/g/escopecz/mautic-form-submit/build-status/master) 5 | [![Code Coverage](https://scrutinizer-ci.com/g/escopecz/mautic-form-submit/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/escopecz/mautic-form-submit/?branch=master) 6 | 7 | Submitting a form can get handy if you want to process the data with your app, but you want to send them to Mautic too. Mautic can then run automated tasks triggered by the form submission. [Read more about it](https://medium.com/@jan_linhart/the-simplest-way-how-to-submit-a-form-data-to-mautic-1454d3afd005) in the original post. 8 | 9 | Since the new Mautic versions prefer cookie tracking over IP tracking which makes more tedious to submit the form as the tracked contact, this library will take care of the cookie sending via CURL. It will also listen the cookie from the response and updates the contact cookie with the values from the submit response. This way if the contact ID changes because of contact merge, the contact will continue browsing under the new contact ID. 10 | 11 | The automatic cookie handling requires that your form will be on a page tracked by the Mautic JS tracking which provides the Mautic contact cookie in the first place. 12 | 13 | ## Install 14 | 15 | ### Via Composer 16 | 17 | ```bash 18 | composer require escopecz/mautic-form-submit 19 | ``` 20 | 21 | ## Usage 22 | 23 | ```php 24 | // Require Composer autoloader 25 | require __DIR__.'/vendor/autoload.php'; 26 | 27 | // Define the namespace of the Mautic object 28 | use Escopecz\MauticFormSubmit\Mautic; 29 | 30 | // Define the namespace of the Mautic configuration object 31 | use Escopecz\MauticFormSubmit\Mautic\Config; 32 | 33 | // It's optional to declare the configuration object to change some default values. 34 | // For example to disable Curl verbose logging. 35 | $config = new Config; 36 | $config->setCurlVerbose(true); 37 | 38 | // Instantiate the Mautic object with the base URL where the Mautic runs 39 | $mautic = new Mautic('https://mymautic.com'); 40 | 41 | // Create a new instance of the Form object with the form ID 342 42 | $form = $mautic->getForm(342); 43 | 44 | // Submit provided data array to the form 342 45 | $result = $form->submit(['f_email' => 'john@doe.email']); 46 | ``` 47 | 48 | - The integer passed to the `getForm()` method must be ID of the Mautic form. 49 | - The array passed to the `submit()` method must be associative array of `['mautic_field_alias' => 'the_value']`. 50 | 51 | For working example see the `examples` dir. 52 | 53 | ## Run project 54 | ``` 55 | ddev start 56 | ``` 57 | Project url: https://mautic-form-submit.ddev.site/ 58 | 59 | ## Testing 60 | 61 | ``` 62 | composer test 63 | composer cs 64 | composer phpstan 65 | ``` 66 | 67 | ### Current status 68 | 69 | [Travis](https://travis-ci.org/escopecz/mautic-form-submit) 70 | [Scrutinizer](https://scrutinizer-ci.com/g/escopecz/mautic-form-submit) 71 | 72 | ## License 73 | 74 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 75 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "escopecz/mautic-form-submit", 3 | "type": "library", 4 | "description": "A library for submitting Mautic form from a 3rd pary PHP app", 5 | "keywords": [ 6 | "mautic" 7 | ], 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "John Linhart", 12 | "email": "john.linhart@mautic.org", 13 | "homepage": "https://mautic.org", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=8.1", 19 | "ext-curl": "*" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit" : "^10.5", 23 | "scrutinizer/ocular": "~1.9", 24 | "rector/rector": "^1.2", 25 | "phpstan/phpstan": "^1.11", 26 | "symplify/easy-coding-standard": "^12.3" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Escopecz\\MauticFormSubmit\\": "src" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Escopecz\\MauticFormSubmit\\Test\\": "tests" 36 | } 37 | }, 38 | "scripts": { 39 | "test": "phpunit", 40 | "test-coverage": "phpdbg -qrr vendor/bin/phpunit", 41 | "cs": "vendor/bin/ecs --fix", 42 | "phpstan": "vendor/bin/phpstan analyse src tests -l 5" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ecs.php: -------------------------------------------------------------------------------- 1 | withPaths([ 12 | __DIR__ . '/examples', 13 | __DIR__ . '/src', 14 | __DIR__ . '/tests', 15 | ]) 16 | ->withRootFiles() 17 | ->withConfiguredRule( 18 | ArraySyntaxFixer::class, 19 | ['syntax' => 'short'] 20 | ) 21 | ->withRules([ 22 | NoUnusedImportsFixer::class, 23 | ListSyntaxFixer::class, 24 | ]); 25 | -------------------------------------------------------------------------------- /examples/simple-email-form/controller.php: -------------------------------------------------------------------------------- 1 | $val) { 14 | $_SESSION[$key] = $val; 15 | } 16 | 17 | if (isset($_POST['email_label']) && isset($_POST[$_POST['email_label']]) && isset($_POST['mautic_base_url']) && isset($_POST['form_id'])) { 18 | 19 | // It's optional to create a Config DTO object and pass it to the Mautic object. 20 | // For example to set Curl verbose logging to true. 21 | $config = new Config; 22 | $config->setCurlVerbose(true); 23 | 24 | $mautic = new Mautic($_POST['mautic_base_url'], $config); 25 | $form = $mautic->getForm($_POST['form_id']); 26 | 27 | $info = $form->submit( 28 | [ 29 | $_POST['email_label'] => $_POST[$_POST['email_label']], 30 | ] 31 | ); 32 | 33 | $_SESSION['info'] = $info; 34 | } 35 | 36 | header(rtrim('Location: http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'], '/controller.php')); 37 | die(); 38 | } 39 | } 40 | 41 | session_start(); 42 | new Controller; 43 | -------------------------------------------------------------------------------- /examples/simple-email-form/index.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | Simple Email Form Submit 12 | 13 | 14 | 15 | Create a Mautic Form with Email field (must have f_email label). Fill in its ID below. The JS tracking will start working on this page then. The cookie will be populated with Mautic Contact ID which will be used to send the submission. 16 |
17 |

Mautic Form Push Settings

18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 |

The actual Mautic form values

33 |
34 | 35 | 36 |
37 | 38 | Fill in the form and submit. The values will be saved to session and you'll be able to fill in the email. 39 | 40 |
41 | 42 | 43 |
44 |

Last Response:

45 |
46 | 
47 |         
48 | 49 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | paths([ 11 | __DIR__ . '/src', 12 | __DIR__ . '/tests', 13 | ]); 14 | 15 | $rectorConfig->sets([ 16 | LevelSetList::UP_TO_PHP_81, 17 | SetList::DEAD_CODE, 18 | SetList::CODE_QUALITY, 19 | SetList::NAMING, 20 | SetList::TYPE_DECLARATION, 21 | ]); 22 | }; -------------------------------------------------------------------------------- /src/Cookie.php: -------------------------------------------------------------------------------- 1 | store[$key])) { 26 | return filter_var($this->store[$key], FILTER_SANITIZE_SPECIAL_CHARS); 27 | } 28 | 29 | return filter_input(INPUT_COOKIE, $key, FILTER_SANITIZE_SPECIAL_CHARS); 30 | } 31 | 32 | /** 33 | * Get cookie with FILTER_SANITIZE_NUMBER_INT 34 | */ 35 | public function getInt(string $key): int 36 | { 37 | if (isset($this->store[$key])) { 38 | return (int) filter_var($this->store[$key], FILTER_SANITIZE_NUMBER_INT); 39 | } 40 | 41 | return (int) filter_input(INPUT_COOKIE, $key, FILTER_SANITIZE_NUMBER_INT); 42 | } 43 | 44 | public function set(string $key, mixed $value): bool 45 | { 46 | $this->store[$key] = $value; 47 | 48 | return setcookie($key, (string) $value); 49 | } 50 | 51 | public function clear(string $key): static 52 | { 53 | setcookie($key, '', ['expires' => time() - 3600]); 54 | unset($_COOKIE[$key]); 55 | unset($this->store[$key]); 56 | 57 | return $this; 58 | } 59 | 60 | public function getSuperGlobalCookie(): array 61 | { 62 | return $_COOKIE; 63 | } 64 | 65 | /** 66 | * Return all cookies as array merged with current state 67 | */ 68 | public function toArray(): array 69 | { 70 | return array_merge($this->getSuperGlobalCookie(), $this->store); 71 | } 72 | 73 | /** 74 | * Creates unique cookie file in system tmp dir and returns absolute path to it. 75 | */ 76 | public function createCookieFile(): string|false 77 | { 78 | return tempnam(sys_get_temp_dir(), 'mauticcookie'); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/HttpHeader.php: -------------------------------------------------------------------------------- 1 | parse($textHeaders); 16 | } 17 | 18 | public function getHeaderValue(string $key): ?string 19 | { 20 | return $this->headers[$key] ?? null; 21 | } 22 | 23 | public function getCookieValue(?string $key): ?string 24 | { 25 | return $this->cookies[$key] ?? null; 26 | } 27 | 28 | /** 29 | * Parse text headers and fills in cookies and headers properites 30 | */ 31 | private function parse(string $headers): void 32 | { 33 | foreach (preg_split('/\r\n|\r|\n/', $headers) as $i => $line) { 34 | if ($i === 0) { 35 | $this->headers['http_code'] = $line; 36 | } else { 37 | [$key, $value] = explode(': ', $line); 38 | 39 | if ($key === 'Set-Cookie') { 40 | [$textCookie] = explode(';', $value); 41 | [$cookieKey, $cookieValue] = explode('=', $textCookie); 42 | 43 | $this->cookies[$cookieKey] = $cookieValue; 44 | } else { 45 | $this->headers[$key] = $value; 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Mautic.php: -------------------------------------------------------------------------------- 1 | baseUrl = rtrim(trim($baseUrl), '/'); 28 | $this->cookie = new MauticCookie; 29 | $this->contact = new Contact($this->cookie); 30 | $this->config = $config ?: new Config; 31 | } 32 | 33 | public function getBaseUrl(): string 34 | { 35 | return $this->baseUrl; 36 | } 37 | 38 | public function getForm(int $id): Form 39 | { 40 | return new Form($this, $id); 41 | } 42 | 43 | public function setContact(Contact $contact): static 44 | { 45 | $this->contact = $contact; 46 | 47 | return $this; 48 | } 49 | 50 | public function getContact(): Contact 51 | { 52 | return $this->contact; 53 | } 54 | 55 | public function getCookie(): MauticCookie 56 | { 57 | return $this->cookie; 58 | } 59 | 60 | public function getConfig(): Config 61 | { 62 | return $this->config; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Mautic/Config.php: -------------------------------------------------------------------------------- 1 | curlVerbose; 23 | } 24 | 25 | /** 26 | * Set Curl verbose logging option 27 | */ 28 | public function setCurlVerbose(bool $curlVerbose): static 29 | { 30 | $this->curlVerbose = $curlVerbose; 31 | 32 | return $this; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Mautic/Contact.php: -------------------------------------------------------------------------------- 1 | ip = $this->getIpFromServer(); 21 | } 22 | 23 | /** 24 | * Returns Contact ID 25 | */ 26 | public function getId(): int 27 | { 28 | return (int) $this->cookie->getContactId(); 29 | } 30 | 31 | /** 32 | * Set Mautic Contact ID to global cookie 33 | */ 34 | public function setId(int $contactId): static 35 | { 36 | $this->cookie->setContactId($contactId); 37 | 38 | return $this; 39 | } 40 | 41 | public function getIp(): ?string 42 | { 43 | return $this->ip; 44 | } 45 | 46 | public function setIp(string $ip): void 47 | { 48 | $this->ip = $ip; 49 | } 50 | 51 | public function getSessionId(): ?string 52 | { 53 | return $this->cookie->getSessionId(); 54 | } 55 | 56 | 57 | public function setSessionId(string $sessionId): static 58 | { 59 | $this->cookie->setSessionId($sessionId); 60 | 61 | return $this; 62 | } 63 | 64 | public function setDeviceId(string $deviceId): static 65 | { 66 | $this->cookie->setDeviceId($deviceId); 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * Guesses IP address from $_SERVER 73 | */ 74 | public function getIpFromServer(): string 75 | { 76 | $ip = ''; 77 | $ipHolders = [ 78 | 'HTTP_CLIENT_IP', 79 | 'HTTP_X_FORWARDED_FOR', 80 | 'HTTP_X_FORWARDED', 81 | 'HTTP_X_CLUSTER_CLIENT_IP', 82 | 'HTTP_FORWARDED_FOR', 83 | 'HTTP_FORWARDED', 84 | 'REMOTE_ADDR' 85 | ]; 86 | 87 | foreach ($ipHolders as $ipHolder) { 88 | if (!empty($_SERVER[$ipHolder])) { 89 | $ip = $_SERVER[$ipHolder]; 90 | if (str_contains((string) $ip, ',')) { 91 | // Multiple IPs are present so use the last IP which should be 92 | // the most reliable IP that last connected to the proxy 93 | $ips = explode(',', (string) $ip); 94 | $ips = array_map('trim', $ips); 95 | $ip = end($ips); 96 | } 97 | $ip = trim((string) $ip); 98 | break; 99 | } 100 | } 101 | 102 | return $ip; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Mautic/Cookie.php: -------------------------------------------------------------------------------- 1 | getInt(self::MTC_ID)) !== 0) { 40 | return $mtcId; 41 | } elseif ($mauticSessionId = $this->getSessionId()) { 42 | return $this->getInt($mauticSessionId); 43 | } 44 | 45 | return null; 46 | } 47 | 48 | /** 49 | * Set Mautic Contact ID cookies 50 | * Note: Call setMauticSessionId prior to this 51 | */ 52 | public function setContactId(int $contactId): static 53 | { 54 | $this->set(self::MTC_ID, $contactId); 55 | 56 | if ($sessionId = $this->getSessionId()) { 57 | $this->set($sessionId, $contactId); 58 | } 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * Unit Mautic Contact ID cookies 65 | */ 66 | public function unsetContactId(): static 67 | { 68 | $this->clear(self::MTC_ID); 69 | 70 | if ($sessionId = $this->getSessionId()) { 71 | $this->clear($sessionId); 72 | } 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * Returns Mautic session ID if it exists in the cookie 79 | */ 80 | public function getSessionId(): ?string 81 | { 82 | if ($mauticSessionId = $this->get(self::MAUTIC_SESSION_ID)) { 83 | return $mauticSessionId; 84 | } 85 | 86 | if ($mauticSessionId = $this->get(self::MTC_SID)) { 87 | return $mauticSessionId; 88 | } 89 | 90 | return null; 91 | } 92 | 93 | /** 94 | * Set Mautic Session ID cookies 95 | */ 96 | public function setSessionId(string $sessionId): static 97 | { 98 | $this->set(self::MAUTIC_SESSION_ID, $sessionId); 99 | $this->set(self::MTC_SID, $sessionId); 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * Set Mautic Device ID cookies 106 | */ 107 | public function setDeviceId(string $deviceId): static 108 | { 109 | $this->set(self::MAUTIC_DEVICE_ID, $deviceId); 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * Unset Mautic Session ID cookies 116 | */ 117 | public function unsetSessionId(): static 118 | { 119 | $this->clear(self::MAUTIC_SESSION_ID); 120 | $this->clear(self::MTC_SID); 121 | 122 | return $this; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Mautic/Form.php: -------------------------------------------------------------------------------- 1 | mautic->getCookie()->getSuperGlobalCookie(); 27 | $request = $this->prepareRequest($data); 28 | 29 | $ch = curl_init($request['url']); 30 | curl_setopt($ch, CURLOPT_POST, 1); 31 | curl_setopt($ch, CURLOPT_POSTFIELDS, $request['query']); 32 | 33 | if (isset($request['header'])) { 34 | curl_setopt($ch, CURLOPT_HTTPHEADER, $request['header']); 35 | } 36 | 37 | if (isset($request['referer'])) { 38 | curl_setopt($ch, CURLOPT_REFERER, $request['referer']); 39 | } 40 | 41 | if (isset($request['cookie'])) { 42 | curl_setopt($ch, CURLOPT_COOKIEFILE, $this->mautic->getCookie()->createCookieFile()); 43 | } 44 | 45 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 46 | curl_setopt($ch, CURLOPT_VERBOSE, $this->mautic->getConfig()->getCurlVerbose()); 47 | curl_setopt($ch, CURLOPT_HEADER, 1); 48 | 49 | foreach ($curlOpts as $key => $value) { 50 | curl_setopt($ch, $key, $value); 51 | } 52 | 53 | $result = curl_exec($ch); 54 | $response = $this->prepareResponse($result); 55 | $response['info'] = curl_getinfo($ch); 56 | curl_close($ch); 57 | 58 | $contact = $this->mautic->getContact(); 59 | $httpHeader = new HttpHeader($response['header']); 60 | $sessionId = $httpHeader->getCookieValue(Cookie::MAUTIC_SESSION_ID); 61 | $deviceId = $httpHeader->getCookieValue(Cookie::MAUTIC_DEVICE_ID); 62 | $contactId = $httpHeader->getCookieValue($sessionId); 63 | 64 | if ($sessionId) { 65 | $contact->setSessionId($sessionId); 66 | } 67 | 68 | if ($deviceId) { 69 | $contact->setDeviceId($deviceId); 70 | } 71 | 72 | if ($contactId) { 73 | $contact->setId((int)$contactId); 74 | } 75 | 76 | return [ 77 | 'original_cookie' => $originalCookie, 78 | 'new_cookie' => $this->mautic->getCookie()->toArray(), 79 | 'request' => $request, 80 | 'response' => $response, 81 | ]; 82 | } 83 | 84 | /** 85 | * Prepares data for CURL request based on provided form data, $_COOKIE and $_SERVER 86 | */ 87 | public function prepareRequest(array $data): array 88 | { 89 | $contact = $this->mautic->getContact(); 90 | $request = ['header' => []]; 91 | 92 | $data['formId'] = $this->id; 93 | 94 | // return has to be part of the form data array so Mautic would accept the submission 95 | if (!isset($data['return'])) { 96 | $data['return'] = ''; 97 | } 98 | 99 | $request['url'] = $this->getUrl(); 100 | $request['data'] = ['mauticform' => $data]; 101 | 102 | if ($contactId = $contact->getId()) { 103 | $request['data']['mtc_id'] = $contactId; 104 | } 105 | 106 | if ($contactIp = $contact->getIp()) { 107 | $request['header'][] = "X-Forwarded-For: $contactIp"; 108 | $request['header'][] = "Client-Ip: $contactIp"; 109 | } 110 | 111 | if ($sessionId = $contact->getSessionId()) { 112 | $request['header'][] = "Cookie: mautic_session_id=$sessionId"; 113 | $request['header'][] = "Cookie: mautic_device_id=$sessionId"; 114 | } 115 | 116 | if (isset($_SERVER['HTTP_REFERER'])) { 117 | $request['referer'] = $_SERVER["HTTP_REFERER"]; 118 | } 119 | 120 | $request['query'] = http_build_query($request['data']); 121 | 122 | return $request; 123 | } 124 | 125 | /** 126 | * Process the result and split into headers and content 127 | */ 128 | public function prepareResponse(string|bool $result): array 129 | { 130 | $response = ['header' => null, 'content' => null]; 131 | $d = "\r\n\r\n"; // Headers and content delimiter 132 | 133 | if (is_string($result) && str_contains($result, $d)) { 134 | [$header, $content] = explode($d, $result, 2); 135 | if (stripos($header, '100 Continue') !== false && str_contains($content, $d)) { 136 | [$header, $content] = explode($d, $content, 2); 137 | } 138 | $response['header'] = $header; 139 | $response['content'] = htmlentities($content); 140 | } 141 | 142 | return $response; 143 | } 144 | 145 | /** 146 | * Builds the form URL 147 | */ 148 | public function getUrl(): string 149 | { 150 | return sprintf('%s/form/submit?formId=%d', $this->mautic->getBaseUrl(), $this->id); 151 | } 152 | 153 | public function getId(): int 154 | { 155 | return $this->id; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /tests/CookieTest.php: -------------------------------------------------------------------------------- 1 | assertNull($cookie->get($key)); 22 | 23 | $val = 452423; 24 | $cookie->set($key, $val); 25 | 26 | $this->assertSame((string) $val, $cookie->get($key)); 27 | $this->assertSame($val, $cookie->getInt($key)); 28 | 29 | $cookie->clear($key); 30 | 31 | $this->assertSame(0, $cookie->getInt($key)); 32 | } 33 | 34 | /** 35 | * @runInSeparateProcess 36 | */ 37 | function test_super_global_cookie(): void 38 | { 39 | $cookie = new Cookie(); 40 | 41 | $this->assertTrue(is_array($cookie->getSuperGlobalCookie())); 42 | } 43 | 44 | /** 45 | * @runInSeparateProcess 46 | */ 47 | function test_to_array(): void 48 | { 49 | $cookie = new Cookie(); 50 | $key = 'some_cookie'; 51 | $val = 452423; 52 | $cookie->set($key, $val); 53 | 54 | $this->assertSame([$key => $val], $cookie->toArray()); 55 | } 56 | 57 | function test_get_cookie_file(): void 58 | { 59 | $cookie = new Cookie; 60 | $file = $cookie->createCookieFile(); 61 | 62 | $this->assertTrue(is_string($file)); 63 | $this->assertTrue(file_exists($file)); 64 | $this->assertTrue(is_writable($file)); 65 | $this->assertTrue(unlink($file)); 66 | } 67 | } -------------------------------------------------------------------------------- /tests/HttpHeaderTest.php: -------------------------------------------------------------------------------- 1 | testTextHeader); 34 | $this->assertEquals('6txmz3mu2dslmkhrera668e', $httpHeader->getCookieValue('mautic_session_id')); 35 | $this->assertEquals('18061', $httpHeader->getCookieValue('mtc_id')); 36 | $this->assertEquals('18061', $httpHeader->getCookieValue('6txmz3mu2dslmkhrera668e')); 37 | $this->assertEquals('Apache/2.4.33 (Unix) OpenSSL/1.0.2o PHP/7.1.16', $httpHeader->getHeaderValue('Server')); 38 | } 39 | } -------------------------------------------------------------------------------- /tests/Mautic/ContactTest.php: -------------------------------------------------------------------------------- 1 | baseUrl); 23 | $contact = $mautic->getContact(); 24 | 25 | $this->assertInstanceOf(Contact::class, $contact); 26 | $this->assertSame(0, $contact->getId()); 27 | $this->assertSame('', $contact->getIp()); 28 | } 29 | 30 | /** 31 | * @runInSeparateProcess 32 | */ 33 | function test_set_get_id(): void 34 | { 35 | $contactId = 452; 36 | $mautic = new Mautic($this->baseUrl); 37 | $contact = $mautic->getContact(); 38 | $contact->setId($contactId); 39 | 40 | $this->assertSame($contactId, $contact->getId()); 41 | } 42 | 43 | function test_set_get_ip(): void 44 | { 45 | $ip = '345.2.2.2'; 46 | $mautic = new Mautic($this->baseUrl); 47 | $contact = $mautic->getContact(); 48 | $contact->setIp($ip); 49 | 50 | $this->assertSame($ip, $contact->getIp()); 51 | } 52 | 53 | /** 54 | * @runInSeparateProcess 55 | */ 56 | function test_get_id_from_mtc_id_cookie(): void 57 | { 58 | $contactId = 4344; 59 | $cookie = new Cookie; 60 | $cookie->setContactId($contactId); 61 | $contact = new Contact($cookie); 62 | 63 | $this->assertSame($contactId, $contact->getId()); 64 | $cookie->clear(Cookie::MTC_ID); 65 | } 66 | 67 | /** 68 | * @runInSeparateProcess 69 | */ 70 | function test_get_id_from_mautic_session_id_cookie(): void 71 | { 72 | $contactId = 4344; 73 | $sessionId = 'slk3jhkn3gkn23lkgn3lkgn'; 74 | $cookie = new Cookie; 75 | $cookie->setSessionId($sessionId) 76 | ->setContactId($contactId); 77 | $contact = new Contact($cookie); 78 | 79 | $this->assertEquals($contactId, $contact->getId()); 80 | $cookie->unsetSessionId() 81 | ->unsetContactId(); 82 | } 83 | 84 | function test_get_ip_from_server(): void 85 | { 86 | $contactIp = '345.2.2.2'; 87 | $_SERVER['REMOTE_ADDR'] = $contactIp; 88 | $contact = new Contact(new Cookie); 89 | 90 | $this->assertSame($contactIp, $contact->getIp()); 91 | unset($_SERVER['REMOTE_ADDR']); 92 | } 93 | 94 | function test_get_ip_from_server_method(): void 95 | { 96 | $contact = new Contact(new Cookie); 97 | 98 | $this->assertSame('', $contact->getIpFromServer()); 99 | 100 | $contactIp = '345.2.2.2'; 101 | $_SERVER['REMOTE_ADDR'] = $contactIp; 102 | 103 | $this->assertSame($contactIp, $contact->getIpFromServer()); 104 | unset($_SERVER['REMOTE_ADDR']); 105 | } 106 | 107 | function test_get_ip_from_server_method_when_multiple_ips(): void 108 | { 109 | $contact = new Contact(new Cookie); 110 | 111 | $this->assertSame('', $contact->getIpFromServer()); 112 | 113 | $_SERVER['REMOTE_ADDR'] = '222.333.444.4., 555.666.777.7, 345.2.2.2'; 114 | 115 | // The last IP from the list is the right one 116 | $this->assertSame('345.2.2.2', $contact->getIpFromServer()); 117 | unset($_SERVER['REMOTE_ADDR']); 118 | } 119 | 120 | /** 121 | * @runInSeparateProcess 122 | */ 123 | function test_set_session_id_to_cookie(): void 124 | { 125 | $cookie = new Cookie; 126 | $contact = new Contact($cookie); 127 | 128 | $this->assertSame(null, $contact->getSessionId()); 129 | 130 | $sessionId = 'sadfasfd98fuasofuasd9f87asfo'; 131 | $contact->setSessionId($sessionId); 132 | 133 | $this->assertSame($sessionId, $contact->getSessionId()); 134 | $cookie->unsetSessionId(); 135 | } 136 | 137 | /** 138 | * @runInSeparateProcess 139 | */ 140 | function test_set_contact_id_to_cookie(): void 141 | { 142 | $cookie = new Cookie; 143 | $contact = new Contact($cookie); 144 | 145 | $this->assertSame(0, $contact->getId()); 146 | 147 | $contactId = 2332; 148 | $contact->setId($contactId); 149 | 150 | $this->assertSame($contactId, $contact->getId()); 151 | $this->assertEquals($contactId, $cookie->getContactId()); 152 | $cookie->unsetContactId(); 153 | } 154 | } -------------------------------------------------------------------------------- /tests/Mautic/CookieTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(null, $cookie->getContactId()); 20 | 21 | $contactId = 4344; 22 | $cookie->setContactId($contactId); 23 | 24 | $this->assertEquals($contactId, $cookie->getContactId()); 25 | $cookie->unsetContactId(); 26 | 27 | $this->assertEquals(null, $cookie->getContactId()); 28 | 29 | } 30 | 31 | /** 32 | * @runInSeparateProcess 33 | */ 34 | function test_set_get_unset_session_id(): void 35 | { 36 | $cookie = new Cookie; 37 | 38 | $this->assertSame(null, $cookie->getSessionId()); 39 | 40 | $sid = 'kjsfk3j2jnfl2kj3rl2kj'; 41 | $cookie->set(Cookie::MAUTIC_SESSION_ID, $sid); 42 | 43 | $this->assertSame($sid, $cookie->getSessionId()); 44 | $cookie->clear(Cookie::MAUTIC_SESSION_ID); 45 | 46 | $this->assertSame(null, $cookie->getSessionId()); 47 | 48 | $cookie->set(Cookie::MTC_SID, $sid); 49 | $this->assertSame($sid, $cookie->getSessionId()); 50 | 51 | $cookie->clear(Cookie::MTC_SID); 52 | $this->assertSame(null, $cookie->getSessionId()); 53 | } 54 | 55 | /** 56 | * @runInSeparateProcess 57 | */ 58 | function test_set_get_unset_session_id2(): void 59 | { 60 | $cookie = new Cookie; 61 | 62 | $this->assertSame(null, $cookie->getSessionId()); 63 | 64 | $sid = 'kjsfk3j2jnfl2kj3rl2kj'; 65 | $cookie->setSessionId($sid); 66 | 67 | $this->assertSame($sid, $cookie->getSessionId()); 68 | 69 | $cookie->unsetSessionId(); 70 | 71 | $this->assertSame(null, $cookie->getSessionId()); 72 | 73 | $cookie->set(Cookie::MTC_SID, $sid); 74 | 75 | $this->assertSame($sid, $cookie->getSessionId()); 76 | 77 | $cookie->unsetSessionId(); 78 | 79 | $this->assertSame(null, $cookie->getSessionId()); 80 | } 81 | } -------------------------------------------------------------------------------- /tests/Mautic/FormTest.php: -------------------------------------------------------------------------------- 1 | baseUrl); 19 | $formId = 3434; 20 | $form = new Form($mautic, $formId); 21 | 22 | $this->assertSame($formId, $form->getId()); 23 | } 24 | 25 | function test_get_id_int_in_mautic_object(): void 26 | { 27 | $mautic = new Mautic($this->baseUrl); 28 | $formId = 3434; 29 | $form = $mautic->getForm($formId); 30 | 31 | $this->assertSame($formId, $form->getId()); 32 | } 33 | 34 | function test_prepare_request(): void 35 | { 36 | $mautic = new Mautic($this->baseUrl); 37 | $formId = 3434; 38 | $form = new Form($mautic, $formId); 39 | $data = [ 40 | 'email' => 'john@doe.email', 41 | 'first_name' => 'John', 42 | 'last_name' => 'Doe', 43 | ]; 44 | $request = $form->prepareRequest($data); 45 | 46 | $this->assertSame($this->baseUrl.'/form/submit?formId='.$formId, $request['url']); 47 | $this->assertSame($data['email'], $request['data']['mauticform']['email']); 48 | $this->assertSame($data['first_name'], $request['data']['mauticform']['first_name']); 49 | $this->assertSame($data['last_name'], $request['data']['mauticform']['last_name']); 50 | $this->assertSame($formId, $request['data']['mauticform']['formId']); 51 | $this->assertSame('', $request['data']['mauticform']['return']); 52 | } 53 | 54 | /** 55 | * @dataProvider response_result_provider 56 | */ 57 | function test_prepare_response($result, $expectedHeader, $expectedContentType): void 58 | { 59 | $mautic = new Mautic($this->baseUrl); 60 | $formId = 3434; 61 | $form = $mautic->getForm($formId); 62 | 63 | $response = $form->prepareResponse($result); 64 | 65 | $this->assertSame($expectedHeader, $response['header']); 66 | switch ($expectedContentType) { 67 | case 'string': 68 | $this->assertIsString($response['content']); 69 | break; 70 | case 'null': 71 | $this->assertNull($response['content']); 72 | break; 73 | default: 74 | throw new \InvalidArgumentException("Nieznany typ: $expectedContentType"); 75 | } 76 | } 77 | 78 | static function response_result_provider(): array 79 | { 80 | $continue = "HTTP/1.1 100 Continue"; 81 | $header = "HTTP/1.1 302 Found\r 82 | Date: Wed, 17 Apr 2019 11:41:44 GMT\r 83 | Content-Type: text/html; charset=UTF-8\r 84 | Location: ..."; 85 | $content = ''; 86 | 87 | $d = "\r\n\r\n"; // Delimiter between headers and content 88 | 89 | return [ 90 | // Normal response: headers + content 91 | [$header . $d . $content, $header, 'string'], 92 | 93 | // Continue response: 100 Continue + headers + content 94 | [$continue . $d . $header . $d . $content, $header, 'string'], 95 | 96 | // cURL returning false because of failure to execute request 97 | [false, null, 'null'], 98 | ]; 99 | } 100 | 101 | function test_get_url(): void 102 | { 103 | $mautic = new Mautic($this->baseUrl); 104 | $formId = 3434; 105 | $form = $mautic->getForm($formId); 106 | 107 | $this->assertSame($this->baseUrl.'/form/submit?formId='.$formId, $form->getUrl()); 108 | } 109 | } -------------------------------------------------------------------------------- /tests/MauticTest.php: -------------------------------------------------------------------------------- 1 | baseUrl); 20 | $this->assertSame($this->baseUrl, $mautic->getBaseUrl()); 21 | } 22 | 23 | function test_get_form(): void 24 | { 25 | $mautic = new Mautic($this->baseUrl); 26 | $formId = 7; 27 | $form = $mautic->getForm($formId); 28 | 29 | $this->assertInstanceOf(Form::class, $form); 30 | $this->assertSame($formId, $form->getId()); 31 | } 32 | 33 | /** 34 | * @runInSeparateProcess 35 | */ 36 | function test_get_contact(): void 37 | { 38 | $mautic = new Mautic($this->baseUrl); 39 | $contact = $mautic->getContact(); 40 | 41 | $this->assertInstanceOf(Contact::class, $contact); 42 | $this->assertSame(0, $contact->getId()); 43 | $this->assertSame('', $contact->getIp()); 44 | } 45 | 46 | /** 47 | * @runInSeparateProcess 48 | */ 49 | function test_get_set_contact(): void 50 | { 51 | $mautic = new Mautic($this->baseUrl); 52 | $contactId = 4; 53 | $contactIp = '234.3.2.33'; 54 | $contact = new Contact(new Cookie); 55 | $contact->setId($contactId) 56 | ->setIp($contactIp); 57 | $mautic->setContact($contact); 58 | $contactB = $mautic->getContact(); 59 | 60 | $this->assertInstanceOf(Contact::class, $contactB); 61 | $this->assertSame($contact->getId(), $contactB->getId()); 62 | $this->assertSame($contact->getIp(), $contactB->getIp()); 63 | $this->assertSame($contactId, $contactB->getId()); 64 | $this->assertSame($contactIp, $contactB->getIp()); 65 | } 66 | } --------------------------------------------------------------------------------