Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
Mautic.integrationsConfigOnLoad = function () {
|
||||
mQuery('.integration-keyword-filter').each(function() {
|
||||
mQuery(this).off("keyup.integration-filter").on("keyup.integration-filter", function (event) {
|
||||
var integration = mQuery(this).attr('data-integration');
|
||||
var object = mQuery(this).attr('data-object');
|
||||
Mautic.getPaginatedIntegrationFields(
|
||||
{
|
||||
'integration': integration,
|
||||
'object': object,
|
||||
'keyword': mQuery(this).val()
|
||||
},
|
||||
1,
|
||||
this
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Mautic.activateIntegrationFieldUpdateActions();
|
||||
};
|
||||
|
||||
Mautic.getPaginatedIntegrationFields = function(settings, page, element) {
|
||||
var requestName = settings.integration + '-' + settings.object;
|
||||
var action = mauticBaseUrl + 's/integration/' + settings.integration + '/config/' + settings.object + '/' + page;
|
||||
if (settings.keyword) {
|
||||
action = action + '?keyword=' + settings.keyword;
|
||||
}
|
||||
|
||||
if (typeof Mautic.activeActions == 'undefined') {
|
||||
Mautic.activeActions = {};
|
||||
} else if (typeof Mautic.activeActions[requestName] != 'undefined') {
|
||||
Mautic.activeActions[requestName].abort();
|
||||
}
|
||||
|
||||
var object = settings.object;
|
||||
var fieldsTab = '#field-mappings-'+object+'-container';
|
||||
|
||||
if (element && mQuery(element).is('input')) {
|
||||
Mautic.activateLabelLoadingIndicator(mQuery(element).attr('id'));
|
||||
}
|
||||
var fieldsContainer = '#field-mappings-'+object;
|
||||
|
||||
var modalId = '#'+mQuery(fieldsContainer).closest('.modal').attr('id');
|
||||
Mautic.startModalLoadingBar(modalId);
|
||||
|
||||
Mautic.activeActions[requestName] = mQuery.ajax({
|
||||
showLoadingBar: false,
|
||||
url: action,
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
mQuery(fieldsContainer).html(response.html);
|
||||
Mautic.onPageLoad(fieldsContainer);
|
||||
Mautic.activateIntegrationFieldUpdateActions();
|
||||
if (mQuery(fieldsTab).length) {
|
||||
mQuery(fieldsTab).removeClass('hide');
|
||||
}
|
||||
} else if (mQuery(fieldsTab).length) {
|
||||
mQuery(fieldsTab).addClass('hide');
|
||||
}
|
||||
|
||||
if (element) {
|
||||
Mautic.removeLabelLoadingIndicator();
|
||||
}
|
||||
|
||||
Mautic.stopModalLoadingBar(modalId);
|
||||
},
|
||||
error: function (request, textStatus, errorThrown) {
|
||||
Mautic.processAjaxError(request, textStatus, errorThrown);
|
||||
},
|
||||
complete: function () {
|
||||
delete Mautic.activeActions[requestName]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Mautic.updateIntegrationField = function(integration, object, field, fieldOption, fieldValue) {
|
||||
var action = mauticBaseUrl + 's/integration/' + integration + '/config/' + object + '/field/' + field;
|
||||
var modal = mQuery('form[name=integration_config]').closest('.modal');
|
||||
var requestName = integration + object + field + fieldOption;
|
||||
|
||||
// Disable submit buttons until the action is done so nothing is lost
|
||||
mQuery(modal).find('.modal-form-buttons .btn').prop('disabled', true);
|
||||
|
||||
if (typeof Mautic.activeActions == 'undefined') {
|
||||
Mautic.activeActions = {};
|
||||
} else if (typeof Mautic.activeActions[requestName] != 'undefined') {
|
||||
Mautic.activeActions[requestName].abort();
|
||||
}
|
||||
|
||||
Mautic.startModalLoadingBar(mQuery(modal).attr('id'));
|
||||
|
||||
// Must use bracket notation to use variable for key
|
||||
var obj = {};
|
||||
obj[fieldOption] = fieldValue;
|
||||
|
||||
Mautic.activeActions[requestName] = mQuery.ajax({
|
||||
showLoadingBar: false,
|
||||
url: action,
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: obj,
|
||||
error: function (request, textStatus, errorThrown) {
|
||||
Mautic.processAjaxError(request, textStatus, errorThrown);
|
||||
},
|
||||
complete: function () {
|
||||
modal.find('.modal-form-buttons .btn').prop('disabled', false);
|
||||
delete Mautic.activeActions[requestName];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Mautic.activateIntegrationFieldUpdateActions = function () {
|
||||
mQuery('.integration-mapped-field').each(function() {
|
||||
mQuery(this).off("change.integration-mapped-field").on("change.integration-mapped-field", function (event) {
|
||||
var integration = mQuery(this).attr('data-integration');
|
||||
var object = mQuery(this).attr('data-object');
|
||||
var field = mQuery(this).attr('data-field');
|
||||
Mautic.updateIntegrationField(integration, object, field, 'mappedField', mQuery(this).val());
|
||||
});
|
||||
});
|
||||
|
||||
mQuery('.integration-sync-direction').each(function() {
|
||||
mQuery(this).off("change.integration-sync-direction").on("change.integration-sync-direction", function (event) {
|
||||
var integration = mQuery(this).attr('data-integration');
|
||||
var object = mQuery(this).attr('data-object');
|
||||
var field = mQuery(this).attr('data-field');
|
||||
Mautic.updateIntegrationField(integration, object, field, 'syncDirection', mQuery(this).val());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Mautic.authorizeIntegration = function () {
|
||||
mQuery('#integration_details_in_auth').val(1);
|
||||
Mautic.postForm(mQuery('form[name="integration_config"]'), 'loadIntegrationAuthWindow');
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials;
|
||||
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
|
||||
interface HeaderCredentialsInterface extends AuthCredentialsInterface
|
||||
{
|
||||
public function getKeyName(): string;
|
||||
|
||||
public function getApiKey(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials;
|
||||
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
|
||||
interface ParameterCredentialsInterface extends AuthCredentialsInterface
|
||||
{
|
||||
public function getKeyName(): string;
|
||||
|
||||
public function getApiKey(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\ApiKey;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Handler\CurlHandler;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Middleware;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\HeaderCredentialsInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\ParameterCredentialsInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface;
|
||||
use Mautic\IntegrationsBundle\Exception\InvalidCredentialsException;
|
||||
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
|
||||
|
||||
/**
|
||||
* Factory for building HTTP clients using basic auth.
|
||||
*/
|
||||
class HttpFactory implements AuthProviderInterface
|
||||
{
|
||||
public const NAME = 'api_key';
|
||||
|
||||
/**
|
||||
* Cache of initialized clients.
|
||||
*
|
||||
* @var Client[]
|
||||
*/
|
||||
private array $initializedClients = [];
|
||||
|
||||
private HeaderCredentialsInterface|ParameterCredentialsInterface|null $credentials = null;
|
||||
|
||||
public function getAuthType(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HeaderCredentialsInterface|ParameterCredentialsInterface $credentials
|
||||
*
|
||||
* @throws PluginNotConfiguredException
|
||||
* @throws InvalidCredentialsException
|
||||
*/
|
||||
public function getClient(AuthCredentialsInterface $credentials, ?AuthConfigInterface $config = null): ClientInterface
|
||||
{
|
||||
if (!$this->credentialsAreValid($credentials)) {
|
||||
throw new InvalidCredentialsException(sprintf('Credentials must implement either the %s or %s interfaces', HeaderCredentialsInterface::class, ParameterCredentialsInterface::class));
|
||||
}
|
||||
|
||||
if (!$this->credentialsAreConfigured($credentials)) {
|
||||
throw new PluginNotConfiguredException('API key is missing');
|
||||
}
|
||||
|
||||
// Return cached initialized client if there is one.
|
||||
if (!empty($this->initializedClients[$credentials->getKeyName()])) {
|
||||
return $this->initializedClients[$credentials->getKeyName()];
|
||||
}
|
||||
|
||||
$this->credentials = $credentials;
|
||||
|
||||
if ($credentials instanceof HeaderCredentialsInterface) {
|
||||
$this->initializedClients[$credentials->getKeyName()] = $this->getHeaderClient();
|
||||
|
||||
return $this->initializedClients[$credentials->getKeyName()];
|
||||
}
|
||||
|
||||
$this->initializedClients[$credentials->getKeyName()] = $this->getParameterClient();
|
||||
|
||||
return $this->initializedClients[$credentials->getKeyName()];
|
||||
}
|
||||
|
||||
private function credentialsAreValid(AuthCredentialsInterface $credentials): bool
|
||||
{
|
||||
return $credentials instanceof HeaderCredentialsInterface || $credentials instanceof ParameterCredentialsInterface;
|
||||
}
|
||||
|
||||
private function credentialsAreConfigured(AuthCredentialsInterface $credentials): bool
|
||||
{
|
||||
return !empty($credentials->getApiKey());
|
||||
}
|
||||
|
||||
private function getHeaderClient(): ClientInterface
|
||||
{
|
||||
return new Client(
|
||||
[
|
||||
'headers' => [$this->credentials->getKeyName() => $this->credentials->getApiKey()],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function getParameterClient(): ClientInterface
|
||||
{
|
||||
$handler = new HandlerStack();
|
||||
$handler->setHandler(new CurlHandler());
|
||||
|
||||
$handler->unshift(
|
||||
Middleware::mapRequest(
|
||||
fn (Request $request) => $request->withUri(
|
||||
Uri::withQueryValue($request->getUri(), $this->credentials->getKeyName(), $this->credentials->getApiKey())
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return new Client(
|
||||
[
|
||||
'handler' => $handler,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider;
|
||||
|
||||
interface AuthConfigInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider;
|
||||
|
||||
interface AuthCredentialsInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
|
||||
interface AuthProviderInterface
|
||||
{
|
||||
public function getAuthType(): string;
|
||||
|
||||
public function getClient(AuthCredentialsInterface $credentials, ?AuthConfigInterface $config = null): ClientInterface;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\BasicAuth;
|
||||
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
|
||||
interface CredentialsInterface extends AuthCredentialsInterface
|
||||
{
|
||||
public function getUsername(): ?string;
|
||||
|
||||
public function getPassword(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\BasicAuth;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface;
|
||||
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
|
||||
|
||||
/**
|
||||
* Factory for building HTTP clients using basic auth.
|
||||
*/
|
||||
class HttpFactory implements AuthProviderInterface
|
||||
{
|
||||
public const NAME = 'basic_auth';
|
||||
|
||||
/**
|
||||
* Cache of initialized clients.
|
||||
*
|
||||
* @var Client[]
|
||||
*/
|
||||
private array $initializedClients = [];
|
||||
|
||||
public function getAuthType(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PluginNotConfiguredException
|
||||
*/
|
||||
public function getClient(AuthCredentialsInterface $credentials, ?AuthConfigInterface $config = null): ClientInterface
|
||||
{
|
||||
if (!$this->credentialsAreConfigured($credentials)) {
|
||||
throw new PluginNotConfiguredException('Username and/or password is missing');
|
||||
}
|
||||
|
||||
// Return cached initialized client if there is one.
|
||||
if (!empty($this->initializedClients[$credentials->getUsername()])) {
|
||||
return $this->initializedClients[$credentials->getUsername()];
|
||||
}
|
||||
|
||||
$this->initializedClients[$credentials->getUsername()] = new Client(
|
||||
[
|
||||
'auth' => [
|
||||
$credentials->getUsername(),
|
||||
$credentials->getPassword(),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
return $this->initializedClients[$credentials->getUsername()];
|
||||
}
|
||||
|
||||
protected function credentialsAreConfigured(CredentialsInterface $credentials): bool
|
||||
{
|
||||
return $credentials->getUsername() && $credentials->getPassword();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth1aThreeLegged;
|
||||
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
|
||||
interface CredentialsInterface extends AuthCredentialsInterface
|
||||
{
|
||||
public function getAuthorizationUrl(): string;
|
||||
|
||||
public function getRequestTokenUrl(): string;
|
||||
|
||||
public function getAccessTokenUrl(): string;
|
||||
|
||||
public function getAuthCallbackUrl(): ?string;
|
||||
|
||||
public function getConsumerId(): ?string;
|
||||
|
||||
public function getConsumerSecret(): ?string;
|
||||
|
||||
public function getAccessToken(): ?string;
|
||||
|
||||
public function getRequestToken(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth1aTwoLegged;
|
||||
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
|
||||
interface CredentialsInterface extends AuthCredentialsInterface
|
||||
{
|
||||
public function getAuthUrl(): string;
|
||||
|
||||
public function getConsumerKey(): ?string;
|
||||
|
||||
public function getConsumerSecret(): ?string;
|
||||
|
||||
public function getToken(): ?string;
|
||||
|
||||
public function getTokenSecret(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth1aTwoLegged;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Subscriber\Oauth\Oauth1;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface;
|
||||
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
|
||||
|
||||
/**
|
||||
* Factory for building HTTP clients that will sign the requests with Oauth1a headers.
|
||||
*/
|
||||
class HttpFactory implements AuthProviderInterface
|
||||
{
|
||||
public const NAME = 'oauth1a_two_legged';
|
||||
|
||||
/**
|
||||
* Cache of initialized clients.
|
||||
*
|
||||
* @var Client[]
|
||||
*/
|
||||
private array $initializedClients = [];
|
||||
|
||||
public function getAuthType(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PluginNotConfiguredException
|
||||
*/
|
||||
public function getClient(AuthCredentialsInterface $credentials, ?AuthConfigInterface $config = null): ClientInterface
|
||||
{
|
||||
// Return cached initialized client if there is one.
|
||||
if (!empty($this->initializedClients[$credentials->getConsumerKey()])) {
|
||||
return $this->initializedClients[$credentials->getConsumerKey()];
|
||||
}
|
||||
|
||||
if (!$this->credentialsAreConfigured($credentials)) {
|
||||
throw new PluginNotConfiguredException('Oauth1a Credentials or URL is missing');
|
||||
}
|
||||
|
||||
$this->initializedClients[$credentials->getConsumerKey()] = $this->buildClient($credentials);
|
||||
|
||||
return $this->initializedClients[$credentials->getConsumerKey()];
|
||||
}
|
||||
|
||||
private function buildClient(CredentialsInterface $credentials): Client
|
||||
{
|
||||
$stack = HandlerStack::create();
|
||||
$stack->push($this->createOauth1($credentials));
|
||||
|
||||
return new Client(
|
||||
[
|
||||
'handler' => $stack,
|
||||
'base_uri' => $credentials->getAuthUrl(),
|
||||
'auth' => 'oauth',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function createOauth1(CredentialsInterface $credentials): Oauth1
|
||||
{
|
||||
$config = [
|
||||
'consumer_key' => $credentials->getConsumerKey(),
|
||||
'consumer_secret' => $credentials->getConsumerSecret(),
|
||||
];
|
||||
|
||||
if ($credentials->getToken() && $credentials->getTokenSecret()) {
|
||||
$config['token'] = $credentials->getToken();
|
||||
$config['token_secret'] = $credentials->getTokenSecret();
|
||||
}
|
||||
|
||||
return new Oauth1($config);
|
||||
}
|
||||
|
||||
private function credentialsAreConfigured(CredentialsInterface $credentials): bool
|
||||
{
|
||||
return !empty($credentials->getAuthUrl()) && !empty($credentials->getConsumerKey()) && !empty($credentials->getConsumerSecret());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
|
||||
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
|
||||
interface AccessTokenInterface extends AuthCredentialsInterface
|
||||
{
|
||||
public function getAccessToken(): ?string;
|
||||
|
||||
public function getAccessTokenExpiry(): ?\DateTimeImmutable;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
|
||||
|
||||
interface CodeInterface
|
||||
{
|
||||
public function getCode(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
|
||||
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
|
||||
interface CredentialsInterface extends AuthCredentialsInterface
|
||||
{
|
||||
public function getAuthorizationUrl(): string;
|
||||
|
||||
public function getTokenUrl(): string;
|
||||
|
||||
public function getClientId(): ?string;
|
||||
|
||||
public function getClientSecret(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
|
||||
|
||||
interface RedirectUriInterface
|
||||
{
|
||||
public function getRedirectUri(): string;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
|
||||
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
|
||||
interface RefreshTokenInterface extends AuthCredentialsInterface
|
||||
{
|
||||
public function getRefreshToken(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
|
||||
|
||||
interface ScopeInterface
|
||||
{
|
||||
public function getScope(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials;
|
||||
|
||||
interface StateInterface
|
||||
{
|
||||
public function getState(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use kamermans\OAuth2\GrantType\AuthorizationCode;
|
||||
use kamermans\OAuth2\GrantType\RefreshToken;
|
||||
use kamermans\OAuth2\OAuth2Middleware;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\CodeInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\CredentialsInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\RedirectUriInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\ScopeInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigCredentialsSignerInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenFactoryInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenPersistenceInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenSignerInterface;
|
||||
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
|
||||
|
||||
/**
|
||||
* Factory for building HTTP clients that will sign the requests with Oauth2 headers.
|
||||
* Based on Guzzle OAuth 2.0 Subscriber - kamermans/guzzle-oauth2-subscriber package.
|
||||
*
|
||||
* @see https://github.com/kamermans/guzzle-oauth2-subscriber
|
||||
*/
|
||||
class HttpFactory implements AuthProviderInterface
|
||||
{
|
||||
public const NAME = 'oauth2_three_legged';
|
||||
|
||||
private ?AuthCredentialsInterface $credentials = null;
|
||||
|
||||
private ConfigCredentialsSignerInterface|ConfigTokenPersistenceInterface|ConfigTokenSignerInterface|AuthConfigInterface|null $config = null;
|
||||
|
||||
private ?Client $reAuthClient = null;
|
||||
|
||||
/**
|
||||
* Cache of initialized clients.
|
||||
*
|
||||
* @var Client[]
|
||||
*/
|
||||
private array $initializedClients = [];
|
||||
|
||||
public function getAuthType(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConfigCredentialsSignerInterface|ConfigTokenPersistenceInterface|ConfigTokenSignerInterface|AuthConfigInterface $config
|
||||
*
|
||||
* @throws PluginNotConfiguredException
|
||||
*/
|
||||
public function getClient(AuthCredentialsInterface $credentials, ?AuthConfigInterface $config = null): ClientInterface
|
||||
{
|
||||
if (!$this->credentialsAreConfigured($credentials)) {
|
||||
throw new PluginNotConfiguredException('Missing credentials');
|
||||
}
|
||||
|
||||
// Return cached initialized client if there is one.
|
||||
if (isset($this->initializedClients[$credentials->getClientId()])) {
|
||||
return $this->initializedClients[$credentials->getClientId()];
|
||||
}
|
||||
|
||||
$this->credentials = $credentials;
|
||||
$this->config = $config;
|
||||
|
||||
$options = [
|
||||
'handler' => $this->getStackHandler(),
|
||||
'auth' => 'oauth',
|
||||
];
|
||||
|
||||
// Set up base URI if it's configured.
|
||||
if (method_exists($credentials, 'getBaseUri') && ($baseUri = $credentials->getBaseUri()) !== null) {
|
||||
$options['base_uri'] = $baseUri;
|
||||
}
|
||||
|
||||
$this->initializedClients[$credentials->getClientId()] = new Client($options);
|
||||
|
||||
return $this->initializedClients[$credentials->getClientId()];
|
||||
}
|
||||
|
||||
protected function credentialsAreConfigured(CredentialsInterface $credentials): bool
|
||||
{
|
||||
if (empty($credentials->getAuthorizationUrl())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($credentials->getTokenUrl())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($credentials->getClientId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($credentials->getClientSecret())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getStackHandler(): HandlerStack
|
||||
{
|
||||
$reAuthConfig = $this->getReAuthConfig();
|
||||
$grantType = new AuthorizationCode($this->getReAuthClient(), $reAuthConfig);
|
||||
$refreshTokenGrantType = new RefreshToken($this->getReAuthClient(), $reAuthConfig);
|
||||
$middleware = new OAuth2Middleware($grantType, $refreshTokenGrantType);
|
||||
|
||||
$this->configureMiddleware($middleware);
|
||||
|
||||
$stack = HandlerStack::create();
|
||||
$stack->push($middleware);
|
||||
|
||||
return $stack;
|
||||
}
|
||||
|
||||
private function getReAuthClient(): ClientInterface
|
||||
{
|
||||
if ($this->reAuthClient) {
|
||||
return $this->reAuthClient;
|
||||
}
|
||||
|
||||
$this->reAuthClient = new Client([
|
||||
'base_uri' => $this->credentials->getTokenUrl(),
|
||||
]);
|
||||
|
||||
return $this->reAuthClient;
|
||||
}
|
||||
|
||||
private function getReAuthConfig(): array
|
||||
{
|
||||
$config = [
|
||||
'client_id' => $this->credentials->getClientId(),
|
||||
'client_secret' => $this->credentials->getClientSecret(),
|
||||
'code' => '',
|
||||
];
|
||||
|
||||
if ($this->credentials instanceof ScopeInterface) {
|
||||
$config['scope'] = $this->credentials->getScope();
|
||||
}
|
||||
|
||||
if ($this->credentials instanceof RedirectUriInterface) {
|
||||
$config['redirect_uri'] = $this->credentials->getRedirectUri();
|
||||
}
|
||||
|
||||
if ($this->credentials instanceof CodeInterface) {
|
||||
$config['code'] = $this->credentials->getCode();
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function configureMiddleware(OAuth2Middleware $oauth): void
|
||||
{
|
||||
if (!$this->config) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->config instanceof ConfigCredentialsSignerInterface) {
|
||||
$oauth->setClientCredentialsSigner($this->config->getCredentialsSigner());
|
||||
}
|
||||
|
||||
if ($this->config instanceof ConfigTokenPersistenceInterface) {
|
||||
$oauth->setTokenPersistence($this->config->getTokenPersistence());
|
||||
}
|
||||
|
||||
if ($this->config instanceof ConfigTokenSignerInterface) {
|
||||
$oauth->setAccessTokenSigner($this->config->getTokenSigner());
|
||||
}
|
||||
|
||||
if ($this->config instanceof ConfigTokenFactoryInterface) {
|
||||
$oauth->setTokenFactory($this->config->getTokenFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials;
|
||||
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
|
||||
interface ClientCredentialsGrantInterface extends AuthCredentialsInterface
|
||||
{
|
||||
public function getAuthorizationUrl(): string;
|
||||
|
||||
public function getClientId(): ?string;
|
||||
|
||||
public function getClientSecret(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials;
|
||||
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
|
||||
interface PasswordCredentialsGrantInterface extends AuthCredentialsInterface
|
||||
{
|
||||
public function getAuthorizationUrl(): string;
|
||||
|
||||
public function getClientId(): ?string;
|
||||
|
||||
public function getClientSecret(): ?string;
|
||||
|
||||
public function getUsername(): ?string;
|
||||
|
||||
public function getPassword(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials;
|
||||
|
||||
interface ScopeInterface
|
||||
{
|
||||
public function getScope(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials;
|
||||
|
||||
interface StateInterface
|
||||
{
|
||||
public function getState(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use kamermans\OAuth2\GrantType\ClientCredentials;
|
||||
use kamermans\OAuth2\GrantType\GrantTypeInterface;
|
||||
use kamermans\OAuth2\GrantType\PasswordCredentials;
|
||||
use kamermans\OAuth2\GrantType\RefreshToken;
|
||||
use kamermans\OAuth2\OAuth2Middleware;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials\ClientCredentialsGrantInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials\PasswordCredentialsGrantInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials\ScopeInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials\StateInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigCredentialsSignerInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenFactoryInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenPersistenceInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenSignerInterface;
|
||||
use Mautic\IntegrationsBundle\Exception\InvalidCredentialsException;
|
||||
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
|
||||
|
||||
/**
|
||||
* Factory for building HTTP clients that will sign the requests with Oauth2 headers.
|
||||
* Based on Guzzle OAuth 2.0 Subscriber - kamermans/guzzle-oauth2-subscriber package.
|
||||
*
|
||||
* @see https://github.com/kamermans/guzzle-oauth2-subscriber
|
||||
*/
|
||||
class HttpFactory implements AuthProviderInterface
|
||||
{
|
||||
public const NAME = 'oauth2_two_legged';
|
||||
|
||||
private PasswordCredentialsGrantInterface|ClientCredentialsGrantInterface|null $credentials = null;
|
||||
|
||||
private ConfigCredentialsSignerInterface|ConfigTokenPersistenceInterface|ConfigTokenSignerInterface|AuthConfigInterface|ConfigTokenFactoryInterface|null $config = null;
|
||||
|
||||
private ?Client $reAuthClient = null;
|
||||
|
||||
/**
|
||||
* Cache of initialized clients.
|
||||
*
|
||||
* @var Client[]
|
||||
*/
|
||||
private array $initializedClients = [];
|
||||
|
||||
public function getAuthType(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PasswordCredentialsGrantInterface|ClientCredentialsGrantInterface $credentials
|
||||
* @param ConfigCredentialsSignerInterface|ConfigTokenPersistenceInterface|ConfigTokenSignerInterface|AuthConfigInterface|ConfigTokenFactoryInterface $config
|
||||
*
|
||||
* @throws PluginNotConfiguredException
|
||||
* @throws InvalidCredentialsException
|
||||
*/
|
||||
public function getClient(AuthCredentialsInterface $credentials, ?AuthConfigInterface $config = null): ClientInterface
|
||||
{
|
||||
if (!$this->credentialsAreValid($credentials)) {
|
||||
throw new InvalidCredentialsException(sprintf('Credentials must implement either the %s or %s interfaces', PasswordCredentialsGrantInterface::class, ClientCredentialsGrantInterface::class));
|
||||
}
|
||||
|
||||
if (!$this->credentialsAreConfigured($credentials)) {
|
||||
throw new PluginNotConfiguredException('Authorization URL, client ID or client secret is missing');
|
||||
}
|
||||
|
||||
// Return cached initialized client if there is one.
|
||||
if (!empty($this->initializedClients[$credentials->getClientId()])) {
|
||||
return $this->initializedClients[$credentials->getClientId()];
|
||||
}
|
||||
|
||||
$this->credentials = $credentials;
|
||||
$this->config = $config;
|
||||
|
||||
$this->initializedClients[$credentials->getClientId()] = new Client(
|
||||
[
|
||||
'handler' => $this->getStackHandler(),
|
||||
'auth' => 'oauth',
|
||||
]
|
||||
);
|
||||
|
||||
return $this->initializedClients[$credentials->getClientId()];
|
||||
}
|
||||
|
||||
private function credentialsAreValid(AuthCredentialsInterface $credentials): bool
|
||||
{
|
||||
return $credentials instanceof PasswordCredentialsGrantInterface || $credentials instanceof ClientCredentialsGrantInterface;
|
||||
}
|
||||
|
||||
private function credentialsAreConfigured(AuthCredentialsInterface $credentials): bool
|
||||
{
|
||||
if (empty($credentials->getAuthorizationUrl()) || empty($credentials->getClientId()) || empty($credentials->getClientSecret())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($credentials instanceof PasswordCredentialsGrantInterface && (empty($credentials->getUsername()) || empty($credentials->getPassword()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getStackHandler(): HandlerStack
|
||||
{
|
||||
$reAuthConfig = $this->getReAuthConfig();
|
||||
$accessTokenGrantType = $this->getGrantType($reAuthConfig);
|
||||
$refreshTokenGrantType = new RefreshToken($this->getReAuthClient(), $reAuthConfig);
|
||||
$middleware = new OAuth2Middleware($accessTokenGrantType, $refreshTokenGrantType);
|
||||
|
||||
$this->configureMiddleware($middleware);
|
||||
|
||||
$stack = HandlerStack::create();
|
||||
$stack->push($middleware);
|
||||
|
||||
return $stack;
|
||||
}
|
||||
|
||||
private function getReAuthClient(): ClientInterface
|
||||
{
|
||||
if ($this->reAuthClient) {
|
||||
return $this->reAuthClient;
|
||||
}
|
||||
|
||||
$this->reAuthClient = new Client(
|
||||
[
|
||||
'base_uri' => $this->credentials->getAuthorizationUrl(),
|
||||
]
|
||||
);
|
||||
|
||||
return $this->reAuthClient;
|
||||
}
|
||||
|
||||
private function getReAuthConfig(): array
|
||||
{
|
||||
$config = [
|
||||
'client_id' => $this->credentials->getClientId(),
|
||||
'client_secret' => $this->credentials->getClientSecret(),
|
||||
];
|
||||
|
||||
if ($this->credentials instanceof ScopeInterface) {
|
||||
$config['scope'] = $this->credentials->getScope();
|
||||
}
|
||||
|
||||
if ($this->credentials instanceof StateInterface) {
|
||||
$config['state'] = $this->credentials->getState();
|
||||
}
|
||||
|
||||
if ($this->credentials instanceof ClientCredentialsGrantInterface) {
|
||||
return $config;
|
||||
}
|
||||
|
||||
$config['username'] = $this->credentials->getUsername();
|
||||
$config['password'] = $this->credentials->getPassword();
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function getGrantType(array $config): GrantTypeInterface
|
||||
{
|
||||
if ($this->credentials instanceof ClientCredentialsGrantInterface) {
|
||||
return new ClientCredentials($this->getReAuthClient(), $config);
|
||||
}
|
||||
|
||||
return new PasswordCredentials($this->getReAuthClient(), $config);
|
||||
}
|
||||
|
||||
private function configureMiddleware(OAuth2Middleware $oauth): void
|
||||
{
|
||||
if (!$this->config) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->config instanceof ConfigCredentialsSignerInterface) {
|
||||
$oauth->setClientCredentialsSigner($this->config->getCredentialsSigner());
|
||||
}
|
||||
|
||||
if ($this->config instanceof ConfigTokenPersistenceInterface) {
|
||||
$oauth->setTokenPersistence($this->config->getTokenPersistence());
|
||||
}
|
||||
|
||||
if ($this->config instanceof ConfigTokenSignerInterface) {
|
||||
$oauth->setAccessTokenSigner($this->config->getTokenSigner());
|
||||
}
|
||||
|
||||
if ($this->config instanceof ConfigTokenFactoryInterface) {
|
||||
$oauth->setTokenFactory($this->config->getTokenFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess;
|
||||
|
||||
use kamermans\OAuth2\Signer\ClientCredentials\SignerInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
|
||||
|
||||
interface ConfigCredentialsSignerInterface extends AuthConfigInterface
|
||||
{
|
||||
public function getCredentialsSigner(): SignerInterface;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess;
|
||||
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\TokenFactoryInterface;
|
||||
|
||||
interface ConfigTokenFactoryInterface extends AuthConfigInterface
|
||||
{
|
||||
public function getTokenFactory(): TokenFactoryInterface;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess;
|
||||
|
||||
use kamermans\OAuth2\Persistence\TokenPersistenceInterface as KamermansTokenPersistenceInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
|
||||
|
||||
interface ConfigTokenPersistenceInterface extends AuthConfigInterface
|
||||
{
|
||||
public function getTokenPersistence(): KamermansTokenPersistenceInterface;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess;
|
||||
|
||||
use kamermans\OAuth2\Signer\AccessToken\SignerInterface;
|
||||
use Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface;
|
||||
|
||||
interface ConfigTokenSignerInterface extends AuthConfigInterface
|
||||
{
|
||||
public function getTokenSigner(): SignerInterface;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token;
|
||||
|
||||
use kamermans\OAuth2\Token\TokenInterface;
|
||||
use kamermans\OAuth2\Token\TokenSerializer;
|
||||
|
||||
class IntegrationToken implements TokenInterface
|
||||
{
|
||||
// Pull in serialize() and unserialize() methods
|
||||
use TokenSerializer;
|
||||
|
||||
/**
|
||||
* @param mixed[] $extraData
|
||||
*/
|
||||
public function __construct(
|
||||
?string $accessToken,
|
||||
?string $refreshToken,
|
||||
$expiresAt = null,
|
||||
private array $extraData = [],
|
||||
) {
|
||||
$this->accessToken = (string) $accessToken;
|
||||
$this->refreshToken = (string) $refreshToken;
|
||||
$this->expiresAt = (int) $expiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The access token
|
||||
*/
|
||||
public function getAccessToken(): string
|
||||
{
|
||||
return $this->accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The refresh token
|
||||
*/
|
||||
public function getRefreshToken(): string
|
||||
{
|
||||
return $this->refreshToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int The expiration timestamp
|
||||
*/
|
||||
public function getExpiresAt(): int
|
||||
{
|
||||
return $this->expiresAt;
|
||||
}
|
||||
|
||||
public function isExpired(): bool
|
||||
{
|
||||
// Consider expired if there is not an access token
|
||||
if (!$this->getAccessToken()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, consider expired if the expiration time has passed
|
||||
return $this->expiresAt && $this->expiresAt < time();
|
||||
}
|
||||
|
||||
public function getExtraData(): array
|
||||
{
|
||||
return $this->extraData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token;
|
||||
|
||||
use kamermans\OAuth2\Token\TokenInterface;
|
||||
|
||||
class IntegrationTokenFactory implements TokenFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @param mixed[] $extraKeysToStore Extra keys returned by the service during the token process that needs to be captured
|
||||
* @param int|null $defaultExpiresIn Default time in seconds that tokens are good for if not given in the response
|
||||
*/
|
||||
public function __construct(
|
||||
private array $extraKeysToStore = [],
|
||||
private ?int $defaultExpiresIn = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(array $data, ?TokenInterface $previousToken = null): IntegrationToken
|
||||
{
|
||||
$accessToken = null;
|
||||
$refreshToken = null;
|
||||
|
||||
// Read "access_token" attribute
|
||||
if (isset($data['access_token'])) {
|
||||
$accessToken = $data['access_token'];
|
||||
}
|
||||
|
||||
// Read "refresh_token" attribute
|
||||
if (isset($data['refresh_token'])) {
|
||||
$refreshToken = $data['refresh_token'];
|
||||
} elseif (null !== $previousToken) {
|
||||
// When requesting a new access token with a refresh token, the
|
||||
// server may not resend a new refresh token. In that case we
|
||||
// should keep the previous refresh token as valid.
|
||||
//
|
||||
// See http://tools.ietf.org/html/rfc6749#section-6
|
||||
$refreshToken = $previousToken->getRefreshToken();
|
||||
}
|
||||
|
||||
$expiresAt = $this->getExpiration($data);
|
||||
|
||||
return new IntegrationToken($accessToken, $refreshToken, $expiresAt, $this->getExtraData($data));
|
||||
}
|
||||
|
||||
private function getExtraData(array $data): array
|
||||
{
|
||||
$extraData = [];
|
||||
foreach ($this->extraKeysToStore as $key) {
|
||||
$extraData[$key] = $data[$key] ?? null;
|
||||
}
|
||||
|
||||
return $extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $data
|
||||
*/
|
||||
private function getExpiration(array $data): ?int
|
||||
{
|
||||
// Read the "expires_at" attribute
|
||||
if (isset($data['expires_at'])) {
|
||||
return (int) $data['expires_at'];
|
||||
}
|
||||
|
||||
// Read the "expires_in" attribute
|
||||
if (isset($data['expires_in'])) {
|
||||
return time() + (int) $data['expires_in'];
|
||||
}
|
||||
|
||||
// Facebook unfortunately breaks the spec by using 'expires' instead of 'expires_in'
|
||||
if (isset($data['expires'])) {
|
||||
return time() + (int) $data['expires'];
|
||||
}
|
||||
|
||||
// Fallback to the default if set
|
||||
if ($this->defaultExpiresIn) {
|
||||
return time() + $this->defaultExpiresIn;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token;
|
||||
|
||||
use kamermans\OAuth2\Token\TokenInterface;
|
||||
|
||||
interface TokenFactoryInterface
|
||||
{
|
||||
public function __invoke(array $data, ?TokenInterface $previousToken = null): IntegrationToken;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token;
|
||||
|
||||
use kamermans\OAuth2\Persistence\TokenPersistenceInterface;
|
||||
use kamermans\OAuth2\Token\TokenInterface;
|
||||
use Mautic\IntegrationsBundle\Exception\IntegrationNotSetException;
|
||||
use Mautic\IntegrationsBundle\Helper\IntegrationsHelper;
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
|
||||
class TokenPersistence implements TokenPersistenceInterface
|
||||
{
|
||||
private ?Integration $integration = null;
|
||||
|
||||
public function __construct(
|
||||
private IntegrationsHelper $integrationsHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the token data into the give token.
|
||||
*
|
||||
* @return TokenInterface|IntegrationToken Restored token
|
||||
*/
|
||||
public function restoreToken(TokenInterface $token): TokenInterface
|
||||
{
|
||||
$apiKeys = $this->getIntegration()->getApiKeys();
|
||||
$apiKeys['expires_at'] ??= null;
|
||||
|
||||
return new IntegrationToken(
|
||||
empty($apiKeys['access_token']) ? null : $apiKeys['access_token'],
|
||||
empty($apiKeys['refresh_token']) ? null : $apiKeys['refresh_token'],
|
||||
$apiKeys['expires_at'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the token data.
|
||||
*/
|
||||
public function saveToken(TokenInterface $token): void
|
||||
{
|
||||
$integration = $this->getIntegration();
|
||||
$oldApiKeys = $integration->getApiKeys();
|
||||
|
||||
if (null === $oldApiKeys) {
|
||||
$oldApiKeys = [];
|
||||
}
|
||||
|
||||
$newApiKeys = [
|
||||
'access_token' => $token->getAccessToken(),
|
||||
'refresh_token' => $token->getRefreshToken(),
|
||||
'expires_at' => $token->getExpiresAt(),
|
||||
];
|
||||
|
||||
$extraData = $token instanceof IntegrationToken ? $token->getExtraData() : [];
|
||||
$newApiKeys = array_merge($oldApiKeys, $extraData, $newApiKeys);
|
||||
|
||||
$integration->setApiKeys($newApiKeys);
|
||||
$this->integrationsHelper->saveIntegrationConfiguration($integration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the saved token data.
|
||||
*/
|
||||
public function deleteToken(): void
|
||||
{
|
||||
$integration = $this->getIntegration();
|
||||
|
||||
$apiKeys = $integration->getApiKeys();
|
||||
|
||||
// Must delete both the access token and the expiration in order for the middleware to refresh
|
||||
unset($apiKeys['access_token'], $apiKeys['expires_at']);
|
||||
|
||||
$integration->setApiKeys($apiKeys);
|
||||
|
||||
$this->integrationsHelper->saveIntegrationConfiguration($integration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a token exists (although it may not be valid).
|
||||
*/
|
||||
public function hasToken(): bool
|
||||
{
|
||||
return !empty($this->getIntegration()->getApiKeys()['access_token']);
|
||||
}
|
||||
|
||||
public function setIntegration(Integration $integration): void
|
||||
{
|
||||
$this->integration = $integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IntegrationNotSetException
|
||||
*/
|
||||
private function getIntegration(): Integration
|
||||
{
|
||||
if ($this->integration) {
|
||||
return $this->integration;
|
||||
}
|
||||
|
||||
throw new IntegrationNotSetException('Integration not set');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token;
|
||||
|
||||
use kamermans\OAuth2\Token\RawToken;
|
||||
use Mautic\IntegrationsBundle\Helper\IntegrationsHelper;
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
|
||||
class TokenPersistenceFactory
|
||||
{
|
||||
public function __construct(
|
||||
private IntegrationsHelper $integrationsHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
public function create(Integration $integration): TokenPersistence
|
||||
{
|
||||
$tokenPersistence = new TokenPersistence($this->integrationsHelper);
|
||||
|
||||
$tokenPersistence->setIntegration($integration);
|
||||
|
||||
$apiKeys = $integration->getApiKeys();
|
||||
|
||||
$token = new RawToken(
|
||||
$apiKeys['access_token'] ?? null,
|
||||
$apiKeys['refresh_token'] ?? null,
|
||||
$apiKeys['expires_at'] ?? null
|
||||
);
|
||||
|
||||
$tokenPersistence->restoreToken($token);
|
||||
|
||||
return $tokenPersistence;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Bundle;
|
||||
|
||||
use Mautic\PluginBundle\Bundle\PluginBundleBase;
|
||||
|
||||
/**
|
||||
* Base Bundle class which should be extended by addon bundles.
|
||||
*/
|
||||
abstract class AbstractPluginBundle extends PluginBundleBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Command;
|
||||
|
||||
use Mautic\IntegrationsBundle\Entity\FieldChangeRepository;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: CleanupCommand::NAME,
|
||||
description: 'Delete records from field changes which are invalid'
|
||||
)]
|
||||
class CleanupCommand extends Command
|
||||
{
|
||||
public const NAME = 'mautic:integrations:cleanup';
|
||||
|
||||
public function __construct(private FieldChangeRepository $fieldChangeRepository)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$numberOfRecordsDeleted = $this->fieldChangeRepository->deleteOrphanLeadChanges();
|
||||
$io->success("$numberOfRecordsDeleted records deleted.");
|
||||
$io->success('Execution time: '.number_format(microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'], 3));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Command;
|
||||
|
||||
use Mautic\IntegrationsBundle\Exception\InvalidValueException;
|
||||
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InputOptionsDAO;
|
||||
use Mautic\IntegrationsBundle\Sync\SyncService\SyncServiceInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: SyncCommand::NAME,
|
||||
description: 'Fetch objects from integration.'
|
||||
)]
|
||||
class SyncCommand extends Command
|
||||
{
|
||||
public const NAME = 'mautic:integrations:sync';
|
||||
|
||||
public function __construct(
|
||||
private SyncServiceInterface $syncService,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addArgument(
|
||||
'integration',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Fetch objects from integration.',
|
||||
null
|
||||
)
|
||||
->addOption(
|
||||
'--start-datetime',
|
||||
'-t',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Set start date/time for updated values in UTC timezone.'
|
||||
)
|
||||
->addOption(
|
||||
'--end-datetime',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Set start date/time for updated values in UTC timezone.'
|
||||
)
|
||||
->addOption(
|
||||
'--mautic-object-id',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
|
||||
'Provide specific Mautic object IDs you want to sync. If some object IDs are provided then the start/end dates have no effect. Example: --mautic-object-id=contact:12 --mautic-object-id=company:13'
|
||||
)
|
||||
->addOption(
|
||||
'--integration-object-id',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
|
||||
'Provide specific integration object IDs you want to sync. If some object IDs are provided then the start/end dates have no effect. It depends on each integration if this is supported. Example: --integration-object-id=Account:12 --integration-object-id=Lead:13'
|
||||
)
|
||||
->addOption(
|
||||
'--first-time-sync',
|
||||
'-f',
|
||||
InputOption::VALUE_NONE,
|
||||
'Notate if this is a first time sync where Mautic will sync existing objects instead of just tracked changes'
|
||||
)
|
||||
->addOption(
|
||||
'--disable-push',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Notate if the sync should execute only pushing items from Mautic to the integration'
|
||||
)
|
||||
->addOption(
|
||||
'--disable-pull',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Notate if the sync should execute only pulling items from integration to the Mautic'
|
||||
)
|
||||
->addOption(
|
||||
'--option',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
|
||||
'Provide option pass to InputOptions Example: --option="type:1" --option="channel_id:1"'
|
||||
)
|
||||
->addOption(
|
||||
'--disable-activity-push',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Notate if the sync should disable the activities sync if the integration supports it'
|
||||
);
|
||||
|
||||
parent::configure();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
try {
|
||||
$inputOptions = new InputOptionsDAO(array_merge($input->getArguments(), $input->getOptions()));
|
||||
} catch (InvalidValueException $e) {
|
||||
$io->error($e->getMessage());
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
try {
|
||||
defined('MAUTIC_INTEGRATION_SYNC_IN_PROGRESS') or define('MAUTIC_INTEGRATION_SYNC_IN_PROGRESS', $inputOptions->getIntegration());
|
||||
|
||||
// Tell audit log to use integration name rather than "System"
|
||||
defined('MAUTIC_AUDITLOG_USER') or define('MAUTIC_AUDITLOG_USER', $inputOptions->getIntegration());
|
||||
|
||||
$this->syncService->processIntegrationSync($inputOptions);
|
||||
} catch (\Throwable $e) {
|
||||
if ('dev' === $input->getOption('env') || (defined('MAUTIC_ENV') && MAUTIC_ENV === 'dev')) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$io->error($e->getMessage());
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->success('Execution time: '.number_format(microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'], 3));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'name' => 'Integrations',
|
||||
'description' => 'Adds support for plugin integrations',
|
||||
'author' => 'Mautic, Inc.',
|
||||
'routes' => [
|
||||
'main' => [
|
||||
'mautic_integration_config' => [
|
||||
'path' => '/integration/{integration}/config',
|
||||
'controller' => 'Mautic\IntegrationsBundle\Controller\ConfigController::editAction',
|
||||
],
|
||||
'mautic_integration_config_field_pagination' => [
|
||||
'path' => '/integration/{integration}/config/{object}/{page}',
|
||||
'controller' => 'Mautic\IntegrationsBundle\Controller\FieldPaginationController::paginateAction',
|
||||
'defaults' => [
|
||||
'page' => 1,
|
||||
],
|
||||
],
|
||||
'mautic_integration_config_field_update' => [
|
||||
'path' => '/integration/{integration}/config/{object}/field/{field}',
|
||||
'controller' => 'Mautic\IntegrationsBundle\Controller\UpdateFieldController::updateAction',
|
||||
],
|
||||
],
|
||||
'public' => [
|
||||
'mautic_integration_public_callback' => [
|
||||
'path' => '/integration/{integration}/callback',
|
||||
'controller' => 'Mautic\IntegrationsBundle\Controller\AuthController::callbackAction',
|
||||
],
|
||||
],
|
||||
],
|
||||
'services' => [
|
||||
'events' => [
|
||||
'mautic.integrations.subscriber.controller' => [
|
||||
'class' => Mautic\IntegrationsBundle\EventListener\ControllerSubscriber::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.helper',
|
||||
'controller_resolver',
|
||||
],
|
||||
],
|
||||
],
|
||||
'helpers' => [
|
||||
'mautic.integrations.helper.variable_expresser' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\VariableExpresser\VariableExpresserHelper::class,
|
||||
],
|
||||
'mautic.integrations.helper' => [
|
||||
'class' => Mautic\IntegrationsBundle\Helper\IntegrationsHelper::class,
|
||||
'arguments' => [
|
||||
'mautic.plugin.integrations.repository.integration',
|
||||
'mautic.integrations.service.encryption',
|
||||
'event_dispatcher',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.helper.auth_integrations' => [
|
||||
'class' => Mautic\IntegrationsBundle\Helper\AuthIntegrationsHelper::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.helper',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.helper.sync_integrations' => [
|
||||
'class' => Mautic\IntegrationsBundle\Helper\SyncIntegrationsHelper::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.helper',
|
||||
'mautic.integrations.internal.object_provider',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.helper.config_integrations' => [
|
||||
'class' => Mautic\IntegrationsBundle\Helper\ConfigIntegrationsHelper::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.helper',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.helper.builder_integrations' => [
|
||||
'class' => Mautic\IntegrationsBundle\Helper\BuilderIntegrationsHelper::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.helper',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.helper.field_validator' => [
|
||||
'class' => Mautic\IntegrationsBundle\Helper\FieldValidationHelper::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.sync.data_exchange.mautic.field_helper',
|
||||
'translator',
|
||||
],
|
||||
],
|
||||
],
|
||||
'other' => [
|
||||
'mautic.integrations.service.encryption' => [
|
||||
'class' => Mautic\IntegrationsBundle\Facade\EncryptionService::class,
|
||||
'arguments' => [
|
||||
'mautic.helper.encryption',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.internal.object_provider' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectProvider::class,
|
||||
'arguments' => [
|
||||
'event_dispatcher',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.notification.helper.owner_provider' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\Helper\OwnerProvider::class,
|
||||
'arguments' => [
|
||||
'event_dispatcher',
|
||||
'mautic.integrations.internal.object_provider',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.auth_provider.api_key' => [
|
||||
'class' => Mautic\IntegrationsBundle\Auth\Provider\ApiKey\HttpFactory::class,
|
||||
],
|
||||
'mautic.integrations.auth_provider.basic_auth' => [
|
||||
'class' => Mautic\IntegrationsBundle\Auth\Provider\BasicAuth\HttpFactory::class,
|
||||
],
|
||||
'mautic.integrations.auth_provider.oauth1atwolegged' => [
|
||||
'class' => Mautic\IntegrationsBundle\Auth\Provider\Oauth1aTwoLegged\HttpFactory::class,
|
||||
],
|
||||
'mautic.integrations.auth_provider.oauth2twolegged' => [
|
||||
'class' => Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\HttpFactory::class,
|
||||
],
|
||||
'mautic.integrations.auth_provider.oauth2threelegged' => [
|
||||
'class' => Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\HttpFactory::class,
|
||||
],
|
||||
'mautic.integrations.auth_provider.token_persistence_factory' => [
|
||||
'class' => Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\TokenPersistenceFactory::class,
|
||||
'arguments' => ['mautic.integrations.helper'],
|
||||
],
|
||||
'mautic.integrations.token.parser' => [
|
||||
'class' => Mautic\IntegrationsBundle\Helper\TokenParser::class,
|
||||
],
|
||||
],
|
||||
'sync' => [
|
||||
'mautic.sync.logger' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Logger\DebugLogger::class,
|
||||
'arguments' => [
|
||||
'monolog.logger.mautic',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.helper.sync_judge' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncJudge\SyncJudge::class,
|
||||
],
|
||||
'mautic.integrations.sync.data_exchange.mautic.order_executioner' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Executioner\OrderExecutioner::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.helper.sync_mapping',
|
||||
'event_dispatcher',
|
||||
'mautic.integrations.internal.object_provider',
|
||||
'mautic.integrations.internal.reference_resolver',
|
||||
'mautic.integrations.internal.field_validator',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.internal.field_validator' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Executioner\FieldValidator::class,
|
||||
'arguments' => [
|
||||
'mautic.lead.repository.field',
|
||||
'mautic.integrations.sync.notification.bulk_notification',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.internal.reference_resolver' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Executioner\ReferenceResolver::class,
|
||||
'arguments' => [
|
||||
'doctrine.dbal.default_connection',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.sync_process.value_helper' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Helper\ValueHelper::class,
|
||||
'arguments' => [],
|
||||
],
|
||||
'mautic.integrations.sync.data_exchange.mautic.field_builder' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ReportBuilder\FieldBuilder::class,
|
||||
'arguments' => [
|
||||
'router',
|
||||
'mautic.integrations.sync.data_exchange.mautic.field_helper',
|
||||
'mautic.integrations.helper.contact_object',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.data_exchange.mautic.full_object_report_builder' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ReportBuilder\FullObjectReportBuilder::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.sync.data_exchange.mautic.field_builder',
|
||||
'mautic.integrations.internal.object_provider',
|
||||
'event_dispatcher',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.data_exchange.mautic.partial_object_report_builder' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ReportBuilder\PartialObjectReportBuilder::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.repository.field_change',
|
||||
'mautic.integrations.sync.data_exchange.mautic.field_helper',
|
||||
'mautic.integrations.sync.data_exchange.mautic.field_builder',
|
||||
'mautic.integrations.internal.object_provider',
|
||||
'event_dispatcher',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.data_exchange.mautic' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.repository.field_change',
|
||||
'mautic.integrations.sync.data_exchange.mautic.field_helper',
|
||||
'mautic.integrations.helper.sync_mapping',
|
||||
'mautic.integrations.sync.data_exchange.mautic.full_object_report_builder',
|
||||
'mautic.integrations.sync.data_exchange.mautic.partial_object_report_builder',
|
||||
'mautic.integrations.sync.data_exchange.mautic.order_executioner',
|
||||
'mautic.integrations.helper.sync_date',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.integration_process.object_change_generator' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Integration\ObjectChangeGenerator::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.sync.sync_process.value_helper',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.integration_process' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Integration\IntegrationSyncProcess::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.helper.sync_date',
|
||||
'mautic.integrations.helper.sync_mapping',
|
||||
'mautic.integrations.sync.integration_process.object_change_generator',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.internal_process.object_change_generator' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Internal\ObjectChangeGenerator::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.helper.sync_judge',
|
||||
'mautic.integrations.sync.sync_process.value_helper',
|
||||
'mautic.integrations.sync.data_exchange.mautic.field_helper',
|
||||
'mautic.integrations.sync.notification.bulk_notification',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.internal_process' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Internal\MauticSyncProcess::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.helper.sync_date',
|
||||
'mautic.integrations.sync.internal_process.object_change_generator',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.service' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\SyncService\SyncService::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.sync.data_exchange.mautic',
|
||||
'mautic.integrations.helper.sync_date',
|
||||
'mautic.integrations.helper.sync_mapping',
|
||||
'mautic.integrations.sync.helper.relations',
|
||||
'mautic.integrations.helper.sync_integrations',
|
||||
'event_dispatcher',
|
||||
'mautic.integrations.sync.notifier',
|
||||
'mautic.integrations.sync.integration_process',
|
||||
'mautic.integrations.sync.internal_process',
|
||||
],
|
||||
'methodCalls' => [
|
||||
'initiateDebugLogger' => ['mautic.sync.logger'],
|
||||
],
|
||||
],
|
||||
'mautic.integrations.helper.sync_date' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Helper\SyncDateHelper::class,
|
||||
'arguments' => [
|
||||
'doctrine.dbal.default_connection',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.helper.relations' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Helper\RelationsHelper::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.helper.sync_mapping',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.notifier' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\Notifier::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.sync.notification.handler_container',
|
||||
'mautic.integrations.helper.sync_integrations',
|
||||
'mautic.integrations.helper.config_integrations',
|
||||
'translator',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.notification.writer' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\Writer::class,
|
||||
'arguments' => [
|
||||
'mautic.core.model.notification',
|
||||
'mautic.core.model.auditlog',
|
||||
'doctrine.orm.entity_manager',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.notification.handler_container' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\Handler\HandlerContainer::class,
|
||||
],
|
||||
'mautic.integrations.sync.notification.handler_company' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\Handler\CompanyNotificationHandler::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.sync.notification.writer',
|
||||
'mautic.integrations.sync.notification.helper_user_notification',
|
||||
'mautic.integrations.sync.notification.helper_company',
|
||||
],
|
||||
'tag' => 'mautic.sync.notification_handler',
|
||||
],
|
||||
'mautic.integrations.sync.notification.handler_contact' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\Handler\ContactNotificationHandler::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.sync.notification.writer',
|
||||
'mautic.lead.repository.lead_event_log',
|
||||
'doctrine.orm.entity_manager',
|
||||
'mautic.integrations.sync.notification.helper_user_summary_notification',
|
||||
],
|
||||
'tag' => 'mautic.sync.notification_handler',
|
||||
],
|
||||
'mautic.integrations.sync.notification.helper_company' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\Helper\CompanyHelper::class,
|
||||
'arguments' => [
|
||||
'doctrine.dbal.default_connection',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.notification.helper_user' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\Helper\UserHelper::class,
|
||||
'arguments' => [
|
||||
'doctrine.dbal.default_connection',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.notification.helper_route' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\Helper\RouteHelper::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.internal.object_provider',
|
||||
'event_dispatcher',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.notification.helper_user_notification' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\Helper\UserNotificationHelper::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.sync.notification.writer',
|
||||
'mautic.integrations.sync.notification.user_notification_builder',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.notification.user_notification_builder' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\Helper\UserNotificationBuilder::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.sync.notification.helper_user',
|
||||
'mautic.integrations.sync.notification.helper.owner_provider',
|
||||
'mautic.integrations.sync.notification.helper_route',
|
||||
'translator',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.notification.bulk_notification' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\BulkNotification::class,
|
||||
'arguments' => [
|
||||
'mautic.core.service.bulk_notification',
|
||||
'mautic.integrations.sync.notification.user_notification_builder',
|
||||
'doctrine.orm.entity_manager',
|
||||
],
|
||||
],
|
||||
'mautic.integrations.sync.notification.helper_user_summary_notification' => [
|
||||
'class' => Mautic\IntegrationsBundle\Sync\Notification\Helper\UserSummaryNotificationHelper::class,
|
||||
'arguments' => [
|
||||
'mautic.integrations.sync.notification.writer',
|
||||
'mautic.integrations.sync.notification.helper_user',
|
||||
'mautic.integrations.sync.notification.helper.owner_provider',
|
||||
'mautic.integrations.sync.notification.helper_route',
|
||||
'translator',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Mautic\CoreBundle\DependencyInjection\MauticCoreExtension;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return function (ContainerConfigurator $configurator): void {
|
||||
$services = $configurator->services()
|
||||
->defaults()
|
||||
->autowire()
|
||||
->autoconfigure()
|
||||
->public();
|
||||
|
||||
$excludes = [
|
||||
'Helper/FieldFilterHelper.php',
|
||||
'Helper/FieldMergerHelper.php',
|
||||
'Auth/Support/Oauth2/Token',
|
||||
'Sync/DAO',
|
||||
'Sync/Exception',
|
||||
'Sync/SyncDataExchange/Internal/Executioner/Exception',
|
||||
'Sync/SyncProcess/SyncProcess.php',
|
||||
'Integration/IntegrationObject.php',
|
||||
];
|
||||
|
||||
$services->load('Mautic\\IntegrationsBundle\\', '../')
|
||||
->exclude('../{'.implode(',', array_merge(MauticCoreExtension::DEFAULT_EXCLUDES, $excludes)).'}');
|
||||
|
||||
$services->load('Mautic\\IntegrationsBundle\\Entity\\', '../Entity/*Repository.php')
|
||||
->tag(Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass::REPOSITORY_SERVICE_TAG);
|
||||
|
||||
$services->alias('mautic.integrations.repository.field_change', Mautic\IntegrationsBundle\Entity\FieldChangeRepository::class);
|
||||
$services->alias('mautic.integrations.repository.object_mapping', Mautic\IntegrationsBundle\Entity\ObjectMappingRepository::class);
|
||||
$services->alias('mautic.plugin.integrations.repository.integration', Mautic\PluginBundle\Entity\IntegrationRepository::class);
|
||||
$services->alias('mautic.integrations.helper.contact_object', Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectHelper\ContactObjectHelper::class);
|
||||
$services->alias('mautic.integrations.helper.company_object', Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectHelper\CompanyObjectHelper::class);
|
||||
$services->alias('mautic.integrations.sync.data_exchange.mautic.field_helper', Mautic\IntegrationsBundle\Sync\SyncDataExchange\Helper\FieldHelper::class);
|
||||
$services->alias('mautic.integrations.helper.sync_mapping', Mautic\IntegrationsBundle\Sync\Helper\MappingHelper::class);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Controller\CommonController;
|
||||
use Mautic\IntegrationsBundle\Exception\IntegrationNotFoundException;
|
||||
use Mautic\IntegrationsBundle\Exception\UnauthorizedException;
|
||||
use Mautic\IntegrationsBundle\Helper\AuthIntegrationsHelper;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class AuthController extends CommonController
|
||||
{
|
||||
public function callbackAction(AuthIntegrationsHelper $authIntegrationsHelper, string $integration, Request $request)
|
||||
{
|
||||
$authenticationError = false;
|
||||
|
||||
try {
|
||||
$authIntegration = $authIntegrationsHelper->getIntegration($integration);
|
||||
$message = $authIntegration->authenticateIntegration($request);
|
||||
} catch (UnauthorizedException $exception) {
|
||||
$message = $exception->getMessage();
|
||||
$authenticationError = true;
|
||||
} catch (IntegrationNotFoundException) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
return $this->render(
|
||||
'@Integrations/Auth/authenticated.html.twig',
|
||||
[
|
||||
'message' => $message,
|
||||
'authenticationError' => $authenticationError,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Controller\AbstractFormController;
|
||||
use Mautic\CoreBundle\Twig\Extension\FormExtension;
|
||||
use Mautic\IntegrationsBundle\Event\ConfigAuthUrlEvent;
|
||||
use Mautic\IntegrationsBundle\Event\ConfigSaveEvent;
|
||||
use Mautic\IntegrationsBundle\Event\FormLoadEvent;
|
||||
use Mautic\IntegrationsBundle\Event\KeysSaveEvent;
|
||||
use Mautic\IntegrationsBundle\Exception\IntegrationNotFoundException;
|
||||
use Mautic\IntegrationsBundle\Form\Type\IntegrationConfigType;
|
||||
use Mautic\IntegrationsBundle\Helper\ConfigIntegrationsHelper;
|
||||
use Mautic\IntegrationsBundle\Helper\FieldMergerHelper;
|
||||
use Mautic\IntegrationsBundle\Helper\FieldValidationHelper;
|
||||
use Mautic\IntegrationsBundle\Integration\BasicIntegration;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormAuthInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormAuthorizeButtonInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormCallbackInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeatureSettingsInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeaturesInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface;
|
||||
use Mautic\IntegrationsBundle\IntegrationEvents;
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
|
||||
class ConfigController extends AbstractFormController
|
||||
{
|
||||
/**
|
||||
* @var BasicIntegration|ConfigFormInterface
|
||||
*/
|
||||
private $integrationObject;
|
||||
|
||||
/**
|
||||
* @var Integration
|
||||
*/
|
||||
private $integrationConfiguration;
|
||||
|
||||
/**
|
||||
* @return array|JsonResponse|RedirectResponse|Response
|
||||
*/
|
||||
public function editAction(
|
||||
Request $request,
|
||||
ConfigIntegrationsHelper $integrationsHelper,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
FieldValidationHelper $fieldValidator,
|
||||
FormFactoryInterface $formFactory,
|
||||
FormExtension $formExtension,
|
||||
string $integration,
|
||||
) {
|
||||
// Check ACL
|
||||
if (!$this->security->isGranted('plugin:plugins:manage')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
try {
|
||||
$this->integrationObject = $integrationsHelper->getIntegration($integration);
|
||||
$this->integrationConfiguration = $this->integrationObject->getIntegrationConfiguration();
|
||||
} catch (IntegrationNotFoundException) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
$event = new FormLoadEvent($this->integrationConfiguration);
|
||||
$dispatcher->dispatch($event, IntegrationEvents::INTEGRATION_CONFIG_FORM_LOAD);
|
||||
|
||||
// Create the form
|
||||
$form = $this->getForm($formFactory);
|
||||
|
||||
if (Request::METHOD_POST === $request->getMethod()) {
|
||||
return $this->submitForm($request, $integrationsHelper, $fieldValidator, $dispatcher, $formFactory, $formExtension, $form);
|
||||
}
|
||||
|
||||
// Clear the session of previously stored fields in case it got stuck
|
||||
/** @var Session $session */
|
||||
$session = $request->getSession();
|
||||
$session->remove("$integration-fields");
|
||||
|
||||
return $this->showForm($request, $form, $formExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormInterface<mixed> $form
|
||||
*/
|
||||
private function submitForm(
|
||||
Request $request,
|
||||
ConfigIntegrationsHelper $integrationsHelper,
|
||||
FieldValidationHelper $fieldValidator,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
FormFactoryInterface $formFactory,
|
||||
FormExtension $formExtension,
|
||||
FormInterface $form,
|
||||
): JsonResponse|Response {
|
||||
if ($this->isFormCancelled($form)) {
|
||||
return $this->closeForm($request);
|
||||
}
|
||||
|
||||
// Get the fields before the form binds partial data due to pagination
|
||||
$settings = $this->integrationConfiguration->getFeatureSettings();
|
||||
$fieldMappings = $settings['sync']['fieldMappings'] ?? [];
|
||||
$oldApiKeys = $this->integrationConfiguration->getApiKeys();
|
||||
|
||||
// Submit the form
|
||||
$form->handleRequest($request);
|
||||
|
||||
$configEvent = new KeysSaveEvent($this->integrationConfiguration, $oldApiKeys);
|
||||
$this->dispatcher->dispatch($configEvent, IntegrationEvents::INTEGRATION_API_KEYS_BEFORE_SAVE);
|
||||
|
||||
if ($this->integrationObject instanceof ConfigFormSyncInterface) {
|
||||
$integration = $this->integrationObject->getName();
|
||||
$settings = $this->integrationConfiguration->getFeatureSettings();
|
||||
$session = $request->getSession();
|
||||
$updatedFields = $session->get("$integration-fields", []);
|
||||
|
||||
$fieldMerger = new FieldMergerHelper($this->integrationObject, $fieldMappings);
|
||||
|
||||
foreach ($updatedFields as $object => $fields) {
|
||||
$fieldMerger->mergeSyncFieldMapping($object, $fields);
|
||||
}
|
||||
|
||||
$settings['sync']['fieldMappings'] = $fieldMerger->getFieldMappings();
|
||||
|
||||
$fieldValidator->validateRequiredFields($form, $this->integrationObject, $settings['sync']['fieldMappings']);
|
||||
|
||||
$this->integrationConfiguration->setFeatureSettings($settings);
|
||||
}
|
||||
|
||||
// Dispatch event prior to saving the Integration. Bundles/plugins may need to modify some field values before save
|
||||
$configEvent = new ConfigSaveEvent($this->integrationConfiguration);
|
||||
$eventDispatcher->dispatch($configEvent, IntegrationEvents::INTEGRATION_CONFIG_BEFORE_SAVE);
|
||||
|
||||
// Show the form if there are errors and the plugin is published or the authorized button was clicked
|
||||
$integrationDetailsPost = $request->request->all()['integration_details'] ?? [];
|
||||
$authorize = !empty($integrationDetailsPost['in_auth']);
|
||||
if ($form->isSubmitted() && !$form->isValid() && ($this->integrationConfiguration->getIsPublished() || $authorize)) {
|
||||
return $this->showForm($request, $form, $formExtension);
|
||||
}
|
||||
|
||||
// Save the integration configuration
|
||||
$integrationsHelper->saveIntegrationConfiguration($this->integrationConfiguration);
|
||||
|
||||
// Dispatch after save event
|
||||
$eventDispatcher->dispatch($configEvent, IntegrationEvents::INTEGRATION_CONFIG_AFTER_SAVE);
|
||||
|
||||
// Show the form if the apply button was clicked
|
||||
if ($this->isFormApplied($form)) {
|
||||
// Regenerate the form
|
||||
$this->resetFieldsInSession($request);
|
||||
$form = $this->getForm($formFactory);
|
||||
|
||||
return $this->showForm($request, $form, $formExtension);
|
||||
}
|
||||
|
||||
// Otherwise close the modal
|
||||
return $this->closeForm($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FormInterface<mixed>
|
||||
*/
|
||||
private function getForm(FormFactoryInterface $formFactory): FormInterface
|
||||
{
|
||||
return $formFactory->create(
|
||||
$this->integrationObject->getConfigFormName() ?: IntegrationConfigType::class,
|
||||
$this->integrationConfiguration,
|
||||
[
|
||||
'action' => $this->generateUrl('mautic_integration_config', ['integration' => $this->integrationObject->getName()]),
|
||||
'integration' => $this->integrationObject->getName(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormInterface<mixed> $form
|
||||
*/
|
||||
private function showForm(Request $request, FormInterface $form, FormExtension $formExtension): Response
|
||||
{
|
||||
$integrationObject = $this->integrationObject;
|
||||
$formView = $form->createView();
|
||||
|
||||
$showFeaturesTab = $integrationObject instanceof ConfigFormFeaturesInterface
|
||||
|| $integrationObject instanceof ConfigFormSyncInterface
|
||||
|| $integrationObject instanceof ConfigFormFeatureSettingsInterface;
|
||||
|
||||
$hasFeatureErrors = (
|
||||
$integrationObject instanceof ConfigFormFeatureSettingsInterface
|
||||
&& $formExtension->containsErrors($formView['featureSettings']['integration'])
|
||||
) || (
|
||||
isset($formView['featureSettings']['sync']['integration'])
|
||||
&& $formExtension->containsErrors($formView['featureSettings']['sync']['integration'])
|
||||
);
|
||||
|
||||
$hasAuthErrors = $integrationObject instanceof ConfigFormAuthInterface && $formExtension->containsErrors($formView['apiKeys']);
|
||||
|
||||
$useSyncFeatures = $integrationObject instanceof ConfigFormSyncInterface;
|
||||
|
||||
$useFeatureSettings = $integrationObject instanceof ConfigFormFeatureSettingsInterface;
|
||||
|
||||
$useAuthorizationUrl = $integrationObject instanceof ConfigFormAuthorizeButtonInterface;
|
||||
|
||||
$callbackUrl = $integrationObject instanceof ConfigFormCallbackInterface ?
|
||||
$integrationObject->getRedirectUri()
|
||||
: false;
|
||||
|
||||
$useConfigFormNotes = $integrationObject instanceof ConfigFormNotesInterface;
|
||||
|
||||
return $this->delegateView(
|
||||
[
|
||||
'viewParameters' => [
|
||||
'integrationObject' => $integrationObject,
|
||||
'form' => $formView,
|
||||
'activeTab' => $request->get('activeTab'),
|
||||
'showFeaturesTab' => $showFeaturesTab,
|
||||
'hasFeatureErrors' => $hasFeatureErrors,
|
||||
'hasAuthErrors' => $hasAuthErrors,
|
||||
'useSyncFeatures' => $useSyncFeatures,
|
||||
'useFeatureSettings' => $useFeatureSettings,
|
||||
'useAuthorizationUrl' => $useAuthorizationUrl,
|
||||
'callbackUrl' => $callbackUrl,
|
||||
'useConfigFormNotes' => $useConfigFormNotes,
|
||||
],
|
||||
'contentTemplate' => $integrationObject->getConfigFormContentTemplate()
|
||||
?: '@Integrations/Config/form.html.twig',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => '#mautic_plugin_index',
|
||||
'mauticContent' => 'integrationsConfig',
|
||||
'route' => false,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function closeForm(Request $request): JsonResponse
|
||||
{
|
||||
$this->resetFieldsInSession($request);
|
||||
|
||||
$response = [
|
||||
'closeModal' => 1,
|
||||
'enabled' => $this->integrationConfiguration->getIsPublished(),
|
||||
'name' => $this->integrationConfiguration->getName(),
|
||||
'mauticContent' => 'integrationsConfig',
|
||||
'flashes' => $this->getFlashContent(),
|
||||
];
|
||||
|
||||
if ($this->integrationObject instanceof ConfigFormAuthorizeButtonInterface) {
|
||||
// Dispatch event to allow listeners to extract information and/or manipulate the URL
|
||||
$authUrl = $this->integrationObject->getAuthorizationUrl();
|
||||
$authUrlEvent = new ConfigAuthUrlEvent($this->integrationConfiguration, $authUrl);
|
||||
$this->dispatcher->dispatch($authUrlEvent, IntegrationEvents::INTEGRATION_CONFIG_ON_GENERATE_AUTH_URL);
|
||||
|
||||
$response['authUrl'] = $authUrlEvent->getAuthUrl();
|
||||
}
|
||||
|
||||
return new JsonResponse($response);
|
||||
}
|
||||
|
||||
private function resetFieldsInSession(Request $request): void
|
||||
{
|
||||
$session = $request->getSession();
|
||||
$session->remove("{$this->integrationObject->getName()}-fields");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Controller\CommonController;
|
||||
use Mautic\IntegrationsBundle\Exception\IntegrationNotFoundException;
|
||||
use Mautic\IntegrationsBundle\Form\Type\IntegrationSyncSettingsObjectFieldMappingType;
|
||||
use Mautic\IntegrationsBundle\Helper\ConfigIntegrationsHelper;
|
||||
use Mautic\IntegrationsBundle\Helper\FieldFilterHelper;
|
||||
use Mautic\IntegrationsBundle\Helper\FieldMergerHelper;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class FieldPaginationController extends CommonController
|
||||
{
|
||||
/**
|
||||
* @return Response
|
||||
*/
|
||||
public function paginateAction(
|
||||
Request $request,
|
||||
FormFactoryInterface $formFactory,
|
||||
ConfigIntegrationsHelper $integrationsHelper,
|
||||
string $integration,
|
||||
string $object,
|
||||
int $page,
|
||||
) {
|
||||
// Check ACL
|
||||
if (!$this->security->isGranted('plugin:plugins:manage')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
// Find the integration
|
||||
try {
|
||||
/** @var ConfigFormSyncInterface $integrationObject */
|
||||
$integrationObject = $integrationsHelper->getIntegration($integration);
|
||||
$integrationConfiguration = $integrationObject->getIntegrationConfiguration();
|
||||
} catch (IntegrationNotFoundException) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
$keyword = $request->get('keyword');
|
||||
$featureSettings = $integrationConfiguration->getFeatureSettings();
|
||||
$currentFields = $this->getFields($request, $integrationObject, $featureSettings, $object);
|
||||
|
||||
$fieldFilterHelper = new FieldFilterHelper($integrationObject);
|
||||
if ($keyword) {
|
||||
$fieldFilterHelper->filterFieldsByKeyword($object, $keyword, $page);
|
||||
} else {
|
||||
$fieldFilterHelper->filterFieldsByPage($object, $page);
|
||||
}
|
||||
|
||||
// Create the form
|
||||
$form = $formFactory->create(
|
||||
IntegrationSyncSettingsObjectFieldMappingType::class,
|
||||
$currentFields,
|
||||
[
|
||||
'integrationFields' => $fieldFilterHelper->getFilteredFields(),
|
||||
'page' => $page,
|
||||
'keyword' => $keyword,
|
||||
'totalFieldCount' => $fieldFilterHelper->getTotalFieldCount(),
|
||||
'object' => $object,
|
||||
'integrationObject' => $integrationObject,
|
||||
'csrf_protection' => false,
|
||||
]
|
||||
);
|
||||
|
||||
$html = $this->render(
|
||||
'@Integrations/Config/field_mapping.html.twig',
|
||||
[
|
||||
'form' => $form->createView(),
|
||||
'integration' => $integration,
|
||||
'object' => $object,
|
||||
'page' => $page,
|
||||
]
|
||||
)->getContent();
|
||||
|
||||
$prefix = "integration_config[featureSettings][sync][fieldMappings][$object]";
|
||||
$idPrefix = str_replace(['][', '[', ']'], '_', $prefix);
|
||||
if (str_ends_with($idPrefix, '_')) {
|
||||
$idPrefix = substr($idPrefix, 0, -1);
|
||||
}
|
||||
|
||||
$formType = 'integration_sync_settings_object_field_mapping';
|
||||
$html = preg_replace('/'.$formType.'\[(.*?)\]/', $prefix.'[$1]', $html);
|
||||
$html = str_replace($formType, $idPrefix, $html);
|
||||
|
||||
return new JsonResponse(
|
||||
[
|
||||
'success' => 1,
|
||||
'html' => $html,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function getFields(Request $request, ConfigFormSyncInterface $integrationObject, array $featureSettings, string $object): array
|
||||
{
|
||||
$fields = $featureSettings['sync']['fieldMappings'] ?? [];
|
||||
|
||||
if (!isset($fields[$object])) {
|
||||
$fields[$object] = [];
|
||||
}
|
||||
|
||||
// Pull those changed from session
|
||||
$session = $request->getSession();
|
||||
$sessionFields = $session->get(sprintf('%s-fields', $integrationObject->getName()), []);
|
||||
|
||||
if (!isset($sessionFields[$object])) {
|
||||
return $fields[$object];
|
||||
}
|
||||
|
||||
$fieldMerger = new FieldMergerHelper($integrationObject, $fields);
|
||||
$fieldMerger->mergeSyncFieldMapping($object, $sessionFields[$object]);
|
||||
|
||||
return $fieldMerger->getFieldMappings()[$object];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Controller\CommonController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class UpdateFieldController extends CommonController
|
||||
{
|
||||
public function updateAction(Request $request, string $integration, string $object, string $field): JsonResponse
|
||||
{
|
||||
// Clear the session of previously stored fields in case it got stuck
|
||||
$session = $request->getSession();
|
||||
$updatedFields = $session->get(sprintf('%s-fields', $integration), []);
|
||||
|
||||
if (!isset($updatedFields[$object])) {
|
||||
$updatedFields[$object] = [];
|
||||
}
|
||||
|
||||
if (!isset($updatedFields[$object][$field])) {
|
||||
$updatedFields[$object][$field] = [];
|
||||
}
|
||||
|
||||
if ($mappedField = $request->request->get('mappedField')) {
|
||||
$updatedFields[$object][$field]['mappedField'] = $mappedField;
|
||||
}
|
||||
|
||||
if ($syncDirection = $request->request->get('syncDirection')) {
|
||||
$updatedFields[$object][$field]['syncDirection'] = $syncDirection;
|
||||
}
|
||||
|
||||
$session->set(sprintf('%s-fields', $integration), $updatedFields);
|
||||
|
||||
return new JsonResponse([]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\DTO;
|
||||
|
||||
/**
|
||||
* This class represents tokens which provide links to objects which have been
|
||||
* synced from integrations into Mautic.
|
||||
*/
|
||||
class IntegrationObjectToken
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $objectName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $integration;
|
||||
|
||||
private string $defaultValue = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $linkText;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $baseURL;
|
||||
|
||||
public function __construct(
|
||||
private string $token,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getToken(): string
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $objectName
|
||||
*/
|
||||
public function setObjectName($objectName): void
|
||||
{
|
||||
$this->objectName = $objectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getObjectName()
|
||||
{
|
||||
return $this->objectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $integration
|
||||
*/
|
||||
public function setIntegration($integration): void
|
||||
{
|
||||
$this->integration = $integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIntegration()
|
||||
{
|
||||
return $this->integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $defaultValue
|
||||
*/
|
||||
public function setDefaultValue($defaultValue): void
|
||||
{
|
||||
$this->defaultValue = $defaultValue;
|
||||
}
|
||||
|
||||
public function getDefaultValue(): string
|
||||
{
|
||||
return $this->defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $linkText
|
||||
*/
|
||||
public function setLinkText($linkText): void
|
||||
{
|
||||
$this->linkText = $linkText;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLinkText()
|
||||
{
|
||||
return $this->linkText;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseURL
|
||||
*/
|
||||
public function setBaseURL($baseURL): void
|
||||
{
|
||||
$this->baseURL = $baseURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseURL()
|
||||
{
|
||||
return $this->baseURL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\DTO;
|
||||
|
||||
final class Note
|
||||
{
|
||||
public const TYPE_WARNING = 'warning';
|
||||
|
||||
public const TYPE_INFO = 'info';
|
||||
|
||||
private string $type;
|
||||
|
||||
public function __construct(
|
||||
private string $note,
|
||||
string $type,
|
||||
) {
|
||||
if (!in_array($type, [self::TYPE_INFO, self::TYPE_WARNING])) {
|
||||
throw new \InvalidArgumentException(sprintf('Type value can be either "%s" or "%s".', self::TYPE_INFO, self::TYPE_WARNING));
|
||||
}
|
||||
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function getNote(): string
|
||||
{
|
||||
return $this->note;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class AuthenticationIntegrationPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$taggedServices = $container->findTaggedServiceIds('mautic.authentication_integration');
|
||||
$integrationsHelper = $container->findDefinition('mautic.integrations.helper.auth_integrations');
|
||||
|
||||
foreach ($taggedServices as $id => $tags) {
|
||||
$integrationsHelper->addMethodCall('addIntegration', [new Reference($id)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class BuilderIntegrationPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$taggedServices = $container->findTaggedServiceIds('mautic.builder_integration');
|
||||
$integrationsHelper = $container->findDefinition('mautic.integrations.helper.builder_integrations');
|
||||
|
||||
foreach ($taggedServices as $id => $tags) {
|
||||
$integrationsHelper->addMethodCall('addIntegration', [new Reference($id)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class ConfigIntegrationPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$taggedServices = $container->findTaggedServiceIds('mautic.config_integration');
|
||||
$integrationsHelper = $container->findDefinition('mautic.integrations.helper.config_integrations');
|
||||
|
||||
foreach ($taggedServices as $id => $tags) {
|
||||
$integrationsHelper->addMethodCall('addIntegration', [new Reference($id)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class IntegrationsPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$taggedServices = $container->findTaggedServiceIds('mautic.basic_integration');
|
||||
$integrationsHelper = $container->findDefinition('mautic.integrations.helper');
|
||||
|
||||
foreach ($taggedServices as $id => $tags) {
|
||||
$integrationsHelper->addMethodCall('addIntegration', [new Reference($id)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class SyncIntegrationsPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$taggedServices = $container->findTaggedServiceIds('mautic.sync_integration');
|
||||
$syncIntegrationsHelper = $container->findDefinition('mautic.integrations.helper.sync_integrations');
|
||||
|
||||
foreach ($taggedServices as $id => $tags) {
|
||||
$syncIntegrationsHelper->addMethodCall('addIntegration', [new Reference($id)]);
|
||||
}
|
||||
|
||||
$taggedServices = $container->findTaggedServiceIds('mautic.sync.notification_handler');
|
||||
$handlerContainer = $container->findDefinition('mautic.integrations.sync.notification.handler_container');
|
||||
|
||||
foreach ($taggedServices as $id => $tags) {
|
||||
$handlerContainer->addMethodCall('registerHandler', [new Reference($id)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\DependencyInjection\Compiler;
|
||||
|
||||
use GuzzleHttp\Handler\MockHandler;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class TestPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$definition = $container->getDefinition('mautic.integrations.auth_provider.oauth2threelegged');
|
||||
$definition->setFactory('\Mautic\IntegrationsBundle\Tests\Functional\Auth\Provider\Oauth2ThreeLegged\HttpFactory::factory');
|
||||
$definition->addArgument(new Reference(MockHandler::class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\DependencyInjection;
|
||||
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\BuilderInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
|
||||
|
||||
class IntegrationsExtension extends Extension
|
||||
{
|
||||
/**
|
||||
* @param mixed[] $configs
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Config'));
|
||||
$loader->load('services.php');
|
||||
|
||||
$container->registerForAutoconfiguration(IntegrationInterface::class)
|
||||
->addTag('mautic.integration');
|
||||
$container->registerForAutoconfiguration(BasicInterface::class)
|
||||
->addTag('mautic.basic_integration');
|
||||
$container->registerForAutoconfiguration(ConfigFormInterface::class)
|
||||
->addTag('mautic.config_integration');
|
||||
$container->registerForAutoconfiguration(BuilderInterface::class)
|
||||
->addTag('mautic.builder_integration');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Entity;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
|
||||
|
||||
class FieldChange
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $integration;
|
||||
|
||||
/**
|
||||
* @var int|string
|
||||
*/
|
||||
private $objectId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $objectType;
|
||||
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $modifiedAt;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $columnName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $columnType;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $columnValue;
|
||||
|
||||
public static function loadMetadata(ORM\ClassMetadata $metadata): void
|
||||
{
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder
|
||||
->setTable('sync_object_field_change_report')
|
||||
->setCustomRepositoryClass(FieldChangeRepository::class)
|
||||
->addIndex(['object_type', 'object_id', 'column_name'], 'object_composite_key')
|
||||
->addIndex(['integration', 'object_type', 'object_id', 'column_name'], 'integration_object_composite_key')
|
||||
->addIndex(['integration', 'object_type', 'modified_at'], 'integration_object_type_modification_composite_key');
|
||||
|
||||
$builder->addId();
|
||||
|
||||
$builder
|
||||
->createField('integration', Types::STRING)
|
||||
->build();
|
||||
|
||||
$builder->addBigIntIdField('objectId', 'object_id', false);
|
||||
|
||||
$builder
|
||||
->createField('objectType', Types::STRING)
|
||||
->columnName('object_type')
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('modifiedAt', Types::DATETIME_MUTABLE)
|
||||
->columnName('modified_at')
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('columnName', Types::STRING)
|
||||
->columnName('column_name')
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('columnType', Types::STRING)
|
||||
->columnName('column_type')
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('columnValue', Types::TEXT)
|
||||
->columnName('column_value')
|
||||
->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getIntegration(): string
|
||||
{
|
||||
return $this->integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $integration
|
||||
*
|
||||
* @return FieldChange
|
||||
*/
|
||||
public function setIntegration($integration)
|
||||
{
|
||||
$this->integration = $integration;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setObjectId(int $id): self
|
||||
{
|
||||
$this->objectId = (string) $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObjectId(): int
|
||||
{
|
||||
return (int) $this->objectId;
|
||||
}
|
||||
|
||||
public function setObjectType(string $type): self
|
||||
{
|
||||
$this->objectType = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObjectType(): string
|
||||
{
|
||||
return $this->objectType;
|
||||
}
|
||||
|
||||
public function setModifiedAt(\DateTime $time): self
|
||||
{
|
||||
$this->modifiedAt = $time;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getModifiedAt(): \DateTimeInterface
|
||||
{
|
||||
return $this->modifiedAt;
|
||||
}
|
||||
|
||||
public function setColumnName(string $name): self
|
||||
{
|
||||
$this->columnName = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getColumnName(): string
|
||||
{
|
||||
return $this->columnName;
|
||||
}
|
||||
|
||||
public function setColumnType(string $type): self
|
||||
{
|
||||
$this->columnType = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getColumnType(): string
|
||||
{
|
||||
return $this->columnType;
|
||||
}
|
||||
|
||||
public function setColumnValue(string $value): self
|
||||
{
|
||||
$this->columnValue = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getColumnValue(): string
|
||||
{
|
||||
return $this->columnValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Entity;
|
||||
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Query\Expression\CompositeExpression;
|
||||
use Mautic\CoreBundle\Entity\CommonRepository;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
|
||||
/**
|
||||
* @extends CommonRepository<FieldChange>
|
||||
*/
|
||||
class FieldChangeRepository extends CommonRepository
|
||||
{
|
||||
/**
|
||||
* Takes an object id & type and deletes all entities
|
||||
* that match the given column names.
|
||||
*/
|
||||
public function deleteEntitiesForObjectByColumnName(int $objectId, string $objectType, array $columnNames): void
|
||||
{
|
||||
$qb = $this->getEntityManager()->getConnection()->createQueryBuilder();
|
||||
|
||||
$qb
|
||||
->delete(MAUTIC_TABLE_PREFIX.'sync_object_field_change_report')
|
||||
->where(
|
||||
$qb->expr()->and(
|
||||
$qb->expr()->eq('object_type', ':objectType'),
|
||||
$qb->expr()->eq('object_id', ':objectId'),
|
||||
$qb->expr()->in('column_name', ':columnNames')
|
||||
)
|
||||
)
|
||||
->setParameter('objectType', $objectType)
|
||||
->setParameter('objectId', $objectId)
|
||||
->setParameter('columnNames', $columnNames, ArrayParameterType::STRING)
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an object id & type and deletes all entities that match.
|
||||
*/
|
||||
public function deleteEntitiesForObject(int $objectId, string $objectType, ?string $integration = null, ?\DateTimeInterface $toDateTime = null): void
|
||||
{
|
||||
$qb = $this->getEntityManager()->getConnection()->createQueryBuilder();
|
||||
|
||||
$expr = CompositeExpression::and($qb->expr()->eq('object_type', ':objectType'), $qb->expr()->eq('object_id', ':objectId'));
|
||||
if ($integration) {
|
||||
$expr = $expr->with(
|
||||
$qb->expr()->eq('integration', ':integration')
|
||||
);
|
||||
$qb->setParameter('integration', $integration);
|
||||
}
|
||||
|
||||
if (null !== $toDateTime) {
|
||||
$expr = $expr->with($qb->expr()->lte('modified_at', ':toDateTime'));
|
||||
$qb->setParameter('toDateTime', $toDateTime->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
$qb->setParameter('objectType', $objectType)
|
||||
->setParameter('objectId', $objectId);
|
||||
|
||||
$qb
|
||||
->delete(MAUTIC_TABLE_PREFIX.'sync_object_field_change_report')
|
||||
->where($expr)
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $afterObjectId
|
||||
* @param int $objectCount
|
||||
*/
|
||||
public function findChangesBefore(string $integration, string $objectType, \DateTimeInterface $toDateTime, $afterObjectId = null, $objectCount = 100): array
|
||||
{
|
||||
// Get a list of object IDs so that we can get complete snapshots of the objects
|
||||
$qb = $this->getEntityManager()->getConnection()->createQueryBuilder();
|
||||
$qb
|
||||
->select('f.object_id')
|
||||
->from(MAUTIC_TABLE_PREFIX.'sync_object_field_change_report', 'f')
|
||||
->where(
|
||||
$qb->expr()->and(
|
||||
$qb->expr()->eq('f.integration', ':integration'),
|
||||
$qb->expr()->eq('f.object_type', ':objectType'),
|
||||
$qb->expr()->lte('f.modified_at', ':toDateTime')
|
||||
)
|
||||
);
|
||||
if (Lead::class === $objectType) {
|
||||
$qb->join('f', MAUTIC_TABLE_PREFIX.'leads', 'l', 'l.id = f.object_id');
|
||||
}
|
||||
$qb->setParameter('integration', $integration)
|
||||
->setParameter('objectType', $objectType)
|
||||
->setParameter('toDateTime', $toDateTime->format('Y-m-d H:i:s'))
|
||||
->orderBy('f.object_id')
|
||||
->groupBy('f.object_id')
|
||||
->setMaxResults($objectCount);
|
||||
|
||||
if ($afterObjectId) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->gt('f.object_id', (int) $afterObjectId)
|
||||
);
|
||||
}
|
||||
|
||||
$objectIds = $qb->executeQuery()->fetchFirstColumn();
|
||||
|
||||
if (!$objectIds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get all the field changes for the requested objects
|
||||
$qb
|
||||
->resetQueryParts()
|
||||
->select('*')
|
||||
->from(MAUTIC_TABLE_PREFIX.'sync_object_field_change_report', 'f')
|
||||
->where(
|
||||
$qb->expr()->and(
|
||||
$qb->expr()->eq('f.integration', ':integration'),
|
||||
$qb->expr()->eq('f.object_type', ':objectType'),
|
||||
$qb->expr()->in('f.object_id', $objectIds)
|
||||
)
|
||||
)
|
||||
->setParameter('integration', $integration)
|
||||
->setParameter('objectType', $objectType)
|
||||
// 1. We must sort by f.object_id. Otherwise values stored in PartialObjectReportBuilder::lastProcessedTrackedId will be incorrect.
|
||||
// 2. Newer updated fields must override older updated fields
|
||||
->orderBy('f.object_id, f.modified_at', 'ASC');
|
||||
|
||||
return $qb->executeQuery()->fetchAllAssociative();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $objectId
|
||||
*/
|
||||
public function findChangesForObject(string $integration, string $objectType, $objectId): array
|
||||
{
|
||||
// Get a list of object IDs so that we can get complete snapshots of the objects
|
||||
$qb = $this->getEntityManager()->getConnection()->createQueryBuilder();
|
||||
$qb
|
||||
->select('*')
|
||||
->from(MAUTIC_TABLE_PREFIX.'sync_object_field_change_report', 'f')
|
||||
->where(
|
||||
$qb->expr()->and(
|
||||
$qb->expr()->eq('f.integration', ':integration'),
|
||||
$qb->expr()->eq('f.object_type', ':objectType'),
|
||||
$qb->expr()->eq('f.object_id', ':objectId')
|
||||
)
|
||||
)
|
||||
->setParameter('integration', $integration)
|
||||
->setParameter('objectType', $objectType)
|
||||
->setParameter('objectId', (int) $objectId)
|
||||
->orderBy('f.modified_at'); // Newer updated fields must override older updated fields
|
||||
|
||||
return $qb->executeQuery()->fetchAllAssociative();
|
||||
}
|
||||
|
||||
public function deleteOrphanLeadChanges(): int
|
||||
{
|
||||
$totalDeleted = 0;
|
||||
$limit = 1000;
|
||||
$totalLimit = 100000;
|
||||
$deletedInLastLoop = $limit;
|
||||
|
||||
while ($totalDeleted < $totalLimit && $deletedInLastLoop === $limit && $deleted = $this->doDeleteOrphanLeadChanges($limit)) {
|
||||
$deletedInLastLoop = $deleted;
|
||||
$totalDeleted += $deleted;
|
||||
}
|
||||
|
||||
return $totalDeleted;
|
||||
}
|
||||
|
||||
private function doDeleteOrphanLeadChanges(int $limit): int
|
||||
{
|
||||
$qb = $this->getEntityManager()->getConnection()->createQueryBuilder();
|
||||
|
||||
$qb->select('f.id')
|
||||
->from(MAUTIC_TABLE_PREFIX.'sync_object_field_change_report', 'f')
|
||||
->leftJoin('f', MAUTIC_TABLE_PREFIX.'leads', 'l', 'l.id = f.object_id')
|
||||
->where(
|
||||
$qb->expr()->and(
|
||||
$qb->expr()->eq('object_type', ':objectType'),
|
||||
$qb->expr()->isNull('l.id')
|
||||
)
|
||||
)
|
||||
->setMaxResults($limit)
|
||||
->setParameter('objectType', Lead::class);
|
||||
|
||||
$objectIds = $qb->executeQuery()->fetchFirstColumn();
|
||||
|
||||
if (!$objectIds) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$qb2 = $this->getEntityManager()->getConnection()->createQueryBuilder();
|
||||
$qb2->delete(MAUTIC_TABLE_PREFIX.'sync_object_field_change_report')
|
||||
->where(
|
||||
$qb2->expr()->in('id', ':ids')
|
||||
)
|
||||
->setParameter('ids', $objectIds, ArrayParameterType::INTEGER);
|
||||
|
||||
return $qb2->executeStatement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Entity;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
|
||||
|
||||
class ObjectMapping
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
private ?\DateTimeInterface $dateCreated;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $integration;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $internalObjectName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $internalObjectId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $integrationObjectName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $integrationObjectId;
|
||||
|
||||
private ?\DateTimeInterface $lastSyncDate;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $internalStorage = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isDeleted = false;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $integrationReferenceId;
|
||||
|
||||
public static function loadMetadata(ORM\ClassMetadata $metadata): void
|
||||
{
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder
|
||||
->setTable('sync_object_mapping')
|
||||
->setCustomRepositoryClass(ObjectMappingRepository::class)
|
||||
->addIndex(['internal_object_id'], 'internal_object_id_idx')
|
||||
->addIndex(['integration', 'integration_object_name', 'integration_object_id', 'integration_reference_id'], 'integration_object')
|
||||
->addIndex(['integration', 'integration_object_name', 'integration_reference_id', 'integration_object_id'], 'integration_reference')
|
||||
->addIndex(['integration', 'internal_object_name', 'last_sync_date'], 'integration_integration_object_name_last_sync_date')
|
||||
->addIndex(['integration', 'last_sync_date'], 'integration_last_sync_date');
|
||||
|
||||
$builder->addId();
|
||||
|
||||
$builder
|
||||
->createField('dateCreated', Types::DATETIME_MUTABLE)
|
||||
->columnName('date_created')
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('integration', Types::STRING)
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('internalObjectName', Types::STRING)
|
||||
->columnName('internal_object_name')
|
||||
->build();
|
||||
|
||||
$builder->addBigIntIdField('internalObjectId', 'internal_object_id', false);
|
||||
|
||||
$builder
|
||||
->createField('integrationObjectName', Types::STRING)
|
||||
->columnName('integration_object_name')
|
||||
->build();
|
||||
|
||||
// Must be a string as not all IDs are integer based
|
||||
$builder
|
||||
->createField('integrationObjectId', Types::STRING)
|
||||
->columnName('integration_object_id')
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('lastSyncDate', Types::DATETIME_MUTABLE)
|
||||
->columnName('last_sync_date')
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('internalStorage', Types::JSON)
|
||||
->columnName('internal_storage')
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('isDeleted', Types::BOOLEAN)
|
||||
->columnName('is_deleted')
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('integrationReferenceId', Types::STRING)
|
||||
->columnName('integration_reference_id')
|
||||
->nullable()
|
||||
->build();
|
||||
}
|
||||
|
||||
public function __construct(?\DateTime $dateCreated = null)
|
||||
{
|
||||
if (null === $dateCreated) {
|
||||
$dateCreated = new \DateTime();
|
||||
}
|
||||
|
||||
$this->dateCreated = $dateCreated;
|
||||
$this->lastSyncDate = $dateCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null ?int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return ObjectMapping
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface|null
|
||||
*/
|
||||
public function getDateCreated()
|
||||
{
|
||||
return $this->dateCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIntegration()
|
||||
{
|
||||
return $this->integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $integration
|
||||
*
|
||||
* @return ObjectMapping
|
||||
*/
|
||||
public function setIntegration($integration)
|
||||
{
|
||||
$this->integration = $integration;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getInternalObjectName()
|
||||
{
|
||||
return $this->internalObjectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $internalObjectName
|
||||
*
|
||||
* @return ObjectMapping
|
||||
*/
|
||||
public function setInternalObjectName($internalObjectName)
|
||||
{
|
||||
$this->internalObjectName = $internalObjectName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInternalObjectId(): int
|
||||
{
|
||||
return (int) $this->internalObjectId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $internalObjectId
|
||||
*
|
||||
* @return ObjectMapping
|
||||
*/
|
||||
public function setInternalObjectId($internalObjectId)
|
||||
{
|
||||
$this->internalObjectId = (string) $internalObjectId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIntegrationObjectName()
|
||||
{
|
||||
return $this->integrationObjectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $integrationObjectName
|
||||
*
|
||||
* @return ObjectMapping
|
||||
*/
|
||||
public function setIntegrationObjectName($integrationObjectName)
|
||||
{
|
||||
$this->integrationObjectName = $integrationObjectName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIntegrationObjectId()
|
||||
{
|
||||
return $this->integrationObjectId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $integrationObjectId
|
||||
*
|
||||
* @return ObjectMapping
|
||||
*/
|
||||
public function setIntegrationObjectId($integrationObjectId)
|
||||
{
|
||||
$this->integrationObjectId = $integrationObjectId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getLastSyncDate()
|
||||
{
|
||||
return $this->lastSyncDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|null $lastSyncDate
|
||||
*
|
||||
* @return ObjectMapping
|
||||
*/
|
||||
public function setLastSyncDate($lastSyncDate)
|
||||
{
|
||||
if (null === $lastSyncDate) {
|
||||
$lastSyncDate = new \DateTime();
|
||||
}
|
||||
|
||||
$this->lastSyncDate = $lastSyncDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getInternalStorage()
|
||||
{
|
||||
return $this->internalStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $internalStorage
|
||||
*
|
||||
* @return ObjectMapping
|
||||
*/
|
||||
public function setInternalStorage($internalStorage)
|
||||
{
|
||||
$this->internalStorage = $internalStorage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function appendToInternalStorage($key, $value)
|
||||
{
|
||||
$this->internalStorage[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDeleted()
|
||||
{
|
||||
return $this->isDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $isDeleted
|
||||
*
|
||||
* @return ObjectMapping
|
||||
*/
|
||||
public function setIsDeleted($isDeleted)
|
||||
{
|
||||
$this->isDeleted = $isDeleted;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getIntegrationReferenceId()
|
||||
{
|
||||
return $this->integrationReferenceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $integrationReferenceId
|
||||
*
|
||||
* @return ObjectMapping
|
||||
*/
|
||||
public function setIntegrationReferenceId($integrationReferenceId)
|
||||
{
|
||||
$this->integrationReferenceId = $integrationReferenceId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Entity;
|
||||
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Mautic\CoreBundle\Entity\CommonRepository;
|
||||
use Mautic\CoreBundle\Helper\DateTimeHelper;
|
||||
|
||||
/**
|
||||
* @extends CommonRepository<ObjectMapping>
|
||||
*/
|
||||
class ObjectMappingRepository extends CommonRepository
|
||||
{
|
||||
public function getInternalObject($integration, $integrationObjectName, $integrationObjectId, $internalObjectName): ?array
|
||||
{
|
||||
return $this->doGetInternalObject($integration, $integrationObjectName, $integrationObjectId, $internalObjectName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>|null
|
||||
*/
|
||||
public function getInternalObjectWithLock(string $integration, string $integrationObjectName, string $integrationObjectId, string $internalObjectName, string $lock = 'LOCK IN SHARE MODE'): ?array
|
||||
{
|
||||
return $this->doGetInternalObject($integration, $integrationObjectName, $integrationObjectId, $internalObjectName, $lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function getIntegrationObject($integration, $internalObjectName, $internalObjectId, $integrationObjectName)
|
||||
{
|
||||
$qb = $this->getEntityManager()->getConnection()->createQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from(MAUTIC_TABLE_PREFIX.'sync_object_mapping', 'i')
|
||||
->where(
|
||||
$qb->expr()->and(
|
||||
$qb->expr()->eq('i.integration', ':integration'),
|
||||
$qb->expr()->eq('i.internal_object_name', ':internalObjectName'),
|
||||
$qb->expr()->eq('i.internal_object_id', ':internalObjectId'),
|
||||
$qb->expr()->eq('i.integration_object_name', ':integrationObjectName')
|
||||
)
|
||||
)
|
||||
->setParameter('integration', $integration)
|
||||
->setParameter('internalObjectName', $internalObjectName)
|
||||
->setParameter('internalObjectId', $internalObjectId)
|
||||
->setParameter('integrationObjectName', $integrationObjectName);
|
||||
|
||||
$result = $qb->executeQuery()->fetchAssociative();
|
||||
|
||||
return $result ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $integration
|
||||
* @param string $oldObjectName
|
||||
* @param mixed $oldObjectId
|
||||
* @param string $newObjectName
|
||||
* @param mixed $newObjectId
|
||||
*/
|
||||
public function updateIntegrationObject($integration, $oldObjectName, $oldObjectId, $newObjectName, $newObjectId): int
|
||||
{
|
||||
$qb = $this->getEntityManager()->getConnection()->createQueryBuilder();
|
||||
|
||||
$qb->update(MAUTIC_TABLE_PREFIX.'sync_object_mapping', 'i')
|
||||
->set('integration_object_name', ':newObjectName')
|
||||
->set('integration_object_id', ':newObjectId')
|
||||
->where(
|
||||
$qb->expr()->and(
|
||||
$qb->expr()->eq('i.integration', ':integration'),
|
||||
$qb->expr()->eq('i.integration_object_name', ':oldObjectName'),
|
||||
$qb->expr()->eq('i.integration_object_id', ':oldObjectId')
|
||||
)
|
||||
)
|
||||
->setParameter('newObjectName', $newObjectName)
|
||||
->setParameter('newObjectId', $newObjectId)
|
||||
->setParameter('integration', $integration)
|
||||
->setParameter('oldObjectName', $oldObjectName)
|
||||
->setParameter('oldObjectId', $oldObjectId);
|
||||
|
||||
return $qb->executeStatement();
|
||||
}
|
||||
|
||||
public function updateInternalObjectId(int $internalObjectId, int $id): int
|
||||
{
|
||||
$qb = $this->getEntityManager()->getConnection()->createQueryBuilder();
|
||||
|
||||
$qb->update(MAUTIC_TABLE_PREFIX.'sync_object_mapping')
|
||||
->set('internal_object_id', ':internalObjectId')
|
||||
->where($qb->expr()->eq('id', ':id'))
|
||||
->setParameter('internalObjectId', $internalObjectId)
|
||||
->setParameter('id', $id);
|
||||
|
||||
return $qb->executeStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows inserting a new record when an ORM way is not possible.
|
||||
* For example, when coping with \Doctrine\DBAL\Exception\RetryableException.
|
||||
*/
|
||||
public function insert(string $integration, string $integrationObjectName, string $integrationObjectId, string $internalObjectName, int $internalObjectId, ?\DateTimeInterface $createdAt = null): int
|
||||
{
|
||||
$createdAt = $createdAt ?: new \DateTimeImmutable();
|
||||
$qb = $this->getEntityManager()->getConnection()->createQueryBuilder();
|
||||
|
||||
$qb->insert(MAUTIC_TABLE_PREFIX.'sync_object_mapping')
|
||||
->values([
|
||||
'integration' => ':integration',
|
||||
'integration_object_name' => ':integrationObjectName',
|
||||
'integration_object_id' => ':integrationObjectId',
|
||||
'internal_object_name' => ':internalObjectName',
|
||||
'internal_object_id' => ':internalObjectId',
|
||||
'date_created' => ':date',
|
||||
'last_sync_date' => ':date',
|
||||
'is_deleted' => ':isDeleted',
|
||||
'internal_storage' => ':internalStorage',
|
||||
])
|
||||
->setParameter('integration', $integration)
|
||||
->setParameter('integrationObjectName', $integrationObjectName)
|
||||
->setParameter('integrationObjectId', $integrationObjectId)
|
||||
->setParameter('internalObjectName', $internalObjectName)
|
||||
->setParameter('internalObjectId', $internalObjectId)
|
||||
->setParameter('date', $createdAt->format(DateTimeHelper::FORMAT_DB))
|
||||
->setParameter('isDeleted', false, Types::BOOLEAN)
|
||||
->setParameter('internalStorage', [], Types::JSON);
|
||||
|
||||
return $qb->executeStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[]|string $objectIds
|
||||
*
|
||||
* @return \Doctrine\DBAL\Driver\Statement|int
|
||||
*/
|
||||
public function markAsDeleted(string $integration, string $objectName, $objectIds): int
|
||||
{
|
||||
$qb = $this->getEntityManager()->getConnection()->createQueryBuilder();
|
||||
|
||||
$qb->update(MAUTIC_TABLE_PREFIX.'sync_object_mapping', 'm')
|
||||
->set('is_deleted', 1)
|
||||
->where(
|
||||
$qb->expr()->and(
|
||||
$qb->expr()->eq('m.integration', ':integration'),
|
||||
$qb->expr()->eq('m.integration_object_name', ':objectName')
|
||||
)
|
||||
)
|
||||
->setParameter('integration', $integration)
|
||||
->setParameter('objectName', $objectName);
|
||||
|
||||
if (is_array($objectIds)) {
|
||||
$qb->setParameter('objectId', $objectIds, ArrayParameterType::STRING);
|
||||
$qb->andWhere($qb->expr()->in('m.integration_object_id', ':objectId'));
|
||||
} else {
|
||||
$qb->setParameter('objectId', $objectIds);
|
||||
$qb->andWhere($qb->expr()->eq('m.integration_object_id', ':objectId'));
|
||||
}
|
||||
|
||||
return $qb->executeStatement();
|
||||
}
|
||||
|
||||
public function deleteEntitiesForObject(int $internalObjectId, string $internalObject): void
|
||||
{
|
||||
$qb = $this->_em->createQueryBuilder();
|
||||
$qb->delete(ObjectMapping::class, 'm');
|
||||
$qb->where('m.internalObjectName = :internalObject');
|
||||
$qb->andWhere('m.internalObjectId = :internalObjectId');
|
||||
$qb->setParameter('internalObject', $internalObject);
|
||||
$qb->setParameter('internalObjectId', $internalObjectId);
|
||||
$qb->getQuery()->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectMapping[]
|
||||
*/
|
||||
public function getIntegrationMappingsForInternalObject(string $internalObject, int $internalObjectId): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('m');
|
||||
$qb->select('m')
|
||||
->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('m.internalObjectName', ':internalObject'),
|
||||
$qb->expr()->eq('m.internalObjectId', ':internalObjectId')
|
||||
)
|
||||
)
|
||||
->setParameter('internalObject', $internalObject)
|
||||
->setParameter('internalObjectId', $internalObjectId);
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $integration
|
||||
* @param string $integrationObjectName
|
||||
* @param string $integrationObjectId
|
||||
* @param string $internalObjectName
|
||||
*
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
private function doGetInternalObject($integration, $integrationObjectName, $integrationObjectId, $internalObjectName, ?string $lock = null): ?array
|
||||
{
|
||||
$connection = $this->getEntityManager()->getConnection();
|
||||
$qb = $connection->createQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from(MAUTIC_TABLE_PREFIX.'sync_object_mapping', 'i')
|
||||
->where(
|
||||
$qb->expr()->and(
|
||||
$qb->expr()->eq('i.integration', ':integration'),
|
||||
$qb->expr()->eq('i.integration_object_name', ':integrationObjectName'),
|
||||
$qb->expr()->eq('i.integration_object_id', ':integrationObjectId'),
|
||||
$qb->expr()->eq('i.internal_object_name', ':internalObjectName')
|
||||
)
|
||||
)
|
||||
->setParameter('integration', $integration)
|
||||
->setParameter('integrationObjectName', $integrationObjectName)
|
||||
->setParameter('integrationObjectId', $integrationObjectId)
|
||||
->setParameter('internalObjectName', $internalObjectName);
|
||||
|
||||
$lock = $lock ? ' '.$lock : '';
|
||||
$result = $connection->executeQuery($qb->getSQL().$lock, $qb->getParameters(), $qb->getParameterTypes())->fetchAssociative();
|
||||
|
||||
return $result ?: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\MappingManualDAO;
|
||||
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InputOptionsDAO;
|
||||
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\OrderResultsDAO;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class CompletedSyncIterationEvent extends Event
|
||||
{
|
||||
public function __construct(
|
||||
private OrderResultsDAO $orderResultsDAO,
|
||||
private int $iteration,
|
||||
private InputOptionsDAO $inputOptionsDAO,
|
||||
private MappingManualDAO $mappingManualDAO,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getIntegration(): string
|
||||
{
|
||||
return $this->mappingManualDAO->getIntegration();
|
||||
}
|
||||
|
||||
public function getOrderResults(): OrderResultsDAO
|
||||
{
|
||||
return $this->orderResultsDAO;
|
||||
}
|
||||
|
||||
public function getIteration(): int
|
||||
{
|
||||
return $this->iteration;
|
||||
}
|
||||
|
||||
public function getInputOptions(): InputOptionsDAO
|
||||
{
|
||||
return $this->inputOptionsDAO;
|
||||
}
|
||||
|
||||
public function getMappingManual(): MappingManualDAO
|
||||
{
|
||||
return $this->mappingManualDAO;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
final class ConfigAuthUrlEvent extends Event
|
||||
{
|
||||
public function __construct(private Integration $integrationConfiguration, private string $authUrl)
|
||||
{
|
||||
}
|
||||
|
||||
public function getIntegrationConfiguration(): Integration
|
||||
{
|
||||
return $this->integrationConfiguration;
|
||||
}
|
||||
|
||||
public function getIntegration(): string
|
||||
{
|
||||
return $this->integrationConfiguration->getName();
|
||||
}
|
||||
|
||||
public function getAuthUrl(): string
|
||||
{
|
||||
return $this->authUrl;
|
||||
}
|
||||
|
||||
public function setAuthUrl(string $authUrl): void
|
||||
{
|
||||
$this->authUrl = $authUrl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class ConfigSaveEvent extends Event
|
||||
{
|
||||
public function __construct(
|
||||
private Integration $integrationConfiguration,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getIntegrationConfiguration(): Integration
|
||||
{
|
||||
return $this->integrationConfiguration;
|
||||
}
|
||||
|
||||
public function getIntegration(): string
|
||||
{
|
||||
return $this->integrationConfiguration->getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class FormLoadEvent extends Event
|
||||
{
|
||||
public function __construct(
|
||||
private Integration $integrationConfiguration,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getIntegrationConfiguration(): Integration
|
||||
{
|
||||
return $this->integrationConfiguration;
|
||||
}
|
||||
|
||||
public function getIntegration(): string
|
||||
{
|
||||
return $this->integrationConfiguration->getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\LeadBundle\Entity\Company;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
final class InternalCompanyEvent extends Event
|
||||
{
|
||||
public function __construct(
|
||||
private string $integrationName,
|
||||
private Company $company,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getIntegrationName(): string
|
||||
{
|
||||
return $this->integrationName;
|
||||
}
|
||||
|
||||
public function getCompany(): Company
|
||||
{
|
||||
return $this->company;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
final class InternalContactEvent extends Event
|
||||
{
|
||||
public function __construct(
|
||||
private string $integrationName,
|
||||
private Lead $contact,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getIntegrationName(): string
|
||||
{
|
||||
return $this->integrationName;
|
||||
}
|
||||
|
||||
public function getContact(): Lead
|
||||
{
|
||||
return $this->contact;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\IntegrationsBundle\Entity\ObjectMapping;
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\ObjectInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class InternalObjectCreateEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @var ObjectMapping[]
|
||||
*/
|
||||
private array $objectMappings = [];
|
||||
|
||||
public function __construct(
|
||||
private ObjectInterface $object,
|
||||
private array $createObjects,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getObject(): ObjectInterface
|
||||
{
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
public function getCreateObjects(): array
|
||||
{
|
||||
return $this->createObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectMapping[]
|
||||
*/
|
||||
public function getObjectMappings(): array
|
||||
{
|
||||
return $this->objectMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectMapping[] $objectMappings
|
||||
*/
|
||||
public function setObjectMappings(array $objectMappings): void
|
||||
{
|
||||
$this->objectMappings = $objectMappings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\ObjectInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class InternalObjectEvent extends Event
|
||||
{
|
||||
private array $objects = [];
|
||||
|
||||
public function addObject(ObjectInterface $object): void
|
||||
{
|
||||
$this->objects[] = $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectInterface[]
|
||||
*/
|
||||
public function getObjects(): array
|
||||
{
|
||||
return $this->objects;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\ObjectInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
final class InternalObjectFindByIdEvent extends Event
|
||||
{
|
||||
private ?int $id = null;
|
||||
|
||||
private ?object $entity = null;
|
||||
|
||||
public function __construct(
|
||||
private ObjectInterface $object,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getObject(): ObjectInterface
|
||||
{
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(int $id): void
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getEntity(): ?object
|
||||
{
|
||||
return $this->entity;
|
||||
}
|
||||
|
||||
public function setEntity(object $entity): void
|
||||
{
|
||||
$this->entity = $entity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\IntegrationsBundle\Sync\DAO\DateRange;
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\ObjectInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class InternalObjectFindEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private array $ids = [];
|
||||
|
||||
private array $foundObjects = [];
|
||||
|
||||
private ?DateRange $dateRange = null;
|
||||
|
||||
private ?int $start = null;
|
||||
|
||||
private ?int $limit = null;
|
||||
|
||||
private array $fieldValues = [];
|
||||
|
||||
public function __construct(
|
||||
private ObjectInterface $object,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getObject(): ObjectInterface
|
||||
{
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getIds(): array
|
||||
{
|
||||
return $this->ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[]|string[] $ids
|
||||
*/
|
||||
public function setIds(array $ids): void
|
||||
{
|
||||
$this->ids = $ids;
|
||||
}
|
||||
|
||||
public function getFoundObjects(): array
|
||||
{
|
||||
return $this->foundObjects;
|
||||
}
|
||||
|
||||
public function setFoundObjects(array $foundObjects): void
|
||||
{
|
||||
$this->foundObjects = $foundObjects;
|
||||
}
|
||||
|
||||
public function getDateRange(): ?DateRange
|
||||
{
|
||||
return $this->dateRange;
|
||||
}
|
||||
|
||||
public function setDateRange(?DateRange $dateRange): void
|
||||
{
|
||||
$this->dateRange = $dateRange;
|
||||
}
|
||||
|
||||
public function getStart(): ?int
|
||||
{
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
public function setStart(?int $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function getLimit(): ?int
|
||||
{
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
public function setLimit(?int $limit): void
|
||||
{
|
||||
$this->limit = $limit;
|
||||
}
|
||||
|
||||
public function getFieldValues(): array
|
||||
{
|
||||
return $this->fieldValues;
|
||||
}
|
||||
|
||||
public function setFieldValues(array $fieldValues): void
|
||||
{
|
||||
$this->fieldValues = $fieldValues;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\ObjectInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class InternalObjectOwnerEvent extends Event
|
||||
{
|
||||
/**
|
||||
* Format: [object_id => owner_id].
|
||||
*/
|
||||
private array $owners = [];
|
||||
|
||||
/**
|
||||
* @param int[] $objectIds
|
||||
*/
|
||||
public function __construct(
|
||||
private ObjectInterface $object,
|
||||
private array $objectIds,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getObject(): ObjectInterface
|
||||
{
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getObjectIds(): array
|
||||
{
|
||||
return $this->objectIds;
|
||||
}
|
||||
|
||||
public function getOwners(): array
|
||||
{
|
||||
return $this->owners;
|
||||
}
|
||||
|
||||
public function setOwners(array $owners): void
|
||||
{
|
||||
$this->owners = $owners;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\ObjectInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class InternalObjectRouteEvent extends Event
|
||||
{
|
||||
private ?string $route = null;
|
||||
|
||||
public function __construct(
|
||||
private ObjectInterface $object,
|
||||
private int $id,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getObject(): ObjectInterface
|
||||
{
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getRoute(): ?string
|
||||
{
|
||||
return $this->route;
|
||||
}
|
||||
|
||||
public function setRoute(?string $route): void
|
||||
{
|
||||
$this->route = $route;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\UpdatedObjectMappingDAO;
|
||||
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\ObjectInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class InternalObjectUpdateEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @var UpdatedObjectMappingDAO[]
|
||||
*/
|
||||
private array $updatedObjectMappings = [];
|
||||
|
||||
public function __construct(
|
||||
private ObjectInterface $object,
|
||||
private array $identifiedObjectIds,
|
||||
/**
|
||||
* @var ObjectChangeDAO[]
|
||||
*/
|
||||
private array $updateObjects,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getObject(): ObjectInterface
|
||||
{
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
public function getIdentifiedObjectIds(): array
|
||||
{
|
||||
return $this->identifiedObjectIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectChangeDAO[]
|
||||
*/
|
||||
public function getUpdateObjects(): array
|
||||
{
|
||||
return $this->updateObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UpdatedObjectMappingDAO[]
|
||||
*/
|
||||
public function getUpdatedObjectMappings(): array
|
||||
{
|
||||
return $this->updatedObjectMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UpdatedObjectMappingDAO[] $updatedObjectMappings
|
||||
*/
|
||||
public function setUpdatedObjectMappings(array $updatedObjectMappings): void
|
||||
{
|
||||
$this->updatedObjectMappings = $updatedObjectMappings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class KeysDecryptionEvent extends Event
|
||||
{
|
||||
public function __construct(
|
||||
private Integration $integrationConfiguration,
|
||||
private array $keys,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getIntegrationConfiguration(): Integration
|
||||
{
|
||||
return $this->integrationConfiguration;
|
||||
}
|
||||
|
||||
public function getKeys(): array
|
||||
{
|
||||
return $this->keys;
|
||||
}
|
||||
|
||||
public function setKeys(array $keys): void
|
||||
{
|
||||
$this->keys = $keys;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class KeysEncryptionEvent extends Event
|
||||
{
|
||||
public function __construct(
|
||||
private Integration $integrationConfiguration,
|
||||
private array $keys,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getIntegrationConfiguration(): Integration
|
||||
{
|
||||
return $this->integrationConfiguration;
|
||||
}
|
||||
|
||||
public function getKeys(): array
|
||||
{
|
||||
return $this->keys;
|
||||
}
|
||||
|
||||
public function setKeys(array $keys): void
|
||||
{
|
||||
$this->keys = $keys;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
final class KeysSaveEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @var array<string,string>
|
||||
*/
|
||||
private array $newKeys;
|
||||
|
||||
/**
|
||||
* @param array<string,string> $oldKeys
|
||||
*/
|
||||
public function __construct(private Integration $integrationConfiguration, private array $oldKeys)
|
||||
{
|
||||
$this->newKeys = $integrationConfiguration->getApiKeys();
|
||||
}
|
||||
|
||||
public function getIntegrationConfiguration(): Integration
|
||||
{
|
||||
return $this->integrationConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,string>
|
||||
*/
|
||||
public function getOldKeys(): array
|
||||
{
|
||||
return $this->oldKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,string>
|
||||
*/
|
||||
public function getNewKeys(): array
|
||||
{
|
||||
return $this->newKeys;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\CoreBundle\Event\CommonEvent;
|
||||
|
||||
/**
|
||||
* This event is dispatched to allow plugins to provide tokens which create links
|
||||
* to any synced integration objects they may provide.
|
||||
*/
|
||||
class MappedIntegrationObjectTokenEvent extends CommonEvent
|
||||
{
|
||||
private array $tokens = [];
|
||||
|
||||
/**
|
||||
* Add a new mapped integration object token.
|
||||
*
|
||||
* @param string $integrationName - The name of the integration
|
||||
* @param $objectName - The name of the object in sync_object_mapping table
|
||||
* @param $objectLink - The base_url to direct users to the object on the integration site
|
||||
* @param string $title - The title of the token in the token select dropdown
|
||||
* @param string $linkText - The link text used for the url provided in $objectLink
|
||||
* @param string $default - The default value to show when the token value isnt found
|
||||
*/
|
||||
public function addToken(
|
||||
$integrationName,
|
||||
$objectName,
|
||||
$objectLink,
|
||||
$title = '',
|
||||
$linkText = 'Link Text',
|
||||
$default = 'Default Value',
|
||||
): void {
|
||||
$this->tokens[$integrationName][$objectName] = [
|
||||
'base_url' => $objectLink,
|
||||
'token_title' => $title,
|
||||
'link_text' => $linkText,
|
||||
'default' => $default,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getTokens()
|
||||
{
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get only the tokens provided by a particular integration.
|
||||
*
|
||||
* @param string $integrationName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTokensByIntegration($integrationName)
|
||||
{
|
||||
return $this->tokens[$integrationName] ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class MauticSyncFieldsLoadEvent extends Event
|
||||
{
|
||||
public function __construct(
|
||||
private string $objectName,
|
||||
private array $fields,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getFields(): array
|
||||
{
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
public function addField(string $key, string $name): void
|
||||
{
|
||||
$this->fields[$key] = $name;
|
||||
}
|
||||
|
||||
public function getObjectName(): string
|
||||
{
|
||||
return $this->objectName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Event;
|
||||
|
||||
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InputOptionsDAO;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class SyncEvent extends Event
|
||||
{
|
||||
public function __construct(
|
||||
private InputOptionsDAO $inputOptionsDAO,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getIntegrationName(): string
|
||||
{
|
||||
return $this->inputOptionsDAO->getIntegration();
|
||||
}
|
||||
|
||||
public function isIntegration(string $integrationName): bool
|
||||
{
|
||||
return $this->getIntegrationName() === $integrationName;
|
||||
}
|
||||
|
||||
public function getFromDateTime(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->inputOptionsDAO->getStartDateTime();
|
||||
}
|
||||
|
||||
public function getToDateTime(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->inputOptionsDAO->getEndDateTime();
|
||||
}
|
||||
|
||||
public function getInputOptions(): InputOptionsDAO
|
||||
{
|
||||
return $this->inputOptionsDAO;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\EventListener;
|
||||
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectCreateEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectFindByIdEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectFindEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectOwnerEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectRouteEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectUpdateEvent;
|
||||
use Mautic\IntegrationsBundle\IntegrationEvents;
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Company;
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectHelper\CompanyObjectHelper;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
class CompanyObjectSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private CompanyObjectHelper $companyObjectHelper,
|
||||
private RouterInterface $router,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
IntegrationEvents::INTEGRATION_COLLECT_INTERNAL_OBJECTS => ['collectInternalObjects', 0],
|
||||
IntegrationEvents::INTEGRATION_UPDATE_INTERNAL_OBJECTS => ['updateCompanies', 0],
|
||||
IntegrationEvents::INTEGRATION_CREATE_INTERNAL_OBJECTS => ['createCompanies', 0],
|
||||
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS => [
|
||||
['findCompaniesByIds', 0],
|
||||
['findCompaniesByDateRange', 0],
|
||||
['findCompaniesByFieldValues', 0],
|
||||
],
|
||||
IntegrationEvents::INTEGRATION_FIND_OWNER_IDS => ['findOwnerIdsForCompanies', 0],
|
||||
IntegrationEvents::INTEGRATION_BUILD_INTERNAL_OBJECT_ROUTE => ['buildCompanyRoute', 0],
|
||||
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORD => ['findCompanyById', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function collectInternalObjects(InternalObjectEvent $event): void
|
||||
{
|
||||
$event->addObject(new Company());
|
||||
}
|
||||
|
||||
public function updateCompanies(InternalObjectUpdateEvent $event): void
|
||||
{
|
||||
if (Company::NAME !== $event->getObject()->getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setUpdatedObjectMappings(
|
||||
$this->companyObjectHelper->update(
|
||||
$event->getIdentifiedObjectIds(),
|
||||
$event->getUpdateObjects()
|
||||
)
|
||||
);
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function createCompanies(InternalObjectCreateEvent $event): void
|
||||
{
|
||||
if (Company::NAME !== $event->getObject()->getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setObjectMappings($this->companyObjectHelper->create($event->getCreateObjects()));
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function findCompaniesByIds(InternalObjectFindEvent $event): void
|
||||
{
|
||||
if (Company::NAME !== $event->getObject()->getName() || empty($event->getIds())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setFoundObjects($this->companyObjectHelper->findObjectsByIds($event->getIds()));
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function findCompaniesByDateRange(InternalObjectFindEvent $event): void
|
||||
{
|
||||
if (Company::NAME !== $event->getObject()->getName() || empty($event->getDateRange())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setFoundObjects(
|
||||
$this->companyObjectHelper->findObjectsBetweenDates(
|
||||
$event->getDateRange()->getFromDate(),
|
||||
$event->getDateRange()->getToDate(),
|
||||
$event->getStart(),
|
||||
$event->getLimit()
|
||||
)
|
||||
);
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function findCompaniesByFieldValues(InternalObjectFindEvent $event): void
|
||||
{
|
||||
if (Company::NAME !== $event->getObject()->getName() || empty($event->getFieldValues())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setFoundObjects(
|
||||
$this->companyObjectHelper->findObjectsByFieldValues(
|
||||
$event->getFieldValues()
|
||||
)
|
||||
);
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function findOwnerIdsForCompanies(InternalObjectOwnerEvent $event): void
|
||||
{
|
||||
if (Company::NAME !== $event->getObject()->getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setOwners(
|
||||
$this->companyObjectHelper->findOwnerIds(
|
||||
$event->getObjectIds()
|
||||
)
|
||||
);
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function buildCompanyRoute(InternalObjectRouteEvent $event): void
|
||||
{
|
||||
if (Company::NAME !== $event->getObject()->getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setRoute(
|
||||
$this->router->generate(
|
||||
'mautic_company_action',
|
||||
[
|
||||
'objectAction' => 'view',
|
||||
'objectId' => $event->getId(),
|
||||
]
|
||||
)
|
||||
);
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function findCompanyById(InternalObjectFindByIdEvent $event): void
|
||||
{
|
||||
if (null === $event->getId() || Company::NAME !== $event->getObject()->getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$company = $this->companyObjectHelper->findObjectById($event->getId());
|
||||
|
||||
if (null === $company) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->companyObjectHelper->setFieldValues($company);
|
||||
$event->setEntity($company);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\EventListener;
|
||||
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectCreateEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectFindByIdEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectFindEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectOwnerEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectRouteEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalObjectUpdateEvent;
|
||||
use Mautic\IntegrationsBundle\IntegrationEvents;
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectHelper\ContactObjectHelper;
|
||||
use Mautic\LeadBundle\Exception\ImportFailedException;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
class ContactObjectSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ContactObjectHelper $contactObjectHelper,
|
||||
private RouterInterface $router,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
IntegrationEvents::INTEGRATION_COLLECT_INTERNAL_OBJECTS => ['collectInternalObjects', 0],
|
||||
IntegrationEvents::INTEGRATION_UPDATE_INTERNAL_OBJECTS => ['updateContacts', 0],
|
||||
IntegrationEvents::INTEGRATION_CREATE_INTERNAL_OBJECTS => ['createContacts', 0],
|
||||
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS => [
|
||||
['findContactsByIds', 0],
|
||||
['findContactsByDateRange', 0],
|
||||
['findContactsByFieldValues', 0],
|
||||
],
|
||||
IntegrationEvents::INTEGRATION_FIND_OWNER_IDS => ['findOwnerIdsForContacts', 0],
|
||||
IntegrationEvents::INTEGRATION_BUILD_INTERNAL_OBJECT_ROUTE => ['buildContactRoute', 0],
|
||||
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORD => ['findContactById', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function collectInternalObjects(InternalObjectEvent $event): void
|
||||
{
|
||||
$event->addObject(new Contact());
|
||||
}
|
||||
|
||||
public function updateContacts(InternalObjectUpdateEvent $event): void
|
||||
{
|
||||
if (Contact::NAME !== $event->getObject()->getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setUpdatedObjectMappings(
|
||||
$this->contactObjectHelper->update(
|
||||
$event->getIdentifiedObjectIds(),
|
||||
$event->getUpdateObjects()
|
||||
)
|
||||
);
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function createContacts(InternalObjectCreateEvent $event): void
|
||||
{
|
||||
if (Contact::NAME !== $event->getObject()->getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setObjectMappings($this->contactObjectHelper->create($event->getCreateObjects()));
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function findContactsByIds(InternalObjectFindEvent $event): void
|
||||
{
|
||||
if (Contact::NAME !== $event->getObject()->getName() || empty($event->getIds())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setFoundObjects($this->contactObjectHelper->findObjectsByIds($event->getIds()));
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function findContactsByDateRange(InternalObjectFindEvent $event): void
|
||||
{
|
||||
if (Contact::NAME !== $event->getObject()->getName() || empty($event->getDateRange())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setFoundObjects(
|
||||
$this->contactObjectHelper->findObjectsBetweenDates(
|
||||
$event->getDateRange()->getFromDate(),
|
||||
$event->getDateRange()->getToDate(),
|
||||
$event->getStart(),
|
||||
$event->getLimit()
|
||||
)
|
||||
);
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function findContactsByFieldValues(InternalObjectFindEvent $event): void
|
||||
{
|
||||
if (Contact::NAME !== $event->getObject()->getName() || empty($event->getFieldValues())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setFoundObjects(
|
||||
$this->contactObjectHelper->findObjectsByFieldValues(
|
||||
$event->getFieldValues()
|
||||
)
|
||||
);
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function findOwnerIdsForContacts(InternalObjectOwnerEvent $event): void
|
||||
{
|
||||
if (Contact::NAME !== $event->getObject()->getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setOwners(
|
||||
$this->contactObjectHelper->findOwnerIds(
|
||||
$event->getObjectIds()
|
||||
)
|
||||
);
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
public function buildContactRoute(InternalObjectRouteEvent $event): void
|
||||
{
|
||||
if (Contact::NAME !== $event->getObject()->getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setRoute(
|
||||
$this->router->generate(
|
||||
'mautic_contact_action',
|
||||
[
|
||||
'objectAction' => 'view',
|
||||
'objectId' => $event->getId(),
|
||||
]
|
||||
)
|
||||
);
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ImportFailedException
|
||||
*/
|
||||
public function findContactById(InternalObjectFindByIdEvent $event): void
|
||||
{
|
||||
if (null === $event->getId() || Contact::NAME !== $event->getObject()->getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$contact = $this->contactObjectHelper->findObjectById($event->getId());
|
||||
|
||||
if (null === $contact) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->contactObjectHelper->setFieldValues($contact);
|
||||
$event->setEntity($contact);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\EventListener;
|
||||
|
||||
use Mautic\IntegrationsBundle\Exception\IntegrationNotFoundException;
|
||||
use Mautic\IntegrationsBundle\Helper\IntegrationsHelper;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
class ControllerSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private IntegrationsHelper $integrationsHelper,
|
||||
private ControllerResolverInterface $resolver,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
KernelEvents::CONTROLLER => ['onKernelController', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function onKernelController(\Symfony\Component\HttpKernel\Event\ControllerEvent $event): void
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
|
||||
if ('Mautic\PluginBundle\Controller\PluginController::configAction' === $request->get('_controller')) {
|
||||
$integrationName = $request->get('name');
|
||||
$page = $request->get('page');
|
||||
|
||||
try {
|
||||
$this->integrationsHelper->getIntegration($integrationName);
|
||||
$request->attributes->add(
|
||||
[
|
||||
'integration' => $integrationName,
|
||||
'page' => $page,
|
||||
'_controller' => 'Mautic\IntegrationsBundle\Controller\ConfigController::editAction',
|
||||
'_route_params' => [
|
||||
'integration' => $integrationName,
|
||||
'page' => $page,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$controller = $this->resolver->getController($request);
|
||||
$event->setController($controller);
|
||||
} catch (IntegrationNotFoundException) {
|
||||
// Old integration so ignore and let old PluginBundle code handle it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\EventListener;
|
||||
|
||||
use Doctrine\ORM\EntityNotFoundException;
|
||||
use Mautic\EmailBundle\EmailEvents;
|
||||
use Mautic\EmailBundle\Event\EmailBuilderEvent;
|
||||
use Mautic\EmailBundle\Event\EmailSendEvent;
|
||||
use Mautic\IntegrationsBundle\DTO\IntegrationObjectToken as Token;
|
||||
use Mautic\IntegrationsBundle\Entity\ObjectMappingRepository;
|
||||
use Mautic\IntegrationsBundle\Event\MappedIntegrationObjectTokenEvent;
|
||||
use Mautic\IntegrationsBundle\Helper\TokenParser;
|
||||
use Mautic\IntegrationsBundle\IntegrationEvents;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* This class subscribes to events related to building and providing
|
||||
* tokens for emails, particularly the IntegrationObjectToken.
|
||||
*/
|
||||
class EmailSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected TranslatorInterface $translator,
|
||||
protected EventDispatcherInterface $eventDispatcher,
|
||||
protected TokenParser $tokenParser,
|
||||
protected ObjectMappingRepository $objectMappingRepository,
|
||||
protected IntegrationHelper $integrationHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
EmailEvents::EMAIL_ON_BUILD => ['onEmailBuild', 0],
|
||||
EmailEvents::EMAIL_ON_SEND => ['decodeTokens', 0],
|
||||
EmailEvents::EMAIL_ON_DISPLAY => ['decodeTokens', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function onEmailBuild(EmailBuilderEvent $event): void
|
||||
{
|
||||
$tokens = [];
|
||||
|
||||
$mappedObjectTokens = new MappedIntegrationObjectTokenEvent();
|
||||
$this->eventDispatcher->dispatch(
|
||||
$mappedObjectTokens,
|
||||
IntegrationEvents::INTEGRATION_OBJECT_TOKEN_EVENT
|
||||
);
|
||||
|
||||
foreach ($mappedObjectTokens->getTokens() as $integration => $t) {
|
||||
foreach ($t as $integrationObject => $objectData) {
|
||||
$token = $this->tokenParser->buildTokenWithDefaultOptions(
|
||||
$integrationObject,
|
||||
$integration,
|
||||
$objectData['default'],
|
||||
$objectData['link_text'],
|
||||
$objectData['base_url']
|
||||
);
|
||||
|
||||
$tokens[$token] = $objectData['token_title'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($event->tokensRequested(array_keys($tokens))) {
|
||||
$event->addTokens(
|
||||
$event->filterTokens($tokens)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function decodeTokens(EmailSendEvent $event): void
|
||||
{
|
||||
$tokens = $this->tokenParser->findTokens($event->getContent());
|
||||
|
||||
if (0 === $tokens->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens->map(function (Token $token) use ($event): void {
|
||||
try {
|
||||
$integrationObject = $this->objectMappingRepository->getIntegrationObject(
|
||||
$token->getIntegration(),
|
||||
'lead',
|
||||
$event->getLead()['id'],
|
||||
$token->getObjectName()
|
||||
);
|
||||
|
||||
$url = $token->getBaseURL().'/'.$integrationObject['integration_object_id'];
|
||||
$link = "<a href=\"{$url}\" >".$token->getLinkText().'</a>';
|
||||
$event->addToken($token->getToken(), $link);
|
||||
} catch (EntityNotFoundException) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\EventListener;
|
||||
|
||||
use Mautic\IntegrationsBundle\Entity\FieldChange;
|
||||
use Mautic\IntegrationsBundle\Entity\FieldChangeRepository;
|
||||
use Mautic\IntegrationsBundle\Entity\ObjectMappingRepository;
|
||||
use Mautic\IntegrationsBundle\Event\InternalCompanyEvent;
|
||||
use Mautic\IntegrationsBundle\Event\InternalContactEvent;
|
||||
use Mautic\IntegrationsBundle\Exception\IntegrationNotFoundException;
|
||||
use Mautic\IntegrationsBundle\Exception\InvalidValueException;
|
||||
use Mautic\IntegrationsBundle\Helper\SyncIntegrationsHelper;
|
||||
use Mautic\IntegrationsBundle\IntegrationEvents;
|
||||
use Mautic\IntegrationsBundle\Sync\Exception\ObjectNotFoundException;
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
|
||||
use Mautic\IntegrationsBundle\Sync\VariableExpresser\VariableExpresserHelperInterface;
|
||||
use Mautic\LeadBundle\Entity\Company;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Event as Events;
|
||||
use Mautic\LeadBundle\LeadEvents;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class LeadSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private FieldChangeRepository $fieldChangeRepo,
|
||||
private ObjectMappingRepository $objectMappingRepository,
|
||||
private VariableExpresserHelperInterface $variableExpressor,
|
||||
private SyncIntegrationsHelper $syncIntegrationsHelper,
|
||||
private EventDispatcherInterface $dispatcher,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
LeadEvents::LEAD_POST_SAVE => ['onLeadPostSave', 0],
|
||||
LeadEvents::LEAD_POST_DELETE => ['onLeadPostDelete', 255],
|
||||
LeadEvents::COMPANY_POST_SAVE => ['onCompanyPostSave', 0],
|
||||
LeadEvents::COMPANY_POST_DELETE => ['onCompanyPostDelete', 255],
|
||||
LeadEvents::LEAD_COMPANY_CHANGE => ['onLeadCompanyChange', 128],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IntegrationNotFoundException
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public function onLeadPostSave(Events\LeadEvent $event): void
|
||||
{
|
||||
$lead = $event->getLead();
|
||||
if ($lead->isAnonymous()) {
|
||||
// Do not track visitor changes
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined('MAUTIC_INTEGRATION_SYNC_IN_PROGRESS')) {
|
||||
// Don't track changes just made by an active sync
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->syncIntegrationsHelper->hasObjectSyncEnabled(Contact::NAME)) {
|
||||
// Only track if an integration is syncing with contacts
|
||||
return;
|
||||
}
|
||||
|
||||
$changes = $lead->getChanges(true);
|
||||
|
||||
if (!empty($changes['owner'])) {
|
||||
// Force record of owner change if present in changelist
|
||||
$changes['fields']['owner_id'] = $changes['owner'];
|
||||
}
|
||||
|
||||
if (!empty($changes['points'])) {
|
||||
// Add ability to update points custom field in target
|
||||
$changes['fields']['points'] = $changes['points'];
|
||||
}
|
||||
|
||||
if (isset($changes['fields'])) {
|
||||
$this->recordFieldChanges($changes['fields'], $lead->getId(), Lead::class, $lead);
|
||||
}
|
||||
|
||||
if (isset($changes['dnc_channel_status'])) {
|
||||
$dncChanges = [];
|
||||
foreach ($changes['dnc_channel_status'] as $channel => $change) {
|
||||
$oldValue = $change['old_reason'] ?? '';
|
||||
$newValue = $change['reason'];
|
||||
|
||||
$dncChanges['mautic_internal_dnc_'.$channel] = [$oldValue, $newValue];
|
||||
}
|
||||
|
||||
$this->recordFieldChanges($dncChanges, $lead->getId(), Lead::class, $lead);
|
||||
}
|
||||
}
|
||||
|
||||
public function onLeadPostDelete(Events\LeadEvent $event): void
|
||||
{
|
||||
if ($event->getLead()->isAnonymous()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->fieldChangeRepo->deleteEntitiesForObject((int) $event->getLead()->deletedId, Lead::class);
|
||||
$this->objectMappingRepository->deleteEntitiesForObject((int) $event->getLead()->deletedId, MauticSyncDataExchange::OBJECT_CONTACT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IntegrationNotFoundException
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public function onCompanyPostSave(Events\CompanyEvent $event): void
|
||||
{
|
||||
if (defined('MAUTIC_INTEGRATION_SYNC_IN_PROGRESS')) {
|
||||
// Don't track changes just made by an active sync
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->syncIntegrationsHelper->hasObjectSyncEnabled(MauticSyncDataExchange::OBJECT_COMPANY)) {
|
||||
// Only track if an integration is syncing with companies
|
||||
return;
|
||||
}
|
||||
|
||||
$company = $event->getCompany();
|
||||
$changes = $company->getChanges(true);
|
||||
|
||||
if (!empty($changes['owner'])) {
|
||||
// Force record of owner change if present in changelist
|
||||
$changes['fields']['owner_id'] = $changes['owner'];
|
||||
}
|
||||
|
||||
if (!isset($changes['fields'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->recordFieldChanges($changes['fields'], $company->getId(), Company::class, $company);
|
||||
}
|
||||
|
||||
public function onCompanyPostDelete(Events\CompanyEvent $event): void
|
||||
{
|
||||
$this->fieldChangeRepo->deleteEntitiesForObject((int) $event->getCompany()->deletedId, Company::class);
|
||||
$this->objectMappingRepository->deleteEntitiesForObject((int) $event->getCompany()->deletedId, MauticSyncDataExchange::OBJECT_COMPANY);
|
||||
}
|
||||
|
||||
public function onLeadCompanyChange(Events\LeadChangeCompanyEvent $event): void
|
||||
{
|
||||
$lead = $event->getLead();
|
||||
|
||||
// This mechanism is not able to record multiple company changes.
|
||||
$changes['company'] = [
|
||||
0 => '',
|
||||
1 => $lead->getCompany(),
|
||||
];
|
||||
|
||||
$this->recordFieldChanges($changes, $lead->getId(), Lead::class, $lead);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $objectId
|
||||
*
|
||||
* @throws IntegrationNotFoundException
|
||||
*/
|
||||
private function recordFieldChanges(array $fieldChanges, $objectId, string $objectType, object $object): void
|
||||
{
|
||||
$toPersist = [];
|
||||
$changedFields = [];
|
||||
$objectId = (int) $objectId;
|
||||
|
||||
foreach ($this->syncIntegrationsHelper->getEnabledIntegrations() as $integrationName) {
|
||||
try {
|
||||
$this->dispatchBeforeFieldChangesEvent($integrationName, $object);
|
||||
} catch (InvalidValueException) {
|
||||
continue; // Do not record changes for object and integration that has an invalid value.
|
||||
}
|
||||
|
||||
foreach ($fieldChanges as $key => [$oldValue, $newValue]) {
|
||||
$valueDAO = $this->variableExpressor->encodeVariable($newValue);
|
||||
$changedFields[] = $key;
|
||||
$fieldChangeEntity = (new FieldChange())
|
||||
->setObjectType($objectType)
|
||||
->setObjectId($objectId)
|
||||
->setModifiedAt(new \DateTime())
|
||||
->setColumnName($key)
|
||||
->setColumnType($valueDAO->getType())
|
||||
->setColumnValue($valueDAO->getValue())
|
||||
->setIntegration($integrationName);
|
||||
|
||||
$toPersist[] = $fieldChangeEntity;
|
||||
}
|
||||
}
|
||||
|
||||
$this->fieldChangeRepo->deleteEntitiesForObjectByColumnName($objectId, $objectType, $changedFields);
|
||||
$this->fieldChangeRepo->saveEntities($toPersist);
|
||||
$this->fieldChangeRepo->detachEntities($toPersist);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidValueException
|
||||
*/
|
||||
private function dispatchBeforeFieldChangesEvent(string $integrationName, object $object): void
|
||||
{
|
||||
if ($object instanceof Lead) {
|
||||
if ($this->dispatcher->hasListeners(IntegrationEvents::INTEGRATION_BEFORE_CONTACT_FIELD_CHANGES)) {
|
||||
$this->dispatcher->dispatch(
|
||||
new InternalContactEvent($integrationName, $object),
|
||||
IntegrationEvents::INTEGRATION_BEFORE_CONTACT_FIELD_CHANGES
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($object instanceof Company) {
|
||||
if ($this->dispatcher->hasListeners(IntegrationEvents::INTEGRATION_BEFORE_COMPANY_FIELD_CHANGES)) {
|
||||
$this->dispatcher->dispatch(
|
||||
new InternalCompanyEvent($integrationName, $object),
|
||||
IntegrationEvents::INTEGRATION_BEFORE_COMPANY_FIELD_CHANGES
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidValueException('An object type should be specified. None matches.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\EventListener;
|
||||
|
||||
use Mautic\LeadBundle\Entity\LeadEventLogRepository;
|
||||
use Mautic\LeadBundle\Event\LeadTimelineEvent;
|
||||
use Mautic\LeadBundle\LeadEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class TimelineSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private LeadEventLogRepository $eventLogRepository,
|
||||
private TranslatorInterface $translator,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
LeadEvents::TIMELINE_ON_GENERATE => ['onTimelineGenerate', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function onTimelineGenerate(LeadTimelineEvent $event): void
|
||||
{
|
||||
$eventType = 'integration_sync_issues';
|
||||
$eventTypeName = $this->translator->trans('mautic.integration.sync.timeline_notices');
|
||||
$event->addEventType($eventType, $eventTypeName);
|
||||
|
||||
if (!$event->isApplicable($eventType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$events = $this->eventLogRepository->getEvents($event->getLead(), 'integrations', null, 'sync', $event->getQueryOptions());
|
||||
|
||||
// Add to counter
|
||||
$event->addToCounter($eventType, $events);
|
||||
|
||||
if ($event->isEngagementCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the logs to the event array
|
||||
foreach ($events['results'] as $log) {
|
||||
$event->addEvent(
|
||||
$this->getEventEntry($log, $eventType, $eventTypeName)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $log
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function getEventEntry(array $log, string $eventType, string $eventTypeName): array
|
||||
{
|
||||
$properties = json_decode($log['properties'], true);
|
||||
|
||||
return [
|
||||
'event' => $eventType,
|
||||
'eventId' => $eventType.$log['id'],
|
||||
'eventType' => $eventTypeName,
|
||||
'eventLabel' => $this->translator->trans(
|
||||
'mautic.integration.sync.user_notification.header',
|
||||
[
|
||||
'%integration%' => $properties['integration'],
|
||||
'%object%' => $properties['object'],
|
||||
]
|
||||
),
|
||||
'timestamp' => $log['date_added'],
|
||||
'icon' => 'ri-refresh-line',
|
||||
'contactId' => $log['lead_id'],
|
||||
'contentTemplate' => '@Integrations/Timeline/index.html.twig',
|
||||
'extra' => $properties,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\EventListener;
|
||||
|
||||
use Mautic\CoreBundle\CoreEvents;
|
||||
use Mautic\CoreBundle\Event\CustomTemplateEvent;
|
||||
use Mautic\IntegrationsBundle\Entity\ObjectMappingRepository;
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class UIContactIntegrationsTabSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ObjectMappingRepository $objectMappingRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
CoreEvents::VIEW_INJECT_CUSTOM_TEMPLATE => ['onTemplateRender', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function onTemplateRender(CustomTemplateEvent $event): void
|
||||
{
|
||||
if ('@MauticLead/Lead/lead.html.twig' === $event->getTemplate()) {
|
||||
$vars = $event->getVars();
|
||||
$integrations = $vars['integrations'];
|
||||
|
||||
/** @var Lead $contact */
|
||||
$contact = $vars['lead'];
|
||||
|
||||
$objectMappings = $this->objectMappingRepository->getIntegrationMappingsForInternalObject(
|
||||
Contact::NAME,
|
||||
(int) $contact->getId()
|
||||
);
|
||||
|
||||
foreach ($objectMappings as $objectMapping) {
|
||||
$integrations[] = [
|
||||
'integration' => $objectMapping->getIntegration(),
|
||||
'integration_entity' => $objectMapping->getIntegrationObjectName(),
|
||||
'integration_entity_id' => $objectMapping->getIntegrationObjectId(),
|
||||
'date_added' => $objectMapping->getDateCreated(),
|
||||
'last_sync_date' => $objectMapping->getLastSyncDate(),
|
||||
];
|
||||
}
|
||||
|
||||
$vars['integrations'] = $integrations;
|
||||
|
||||
$event->setVars($vars);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Exception;
|
||||
|
||||
class IntegrationMappingException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Exception;
|
||||
|
||||
class IntegrationNotFoundException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Exception;
|
||||
|
||||
class IntegrationNotSetException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Exception;
|
||||
|
||||
class InvalidCredentialsException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Exception;
|
||||
|
||||
class InvalidFormOptionException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Exception;
|
||||
|
||||
class InvalidProviderException extends \Exception
|
||||
{
|
||||
public function __construct($provider, $code = 0, ?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct(sprintf('The requested auth provider (%s) has not been registered.', $provider), $code, $previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Exception;
|
||||
|
||||
class InvalidValueException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Exception;
|
||||
|
||||
class PathNotFoundException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Exception;
|
||||
|
||||
class PluginNotConfiguredException extends \Exception
|
||||
{
|
||||
protected $message = 'mautic.integration.not_configured';
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Exception;
|
||||
|
||||
class RequiredValueException extends InvalidValueException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Exception;
|
||||
|
||||
class UnauthorizedException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Exception;
|
||||
|
||||
class UnexpectedValueException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Facade;
|
||||
|
||||
use Mautic\CoreBundle\Helper\EncryptionHelper;
|
||||
|
||||
class EncryptionService
|
||||
{
|
||||
public function __construct(
|
||||
private EncryptionHelper $encryptionHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $keys
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public function encrypt($keys)
|
||||
{
|
||||
if (!is_array($keys)) {
|
||||
return $this->encryptionHelper->encrypt($keys);
|
||||
}
|
||||
|
||||
foreach ($keys as $name => $key) {
|
||||
$keys[$name] = $this->encryptionHelper->encrypt($key);
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $onlyPrimaryCipher
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public function decrypt($keys, $onlyPrimaryCipher = false)
|
||||
{
|
||||
if (!is_array($keys)) {
|
||||
return $this->encryptionHelper->decrypt($keys, $onlyPrimaryCipher);
|
||||
}
|
||||
|
||||
foreach ($keys as $name => $key) {
|
||||
$keys[$name] = $this->encryptionHelper->decrypt($key, $onlyPrimaryCipher);
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Form\Type;
|
||||
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<mixed>
|
||||
*/
|
||||
class ActivityListType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private LeadModel $leadModel,
|
||||
) {
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults(
|
||||
[
|
||||
'choices' => array_flip($this->leadModel->getEngagementTypes()),
|
||||
'label' => 'mautic.integration.feature.push_activity.included_events',
|
||||
'label_attr' => [
|
||||
'class' => 'control-label',
|
||||
'tooltip' => 'mautic.integration.feature.push_activity.included_events.tooltip',
|
||||
],
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getParent(): ?string
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Form\Type;
|
||||
|
||||
use Mautic\CoreBundle\Form\Type\FormButtonsType;
|
||||
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
|
||||
use Mautic\IntegrationsBundle\Exception\IntegrationNotFoundException;
|
||||
use Mautic\IntegrationsBundle\Helper\ConfigIntegrationsHelper;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormAuthInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeaturesInterface;
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<Integration>
|
||||
*/
|
||||
class IntegrationConfigType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private ConfigIntegrationsHelper $integrationsHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IntegrationNotFoundException
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$integrationObject = $this->integrationsHelper->getIntegration($options['integration']);
|
||||
|
||||
// isPublished
|
||||
$builder->add(
|
||||
'isPublished',
|
||||
YesNoButtonGroupType::class,
|
||||
[
|
||||
'label' => 'mautic.integration.enabled',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
]
|
||||
);
|
||||
|
||||
// apiKeys
|
||||
if ($integrationObject instanceof ConfigFormAuthInterface) {
|
||||
$builder->add(
|
||||
'apiKeys',
|
||||
$integrationObject->getAuthConfigFormName(),
|
||||
[
|
||||
'label' => false,
|
||||
'integration' => $integrationObject,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// supportedFeatures
|
||||
if ($integrationObject instanceof ConfigFormFeaturesInterface) {
|
||||
// @todo add tooltip support
|
||||
$builder->add(
|
||||
'supportedFeatures',
|
||||
ChoiceType::class,
|
||||
[
|
||||
'label' => 'mautic.integration.features',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'choices' => array_flip($integrationObject->getSupportedFeatures()),
|
||||
'expanded' => true,
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// featureSettings
|
||||
$builder->add(
|
||||
'featureSettings',
|
||||
IntegrationFeatureSettingsType::class,
|
||||
[
|
||||
'label' => false,
|
||||
'integrationObject' => $integrationObject,
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add('buttons', FormButtonsType::class);
|
||||
|
||||
$builder->setAction($options['action']);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setRequired(
|
||||
[
|
||||
'integration',
|
||||
]
|
||||
);
|
||||
|
||||
$resolver->setDefined(
|
||||
[
|
||||
'data_class' => Integration::class,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Form\Type;
|
||||
|
||||
use Mautic\IntegrationsBundle\Exception\IntegrationNotFoundException;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeatureSettingsInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<mixed>
|
||||
*/
|
||||
class IntegrationFeatureSettingsType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @throws IntegrationNotFoundException
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$integrationObject = $options['integrationObject'];
|
||||
if (!$integrationObject instanceof IntegrationInterface) {
|
||||
throw new IntegrationNotFoundException("{$options['integrationObject']} is not recognized");
|
||||
}
|
||||
|
||||
if ($integrationObject instanceof ConfigFormFeatureSettingsInterface) {
|
||||
$builder->add(
|
||||
'integration',
|
||||
$integrationObject->getFeatureSettingsConfigFormName(),
|
||||
[
|
||||
'label' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ($integrationObject instanceof ConfigFormSyncInterface) {
|
||||
$builder->add(
|
||||
'sync',
|
||||
IntegrationSyncSettingsType::class,
|
||||
[
|
||||
'label' => false,
|
||||
'integrationObject' => $integrationObject,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setRequired(
|
||||
[
|
||||
'integrationObject',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Form\Type;
|
||||
|
||||
use Mautic\IntegrationsBundle\Exception\InvalidFormOptionException;
|
||||
use Mautic\IntegrationsBundle\Helper\FieldFilterHelper;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<mixed>
|
||||
*/
|
||||
class IntegrationSyncSettingsFieldMappingsType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private LoggerInterface $logger,
|
||||
private TranslatorInterface $translator,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidFormOptionException
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
if (!is_array($options['objects'])) {
|
||||
throw new InvalidFormOptionException('objects must be an array');
|
||||
}
|
||||
|
||||
$integrationObject = $options['integrationObject'];
|
||||
\assert($integrationObject instanceof ConfigFormSyncInterface);
|
||||
|
||||
$fieldFilterHelper = new FieldFilterHelper($integrationObject);
|
||||
|
||||
foreach ($options['objects'] as $objectName => $objectLabel) {
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($integrationObject, $objectName, $fieldFilterHelper): void {
|
||||
$error = null;
|
||||
|
||||
try {
|
||||
$fieldFilterHelper->filterFieldsByPage($objectName, 1);
|
||||
} catch (\Throwable $exception) {
|
||||
$this->logger->debug($exception->getMessage(), ['exception' => $exception]);
|
||||
|
||||
$error = $this->translator->trans($exception->getMessage());
|
||||
}
|
||||
|
||||
$form = $event->getForm();
|
||||
$form->add(
|
||||
$objectName,
|
||||
IntegrationSyncSettingsObjectFieldMappingType::class,
|
||||
[
|
||||
'label' => false,
|
||||
'integrationFields' => $fieldFilterHelper->getFilteredFields(),
|
||||
'page' => 1,
|
||||
'keyword' => null,
|
||||
'totalFieldCount' => $fieldFilterHelper->getTotalFieldCount(),
|
||||
'object' => $objectName,
|
||||
'integrationObject' => $integrationObject,
|
||||
'error_bubbling' => false,
|
||||
'allow_extra_fields' => true,
|
||||
]
|
||||
);
|
||||
|
||||
if ($error) {
|
||||
$form[$objectName]->addError(new FormError($error));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setRequired(
|
||||
[
|
||||
'integrationObject',
|
||||
'objects',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\IntegrationsBundle\Form\Type;
|
||||
|
||||
use Mautic\IntegrationsBundle\Exception\InvalidFormOptionException;
|
||||
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface;
|
||||
use Mautic\IntegrationsBundle\Mapping\MappedFieldInfoInterface;
|
||||
use Mautic\IntegrationsBundle\Sync\Exception\ObjectNotFoundException;
|
||||
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Helper\FieldHelper;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<mixed>
|
||||
*/
|
||||
class IntegrationSyncSettingsObjectFieldMappingType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private TranslatorInterface $translator,
|
||||
private FieldHelper $fieldHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidFormOptionException
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$integrationFields = $options['integrationFields'];
|
||||
$integrationObject = $options['integrationObject'];
|
||||
\assert($integrationObject instanceof ConfigFormSyncInterface);
|
||||
|
||||
$objectName = $options['object'];
|
||||
foreach ($integrationFields as $fieldName => $fieldInfo) {
|
||||
if (!$fieldInfo instanceof MappedFieldInfoInterface) {
|
||||
throw new InvalidFormOptionException('integrationFields must contain an instance of MappedFieldInfoInterface');
|
||||
}
|
||||
|
||||
$attr = [
|
||||
'label' => $fieldInfo->getLabel(),
|
||||
'mauticFields' => $this->getMauticFields($integrationObject, $objectName),
|
||||
'required' => $fieldInfo->showAsRequired(),
|
||||
'placeholder' => $this->translator->trans('mautic.integration.sync_mautic_field'),
|
||||
'object' => $objectName,
|
||||
'integration' => $integrationObject->getName(),
|
||||
'field' => $fieldInfo,
|
||||
];
|
||||
|
||||
if ($fieldInfo->hasTooltip()) {
|
||||
$attr['attr'] = [
|
||||
'tooltip' => $fieldInfo->getTooltip(),
|
||||
'class' => 'form-control',
|
||||
];
|
||||
}
|
||||
|
||||
$builder->add(
|
||||
$fieldName,
|
||||
IntegrationSyncSettingsObjectFieldType::class,
|
||||
$attr
|
||||
);
|
||||
}
|
||||
|
||||
$builder->add(
|
||||
'filter-keyword',
|
||||
TextType::class,
|
||||
[
|
||||
'label' => false,
|
||||
'mapped' => false,
|
||||
'data' => $options['keyword'],
|
||||
'attr' => [
|
||||
'class' => 'form-control integration-keyword-filter',
|
||||
'placeholder' => $this->translator->trans('mautic.integration.sync_filter_fields'),
|
||||
'data-object' => $objectName,
|
||||
'data-integration' => $integrationObject->getName(),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'filter-totalFieldCount',
|
||||
HiddenType::class,
|
||||
[
|
||||
'label' => false,
|
||||
'mapped' => false,
|
||||
'data' => $options['totalFieldCount'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setRequired(
|
||||
[
|
||||
'integrationFields',
|
||||
'page',
|
||||
'keyword',
|
||||
'totalFieldCount',
|
||||
'object',
|
||||
'integrationObject',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
private function getMauticFields(ConfigFormSyncInterface $integrationObject, string $objectName): array
|
||||
{
|
||||
$mappedObjects = $integrationObject->getSyncMappedObjects();
|
||||
if (!isset($mappedObjects[$objectName])) {
|
||||
throw new ObjectNotFoundException($objectName);
|
||||
}
|
||||
|
||||
$mauticObject = $mappedObjects[$objectName];
|
||||
|
||||
return $this->fieldHelper->getSyncFields($mauticObject);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user