From 2d1d70d093917fd554da7a0935d4de5fe3f29b4d Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Fri, 14 Oct 2022 10:45:49 +0530 Subject: [PATCH 01/29] Documentation for plugins configuration. --- docs/index.rst | 1 + docs/plugins/integrations_configuration.rst | 112 ++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 docs/plugins/integrations_configuration.rst diff --git a/docs/index.rst b/docs/index.rst index dd33ac02..08ba287c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -55,6 +55,7 @@ This is a work in progress. More to come soon. In the meantime, go to :xref:`Leg plugins/installation plugins/data plugins/translations + plugins/integrations_configuration .. toctree:: :maxdepth: 2 diff --git a/docs/plugins/integrations_configuration.rst b/docs/plugins/integrations_configuration.rst new file mode 100644 index 00000000..6666b19e --- /dev/null +++ b/docs/plugins/integrations_configuration.rst @@ -0,0 +1,112 @@ +Integration Configuration +######################### + +The integration plugin provides interfaces to display and store configuration options that can be accessed through the ``\Mautic\PluginBundle\Entity\Integration`` object. + +Registering the Integration for Configuration +============================================= + +To tell the IntegrationsBundle that this integration has configuration options, tag the integration or support class with ``mautic.config_integration`` in the plugin's ``app/config.php``. + +.. code-block:: php + + [ + // ... + 'integrations' => [ + // ... + 'helloworld.integration.configuration' => [ + 'class' => \MauticPlugin\HelloWorldBundle\Integration\Support\ConfigSupport::class, + 'tags' => [ + 'mautic.config_integration', + ], + ], + // ... + ], + // ... + ], + // ... + ]; + +The ``ConfigSupport`` class must implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormInterface``. + +.. code-block:: php + + get(HelloWorldIntegration::NAME)->getIntegrationConfiguration()->getApiKeys(); + $username = $apiKeys['username']; + + +``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormCallbackInterface`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +If the integration leverages an auth provider that requires a callback URL or something similar, this interface provides a means to return a translation string to display in the UI. For example, OAuth2 requires a redirect URI. If the admin has to configure the OAuth credentials in the 3rd party service and needs to know what URL to use in Mautic as the return URI, or callback URL, use the ``getCallbackHelpMessageTranslationKey()`` method. + +Feature Interfaces +------------------ + +``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeatureSettingsInterface`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +This interface provides the Symfony form type class that defines the fields to be displayed on the Features tab. These values are not encrypted. + +.. code-block:: php + get(HelloWorldIntegration::NAME)->getIntegrationConfiguration()->getFeatureSettings(); + $doSomething = $featureSettings['doSomething']; + + +``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeaturesInterface`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Currently the integrations bundle provides default features. To use these features, implement this interface. ``getSupportedFeatures`` returns an array of supported features. For example, if the Integration syncs with Mautic Contacts, ``getSupportedFeatures()`` could ``return [ConfigFormFeaturesInterface::FEATURE_SYNC];``. + +Contact/Company Syncing Interfaces +---------------------------------- +The integrations bundle provides a sync framework for third party services to sync with Mautic's Contacts and Companies. The ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface`` determines the configuration options for this sync feature. Refer to the method docblocks in the interface for more details. From 9cd9730fd5775cae0e14dc1e90171fcd9cac9e66 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Sun, 16 Oct 2022 12:03:42 +0530 Subject: [PATCH 02/29] Added integration related configurations. --- docs/index.rst | 2 +- docs/plugins/integrations/authentication.rst | 727 ++++++++++++++++++ docs/plugins/integrations/builder.rst | 42 + .../configuration.rst} | 45 +- docs/plugins/integrations/index.rst | 16 + docs/plugins/integrations/integrations.rst | 107 +++ docs/plugins/integrations/migrations.rst | 72 ++ docs/plugins/integrations/sync.rst | 77 ++ 8 files changed, 1068 insertions(+), 20 deletions(-) create mode 100644 docs/plugins/integrations/authentication.rst create mode 100644 docs/plugins/integrations/builder.rst rename docs/plugins/{integrations_configuration.rst => integrations/configuration.rst} (63%) create mode 100644 docs/plugins/integrations/index.rst create mode 100644 docs/plugins/integrations/integrations.rst create mode 100644 docs/plugins/integrations/migrations.rst create mode 100644 docs/plugins/integrations/sync.rst diff --git a/docs/index.rst b/docs/index.rst index 08ba287c..433058ab 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -55,7 +55,7 @@ This is a work in progress. More to come soon. In the meantime, go to :xref:`Leg plugins/installation plugins/data plugins/translations - plugins/integrations_configuration + plugins/integrations/index .. toctree:: :maxdepth: 2 diff --git a/docs/plugins/integrations/authentication.rst b/docs/plugins/integrations/authentication.rst new file mode 100644 index 00000000..e95e66e3 --- /dev/null +++ b/docs/plugins/integrations/authentication.rst @@ -0,0 +1,727 @@ +************************** +Integration Authentication +************************** + +.. contents:: Table of Contents + +The integrations bundle provides factories and helpers to create Guzzle Client classes for common authentication protocols. + +---------- + +Registering the Integration for Authentication +============================================== +If the integration requires the user to authenticate through the web (OAuth2 three legged), the integration needs to tag a service with `mautic.auth_integration` to handle the authentication process (redirecting to login, request the access token, etc). This service will need to implement `\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface`. + +.. code-block:: php + + [ + // ... + 'integrations' => [ + // ... + 'helloworld.integration.authentication' => [ + 'class' => \MauticPlugin\HelloWorldBundle\Integration\Support\AuthSupport::class, + 'tags' => [ + 'mautic.auth_integration', + ], + ], + // ... + ], + // ... + ], + // ... + ]; + + +The `AuthSupport` class must implement `\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface`. + +.. code-block:: php + + client = $client; + } + + public function getName(): string + { + return HelloWorldIntegration::NAME; + } + + public function getDisplayName(): string + { + return 'Hello World'; + } + + /** + * Returns true if the integration has already been authorized with the 3rd party service. + * + * @return bool + */ + public function isAuthenticated(): bool + { + $apiKeys = $this->getIntegrationConfiguration()->getApiKeys(); + + return !empty($apiKeys['access_token']) && !empty($apiKeys['refresh_token']); + } + + /** + * Authenticate and obtain the access token + * + * @param Request $request + * + * @return string + */ + public function authenticateIntegration(Request $request): string + { + $code = $request->query->get('code'); + + $this->client->authenticate($code); + + return 'Success!'; + } + } + +Authentication Providers +------------------------ +The integrations bundle comes with a number of popular authentication protocols available to use as Guzzle clients. New ones can be created by implementing `\Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface.` + +**The examples below use anonymous classes. Of course, use OOP with services and factories to generate credential, configuration, and client classes.** The best way to get configuration values such as username, password, consumer key, consumer secret, etc is by using the `mautic.integrations.helper` (`\Mautic\IntegrationsBundle\Helper\IntegrationsHelper`) service in order to leverage the configuration stored in the `Integration` entity's api keys. + +.. code-block:: php + + getIntegration(HelloWorldIntegration::NAME); + + $configuration = $integration->getIntegrationConfiguration(); + $apiKeys = $configuration->getApiKeys(); + + $username = $apiKeys['username'] ?? null; + $password = $apiKeys['password'] ?? null; + + //... + + +Api Key +^^^^^^^ +Use the `mautic.integrations.auth_provider.api_key` service (`\Mautic\IntegrationsBundle\Auth\Provider\ApiKey\HttpFactory`) to obtain a `GuzzleHttp\ClientInterface` that uses an API key for all requests. Out of the box, the factory supports a parameter API key or a header API key. + +Parameter Based API Key +""""""""""""""""""""""" + +To use the parameter based API key, create a credentials class that implements `\Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\ParameterCredentialsInterface`. + +.. code-block:: php + + getIntegration(HelloWorldIntegration::NAME); + + $apiKeys = $integration->getIntegrationConfiguration()->getApiKeys(); + + $credentials = new class($apiKeys['api_key']) implements ParameterCredentialsInterface { + private $key; + + public function __construct(string $key) + { + $this->key = $key; + } + + public function getKeyName(): string + { + return 'apikey'; + } + + public function getApiKey(): string + { + return $this->key; + } + }; + + /** @var $factory HttpFactory */ + $client = $factory->getClient($credentials); + $response = $client->get('https://api-url.com/fetch'); + + +Header Based API Key +"""""""""""""""""""" +.. code-block:: php + + getIntegration(HelloWorldIntegration::NAME); + + $apiKeys = $integration->getIntegrationConfiguration()->getApiKeys(); + + $credentials = new class($apiKeys['api_key']) implements HeaderCredentialsInterface { + private $key; + + public function __construct(string $key) + { + $this->key = $key; + } + + public function getKeyName(): string + { + return 'X-API-KEY'; + } + + public function getApiKey(): string + { + return $this->key; + } + }; + + /** @var $factory HttpFactory */ + $client = $factory->getClient($credentials); + $response = $client->get('https://api-url.com/fetch'); + + +Basic Auth +^^^^^^^^^^ +Use the `mautic.integrations.auth_provider.basic_auth` service (`\Mautic\IntegrationsBundle\Auth\Provider\BasicAuth\HttpFactory`) to obtain a `GuzzleHttp\ClientInterface` that uses basic auth for all requests. + +.. code-block:: php + + getIntegration(HelloWorldIntegration::NAME); + + $configuration = $integration->getIntegrationConfiguration(); + $apiKeys = $configuration->getApiKeys(); + + $credentials = new class($apiKeys['username'], $apiKeys['password']) implements CredentialsInterface { + private $username; + private $password; + + public function __construct(string $username, string $password) + { + $this->username = $username; + $this->password = $password; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getPassword(): string + { + return $this->password; + } + }; + + /** @var $factory HttpFactory */ + $client = $factory->getClient($credentials); + $response = $client->get('https://api-url.com/fetch'); + + +OAuth1a +^^^^^^^ +OAuth1a Three Legged +"""""""""""""""""""" + +This has not been implemented yet. + +OAuth1a Two Legged +"""""""""""""""""" +OAuth1A two legged does not require a user to login as would three legged. + +.. code-block:: php + + getIntegration(HelloWorldIntegration::NAME); + + $configuration = $integration->getIntegrationConfiguration(); + $apiKeys = $configuration->getApiKeys(); + + $credentials = new class( + 'https://api-url.com/oauth/token', + $apiKeys['consumer_key'], + $apiKeys['consumer_secret'] + ) implements CredentialsInterface { + private $authUrl; + private $consumerKey; + private $consumerSecret; + + public function __construct(string $authUrl, string $consumerKey, string $consumerSecret) + { + $this->authUrl = $authUrl; + $this->consumerKey = $consumerKey; + $this->consumerSecret = $consumerSecret; + } + + public function getAuthUrl(): string + { + return $this->authUrl; + } + + public function getConsumerKey(): ?string + { + return $this->consumerKey; + } + + public function getConsumerSecret(): ?string + { + return $this->consumerSecret; + } + + /** + * Not used in this example. Tsk tsk for breaking the interface segregation principle + * + * @return string|null + */ + public function getToken(): ?string + { + return null; + } + + /** + * Not used in this example. Tsk tsk for breaking the interface segregation principle + * + * @return string|null + */ + public function getTokenSecret(): ?string + { + return null; + } + }; + + /** @var $factory HttpFactory */ + $client = $factory->getClient($credentials); + $response = $client->get('https://api-url.com/fetch'); + +OAuth2 +^^^^^^ +Use the OAuth2 factory according to the grant type required. `\Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\HttpFactory` supports `code` and `refresh_token` grant types. `\Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\HttpFactory` supports `client_credentials` and `password`. + +The OAuth2 factories leverages [https://github.com/kamermans/guzzle-oauth2-subscriber] as a middleware. + +Client Configuration +"""""""""""""""""""" +Both OAuth2 factories leverage `\Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface` object to configure things such as configuring the signer (basic auth, post form data, custom), token factory, token persistence, and token signer (bearer auth, basic auth, query string, custom). Use the appropriate interfaces as required for the use case (see the interfaces in `plugins/IntegrationsBundle/Auth/Support/Oauth2/ConfigAccess`). + +See [https://github.com/kamermans/guzzle-oauth2-subscriber] for additional details on configuring the credentials and token signers or creating custom token persistence and factories. + +Integration Token Persistence +""""""""""""""""""""""""""""" +For most use cases, a token persistence service to fetch and store the access tokens generated by using refresh tokens, etc will be required. The integrations bundle provides one that natively uses the `\Mautic\PluginBundle\Entity\Integration` entity's api keys. Anything stored through the service is automatically encrypted. + +Use the `mautic.integrations.auth_provider.token_persistence_factory` service (`\Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\TokenPersistenceFactory`) to generate a `TokenFactoryInterface` to be returned by the `\Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenFactoryInterface` interface. + +.. code-block:: php + + getIntegration(HelloWorldIntegration::NAME); + + /** @var $tokenPersistenceFactory TokenPersistenceFactory */ + $tokenPersistence = $tokenPersistenceFactory->create($integration); + + $config = new class($tokenPersistence) implements ConfigTokenPersistenceInterface { + private $tokenPersistence; + + public function __construct(TokenPersistenceInterface$tokenPersistence) + { + $this->tokenPersistence = $tokenPersistence; + } + + public function getTokenPersistence(): TokenPersistenceInterface + { + return $this->tokenPersistence; + } + }; + +The token persistence service will automatically manage `access_token`, `refresh_token`, and `expires_at` from the authentication process which are stored in the `Integration` entity's api keys array. + +Token Factory +""""""""""""" +In some cases, the 3rd party service may return additional values that are not traditionally part of the oauth2 spec and these values are required for further communication with the api service. In this case, the integration bundle's `\Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\IntegrationTokenFactory` can be used to capture those extra values and store them in the `Integration` entity's api keys array. + +The `IntegrationTokenFactory` can then be returned in a `\Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenFactoryInterface` when configuring the `Client`. + +.. code-block:: php + + tokenFactory = $tokenFactory; + } + + public function getTokenFactory(): TokenFactoryInterface + { + return $this->tokenFactory; + } + }; + +OAuth2 Two Legged +^^^^^^^^^^^^^^^^^ + +Password Grant +"""""""""""""" +Below is an example of the password grant for a service that uses a scope (optional interface). The use of the token persistence is assuming the access token is valid for a period of time (i.e. an hour). + +.. code-block:: php + + getIntegration(HelloWorldIntegration::NAME); + + $configuration = $integration->getIntegrationConfiguration(); + $apiKeys = $configuration->getApiKeys(); + + $credentials = new class( + 'https://api-url.com/oauth/token', + 'scope1,scope2', + $apiKeys['client_id'], + $apiKeys['client_secret'], + $apiKeys['username'], + $apiKeys['password'] + ) implements PasswordCredentialsGrantInterface, ScopeInterface { + private $authorizeUrl; + private $scope; + private $clientId; + private $clientSecret; + private $username; + private $password; + + public function getAuthorizationUrl(): string + { + return $this->authorizeUrl; + } + + public function getClientId(): ?string + { + return $this->clientId; + } + + public function getClientSecret(): ?string + { + return $this->clientSecret; + } + + public function getPassword(): ?string + { + return $this->password; + } + + public function getUsername(): ?string + { + return $this->username; + } + + public function getScope(): ?string + { + return $this->scope; + } + }; + + /** @var $tokenPersistenceFactory TokenPersistenceFactory */ + $tokenPersistence = $tokenPersistenceFactory->create($integration); + $config = new class($tokenPersistence) implements ConfigTokenPersistenceInterface { + private $tokenPersistence; + + public function __construct(TokenPersistenceInterface$tokenPersistence) + { + $this->tokenPersistence = $tokenPersistence; + } + + public function getTokenPersistence(): TokenPersistenceInterface + { + return $this->tokenPersistence; + } + }; + + /** @var $factory HttpFactory */ + $client = $factory->getClient($credentials, $config); + $response = $client->get('https://api-url.com/fetch'); + +Client Credentials Grant +""""""""""""""""""""""" +Below is an example of the client credentials grant for a service that uses a scope (optional interface). The use of the token persistence is assuming the access token is valid for a period of time (i.e. an hour). + +.. code-block:: php + + getIntegration(HelloWorldIntegration::NAME); + + $configuration = $integration->getIntegrationConfiguration(); + $apiKeys = $configuration->getApiKeys(); + + $credentials = new class( + 'https://api-url.com/oauth/token', + 'scope1,scope2', + $apiKeys['client_id'], + $apiKeys['client_secret'] + ) implements ClientCredentialsGrantInterface, ScopeInterface { + private $authorizeUrl; + private $scope; + private $clientId; + private $clientSecret; + + public function getAuthorizationUrl(): string + { + return $this->authorizeUrl; + } + + public function getClientId(): ?string + { + return $this->clientId; + } + + public function getClientSecret(): ?string + { + return $this->clientSecret; + } + + public function getScope(): ?string + { + return $this->scope; + } + }; + + /** @var $tokenPersistenceFactory TokenPersistenceFactory */ + $tokenPersistence = $tokenPersistenceFactory->create($integration); + $config = new class($tokenPersistence) implements ConfigTokenPersistenceInterface { + private $tokenPersistence; + + public function __construct(TokenPersistenceInterface$tokenPersistence) + { + $this->tokenPersistence = $tokenPersistence; + } + + public function getTokenPersistence(): TokenPersistenceInterface + { + return $this->tokenPersistence; + } + }; + + /** @var $factory HttpFactory */ + $client = $factory->getClient($credentials, $config); + $response = $client->get('https://api-url.com/fetch'); + +OAuth2 Three Legged +^^^^^^^^^^^^^^^^^^^ +Three legged OAuth2 with the code grant is the most complex to implement because it involves redirecting the user to the 3rd party service to authenticate then sent back to Mautic to initiate the access token process using a code returned in the request. + +The first step is to register the integration as a [`\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface`](#registering-the-integration-for-authentication). The `authenticateIntegration()` method will be used to initiate the access token process using the `code` returned in the request after the user logs into the 3rd party service. The integrations bundle provides a route that can be used as the redirect or callback URIs through the named route `mautic_integration_public_callback` that requires a `integration` parameter. This redirect URI can be displayed in the UI by using [`ConfigFormCallbackInterface`](https://github.com/mautic/plugin-integrations/wiki/2.-Integration-Configuration#mauticpluginintegrationsbundleintegrationinterfacesconfigformcallbackinterface). This route will find the integration by name from the `AuthIntegrationsHelper` then execute its `authenticateIntegration()`. + +.. code-block:: php + + query->get('code'); + + $this->client->authenticate($code); + + return new Response('OK!'); + } + } + +The trick here is that the `Client`'s `authenticate` method will configure a `ClientInterface` then make a call to any valid API url (*this is required*). By making a call, the middleware will initiate the access token process and store it in the `Integration` entity's api keys through the [`TokenPersistenceFactory`](#integration-token-persistence). The URL is recommended to be something simple like a version check or fetching info for the authenticated user. + +Here is an example of a client, assuming that the user has already logged in and the code is in the request. + +.. code-block:: php + + getIntegration(HelloWorldIntegration::NAME); + + /** @var Router $router */ + $redirectUrl = $router->generate('mautic_integration_public_callback', ['integration' => HelloWorldIntegration::NAME]); + + $configuration = $integration->getIntegrationConfiguration(); + $apiKeys = $configuration->getApiKeys(); + + /** @var Request $request */ + $code = $request->get('code'); + + $credentials = new class( + 'https://api-url.com/oauth/authorize', + 'https://api-url.com/oauth/token', + $redirectUrl, + 'scope1,scope2', + $apiKeys['client_id'], + $apiKeys['client_secret'], + $code + ) implements CredentialsInterface, RedirectUriInterface, ScopeInterface, CodeInterface { + private $authorizeUrl; + private $tokenUrl; + private $redirectUrl; + private $scope; + private $clientId; + private $clientSecret; + private $code; + + public function __construct(string $authorizeUrl, string $tokenUrl, string $redirectUrl, string $scope, string $clientId, string $clientSecret, ?string $code) + { + $this->authorizeUrl = $authorizeUrl; + $this->tokenUrl = $tokenUrl; + $this->redirectUrl = $redirectUrl; + $this->scope = $scope; + $this->clientId = $clientId; + $this->clientSecret = $clientSecret; + $this->code = $code; + } + + public function getAuthorizationUrl(): string + { + return $this->authorizeUrl; + } + + public function getTokenUrl(): string + { + return $this->tokenUrl; + } + + public function getRedirectUri(): string + { + return $this->redirectUrl; + } + + public function getClientId(): ?string + { + return $this->clientId; + } + + public function getClientSecret(): ?string + { + return $this->clientSecret; + } + + public function getScope(): ?string + { + return $this->scope; + } + + public function getCode(): ?string + { + return $this->code; + } + }; + + /** @var $tokenPersistenceFactory TokenPersistenceFactory */ + $tokenPersistence = $tokenPersistenceFactory->create($integration); + $config = new class($tokenPersistence) implements ConfigTokenPersistenceInterface { + private $tokenPersistence; + + public function __construct(TokenPersistenceInterface$tokenPersistence) + { + $this->tokenPersistence = $tokenPersistence; + } + + public function getTokenPersistence(): TokenPersistenceInterface + { + return $this->tokenPersistence; + } + }; + + /** @var $factory HttpFactory */ + $client = $factory->getClient($credentials, $config); + $response = $client->get('https://api-url.com/fetch'); + diff --git a/docs/plugins/integrations/builder.rst b/docs/plugins/integrations/builder.rst new file mode 100644 index 00000000..b630d521 --- /dev/null +++ b/docs/plugins/integrations/builder.rst @@ -0,0 +1,42 @@ +******************** +Integration Builders +******************** + +.. contents:: Table of Contents + +Builders can register itself as a "builder" for email and/or landing pages. + +---- + +Registering the Integration as a Builder +======================================== +To tell the IntegrationsBundle that this integration has configuration options, tag the integration or support class with ``mautic.config_integration`` in the plugin's ``app/config.php``. + +.. code-block:: php + + [ + // ... + 'integrations' => [ + // ... + 'helloworld.integration.builder' => [ + 'class' => \MauticPlugin\HelloWorldBundle\Integration\Support\BuilderSupport::class, + 'tags' => [ + 'mautic.builder_integration', + ], + ], + // ... + ], + // ... + ], + // ... + ]; + + +The ``BuilderSupport`` class must implement:: + + \Mautic\IntegrationsBundle\Integration\Interfaces\BuilderInterface + +The only method currently defined for the interface is ``isSupported`` which should return a boolean if it supports the given feature. Currently, Mautic supports `email` and `page` (landing pages). This will determine what themes should be displayed as an option for the given builder/feature. diff --git a/docs/plugins/integrations_configuration.rst b/docs/plugins/integrations/configuration.rst similarity index 63% rename from docs/plugins/integrations_configuration.rst rename to docs/plugins/integrations/configuration.rst index 6666b19e..c578029e 100644 --- a/docs/plugins/integrations_configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -1,6 +1,8 @@ Integration Configuration ######################### +.. contents:: Table of Contents + The integration plugin provides interfaces to display and store configuration options that can be accessed through the ``\Mautic\PluginBundle\Entity\Integration`` object. Registering the Integration for Configuration @@ -8,7 +10,7 @@ Registering the Integration for Configuration To tell the IntegrationsBundle that this integration has configuration options, tag the integration or support class with ``mautic.config_integration`` in the plugin's ``app/config.php``. -.. code-block:: php +.. code-block:: PHP get(HelloWorldIntegration::NAME)->getIntegrationConfiguration()->getApiKeys(); $username = $apiKeys['username']; -``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormCallbackInterface`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If the integration leverages an auth provider that requires a callback URL or something similar, this interface provides a means to return a translation string to display in the UI. For example, OAuth2 requires a redirect URI. If the admin has to configure the OAuth credentials in the 3rd party service and needs to know what URL to use in Mautic as the return URI, or callback URL, use the ``getCallbackHelpMessageTranslationKey()`` method. +ConfigFormCallbackInterface +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +If the integration leverages an auth provider that requires a callback URL or something similar, this interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormCallbackInterface``, provides a means to return a translation string to display in the UI. For example, OAuth2 requires a redirect URI. If the admin has to configure the OAuth credentials in the third party service and needs to know what URL to use in Mautic as the return URI, or callback URL, use the ``getCallbackHelpMessageTranslationKey()`` method. Feature Interfaces ------------------ -``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeatureSettingsInterface`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This interface provides the Symfony form type class that defines the fields to be displayed on the Features tab. These values are not encrypted. +ConfigFormFeatureSettingsInterface +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeatureSettingsInterface``, provides the Symfony form type class that defines the fields to be displayed on the Features tab. These values are not encrypted. + +.. code-block:: PHP -.. code-block:: php get(HelloWorldIntegration::NAME)->getIntegrationConfiguration()->getFeatureSettings(); - $doSomething = $featureSettings['doSomething']; + $doSomething = $featureSettings['do_Something']; -``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeaturesInterface`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Currently the integrations bundle provides default features. To use these features, implement this interface. ``getSupportedFeatures`` returns an array of supported features. For example, if the Integration syncs with Mautic Contacts, ``getSupportedFeatures()`` could ``return [ConfigFormFeaturesInterface::FEATURE_SYNC];``. +ConfigFormFeaturesInterface +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Currently the integrations bundle provides default features. To use these features, implement this, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeaturesInterface``, interface. ``getSupportedFeatures`` returns an array of supported features. For example, if the Integration syncs with Mautic Contacts, ``getSupportedFeatures()`` could ``return [ConfigFormFeaturesInterface::FEATURE_SYNC];``. Contact/Company Syncing Interfaces ---------------------------------- -The integrations bundle provides a sync framework for third party services to sync with Mautic's Contacts and Companies. The ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface`` determines the configuration options for this sync feature. Refer to the method docblocks in the interface for more details. +The integrations bundle provides a sync framework for third party services to sync with Mautic's Contacts and Companies. The ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface`` determines the configuration options for this sync feature. Refer to the method DocBlocks in the interface for more details. + +Read more about how to leverage the [sync framework](#integration-sync-engine). \ No newline at end of file diff --git a/docs/plugins/integrations/index.rst b/docs/plugins/integrations/index.rst new file mode 100644 index 00000000..521576b4 --- /dev/null +++ b/docs/plugins/integrations/index.rst @@ -0,0 +1,16 @@ +****************************************** +Integration Framework +****************************************** + +.. toctree:: + :caption: Integration Framework + :maxdepth: 3 + :hidden: + + integrations + authentication + builder + configuration + migrations + sync + diff --git a/docs/plugins/integrations/integrations.rst b/docs/plugins/integrations/integrations.rst new file mode 100644 index 00000000..03df1b3f --- /dev/null +++ b/docs/plugins/integrations/integrations.rst @@ -0,0 +1,107 @@ +****************************************** +Getting started with Integration Framework +****************************************** + +.. contents:: Table of Contents + +The IntegrationsBundle is meant to be a drop in replacement for Mautic's PluginBundle's AbstractIntegration class. It provides cleaner interfaces for configuring, authenticating and syncing contacts/companies with third party integrations. + +An example HelloWorld plugin is available https://github.com/mautic/plugin-helloworld. + +--------- + +Using the Integration Framework +=============================== + +Registering the Integration for Authentication +_______________________________________________ + +If the integration requires authentication with the third party service + +1. :ref:`Register the integration` as an integration that requires configuration options. +2. Create a custom Symfony form type for the required credentials and return it as part of the :ref:`config interface`. +3. Create a custom service that builds and configures the Guzzle client required to authenticate and communicate with the third party service. Use an [existing supported factory or create a new one](#authentication-providers). + +Registering the Integration for Configuration +_____________________________________________ + +If the integration has extra configuration settings for features unique to it + +1. :ref:`Register the integration` as an integration that requires configuration options. +2. Create a custom Symfony form type for the features and return it as part of the :ref:`config form feature setting interface`. + +The sync engine +________________ +If the integration syncs with Mautic's contacts and/or companies + +1. Read about :doc:`the sync engine`. + +Registering the Integration as a Builder +________________________________________ +If the integration includes a builder integration (email or landing page) +1. :ref:`Register the integration` as an integration that provides a custom builder. +2. Configure what featured builders the integration supports (Mautic currently supports "email" and "page" builders). + +Basics +====== + +Each integration provides its unique name as registered with Mautic, icon, and display name. When an integration is registered, the integration helper classes will manage the `\Mautic\PluginBundle\Entity\Integration` object through `\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`. It handles decryption and encryption of the integration's API keys so the implementing code never has to. + +Registering the Integration +___________________________ +All integrations whether it uses the config, auth or sync interfaces must have a class that registers itself with Mautic. The integration will be listed no the `/s/plugins` page. + +In the plugin's `Config/config.php`, register the integration using the tag `mautic.basic_integration`. + +.. code-block:: php + + [ + // ... + 'integrations' => [ + 'helloworld.integration' => [ + 'class' => \MauticPlugin\HelloWorldBundle\Integration\HelloWorldIntegration::class, + 'tags' => [ + 'mautic.basic_integration', + ], + ], + // ... + ], + // ... + ], + // ... + ]; + +The ``HelloWorldIntegration`` will need to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class then define the ``getName()``, ``getDisplayName()`` and ``getIcon()`` methods. + +.. code-block:: php + + getTable($this->concatPrefix($this->table))->hasColumn('is_enabled'); + } catch (SchemaException $e) { + return false; + } + } + + protected function up(): void + { + $this->addSql("ALTER TABLE `{$this->concatPrefix($this->table)}` ADD `is_enabled` tinyint(1) 0"); + + $this->addSql("CREATE INDEX {$this->concatPrefix('is_enabled')} ON {$this->concatPrefix($this->table)}(is_enabled);"); + } + } + diff --git a/docs/plugins/integrations/sync.rst b/docs/plugins/integrations/sync.rst new file mode 100644 index 00000000..cf74d11c --- /dev/null +++ b/docs/plugins/integrations/sync.rst @@ -0,0 +1,77 @@ +************************** +Integration Sync Engine +************************** + +.. contents:: Table of Contents + +The sync engine supports bidirectional syncing between Mautic's contact and companies with 3rd party objects. The engine generates a "sync report" from Mautic that it converts to a "sync order" for the integration to process. It then asks for a "sync report" from the integration which it converts to a "sync order" for Mautic to process. + +When building the report, Mautic or the integration will fetch the objects that have been modified or created within the specified timeframe. If the integration supports changes at the field level, it should tell the report on a per field basis when the field was last updated. Otherwise, it should tell the report when the object itself was last modified. These dates are used by the "sync judge" to determine which value should be used in a bi-directional sync. + +The sync is initiated using the ``mautic:integrations:sync`` command. For example:: + + php bin/console mautic:integrations:sync HelloWorld --start-datetime="2020-01-01 00:00:00" --end-datetime="2020-01-02 00:00:00". + +------ + +Registering the Integration for the Sync Engine +=============================================== +To tell the IntegrationsBundle that this integration provides a syncing feature, tag the integration or support class with `mautic.sync_integration` in the plugin's `app/config.php`. + +.. code-block:: php + + [ + // ... + 'integrations' => [ + // ... + 'helloworld.integration.sync' => [ + 'class' => \MauticPlugin\HelloWorldBundle\Integration\Support\SyncSupport::class, + 'tags' => [ + 'mautic.sync_integration', + ], + ], + // ... + ], + // ... + ], + // ... + ]; + + +.. compound:: + + The ``SyncSupport`` class must implement:: + + \Mautic\IntegrationsBundle\Integration\Interfaces\SyncInterface. + +Syncing +======= + +The Mapping Manual +__________________ +The mapping manual tells the sync engine which integration should be synced with which Mautic object (contact or company), the integration fields that were mapped to Mautic fields, and the direction the data is supposed to flow. + +See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/Mapping/Manual/MappingManualFactory.php + +The Sync Data Exchange +______________________ +This is where the sync takes place and is executed by the ``mautic:integrations:sync`` command. Mautic and the integration will build their respective reports of new or modified objects then execute the order from the other side. + +See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/SyncDataExchange.php + +Building Sync Report +____________________ +The sync report tells the sync engine what objects are new and/or modified between the two timestamps given by the engine (or up to the integration discretion if it is a first time sync). Objects should be processed in batches which can be done using the ``RequestDAO::getSyncIteration()``. The sync engine will execute ``SyncDataExchangeInterface::getSyncReport()`` until a report comes back with no objects. + +If the integration supports field level change tracking, it should tell the report so that the sync engine can merge the two data sets more accurately. + +See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/ReportBuilder.php + +Executing the Sync Order +________________________ +The sync order contains all the changes the sync engine has determined should be written to the integration. The integration should communicate back the ID of any objects created or adjust objects as needed such as if they were converted from one to another or deleted. + +See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/OrderExecutioner.php From eb913087e6a03e7872193f781dd386451eee5ad1 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Sun, 16 Oct 2022 12:04:59 +0530 Subject: [PATCH 03/29] Updated gitignore. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 085b0100..fc4b7657 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build .idea -__pycache__ \ No newline at end of file +__pycache__ +.DS_Store \ No newline at end of file From 88350f4ca3ada53e129723f49db1ac53d94763d4 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Sun, 16 Oct 2022 12:44:14 +0530 Subject: [PATCH 04/29] corrected vale errors. --- .github/styles/Vocab/Mautic/accept.txt | 6 ++- docs/plugins/integrations/authentication.rst | 48 ++++++++++---------- docs/plugins/integrations/integrations.rst | 6 +-- docs/plugins/integrations/migrations.rst | 2 +- docs/plugins/integrations/sync.rst | 4 +- 5 files changed, 35 insertions(+), 31 deletions(-) diff --git a/.github/styles/Vocab/Mautic/accept.txt b/.github/styles/Vocab/Mautic/accept.txt index 0b8e0f97..daa5fa59 100644 --- a/.github/styles/Vocab/Mautic/accept.txt +++ b/.github/styles/Vocab/Mautic/accept.txt @@ -121,4 +121,8 @@ Webmecanik www YAML Zapier -Zoho \ No newline at end of file +Zoho +middleware +URIs +false +timeframe \ No newline at end of file diff --git a/docs/plugins/integrations/authentication.rst b/docs/plugins/integrations/authentication.rst index e95e66e3..c1aaca21 100644 --- a/docs/plugins/integrations/authentication.rst +++ b/docs/plugins/integrations/authentication.rst @@ -10,7 +10,7 @@ The integrations bundle provides factories and helpers to create Guzzle Client c Registering the Integration for Authentication ============================================== -If the integration requires the user to authenticate through the web (OAuth2 three legged), the integration needs to tag a service with `mautic.auth_integration` to handle the authentication process (redirecting to login, request the access token, etc). This service will need to implement `\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface`. +If the integration requires the user to authenticate through the web (OAuth2 three legged), the integration needs to tag a service with ``mautic.auth_integration`` to handle the authentication process (redirecting to login, request the access token, etc). This service will need to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface``. .. code-block:: php @@ -35,7 +35,7 @@ If the integration requires the user to authenticate through the web (OAuth2 thr ]; -The `AuthSupport` class must implement `\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface`. +The ``AuthSupport`` class must implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface``. .. code-block:: php @@ -73,7 +73,7 @@ The `AuthSupport` class must implement `\Mautic\IntegrationsBundle\Integration\I } /** - * Returns true if the integration has already been authorized with the 3rd party service. + * Returns true if the integration has already been authorized with the third party service. * * @return bool */ @@ -103,9 +103,9 @@ The `AuthSupport` class must implement `\Mautic\IntegrationsBundle\Integration\I Authentication Providers ------------------------ -The integrations bundle comes with a number of popular authentication protocols available to use as Guzzle clients. New ones can be created by implementing `\Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface.` +The integrations bundle comes with a number of popular authentication protocols available to use as Guzzle clients. New ones can be created by implementing ``\Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface.`` -**The examples below use anonymous classes. Of course, use OOP with services and factories to generate credential, configuration, and client classes.** The best way to get configuration values such as username, password, consumer key, consumer secret, etc is by using the `mautic.integrations.helper` (`\Mautic\IntegrationsBundle\Helper\IntegrationsHelper`) service in order to leverage the configuration stored in the `Integration` entity's api keys. +**The examples below use anonymous classes. Of course, use OOP with services and factories to generate credential, configuration, and client classes.** The best way to get configuration values such as username, password, consumer key, consumer secret, etc is by using the ``mautic.integrations.helper`` (``\Mautic\IntegrationsBundle\Helper\IntegrationsHelper``) service in order to leverage the configuration stored in the ``Integration`` entity's API keys. .. code-block:: php @@ -125,14 +125,14 @@ The integrations bundle comes with a number of popular authentication protocols //... -Api Key +API Key ^^^^^^^ -Use the `mautic.integrations.auth_provider.api_key` service (`\Mautic\IntegrationsBundle\Auth\Provider\ApiKey\HttpFactory`) to obtain a `GuzzleHttp\ClientInterface` that uses an API key for all requests. Out of the box, the factory supports a parameter API key or a header API key. +Use the ``mautic.integrations.auth_provider.api_key`` service (``\Mautic\IntegrationsBundle\Auth\Provider\ApiKey\HttpFactory``) to obtain a ``GuzzleHttp\ClientInterface`` that uses an API key for all requests. Out of the box, the factory supports a parameter API key or a header API key. Parameter Based API Key """"""""""""""""""""""" -To use the parameter based API key, create a credentials class that implements `\Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\ParameterCredentialsInterface`. +To use the parameter based API key, create a credentials class that implements ``\Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\ParameterCredentialsInterface``. .. code-block:: php @@ -212,7 +212,7 @@ Header Based API Key Basic Auth ^^^^^^^^^^ -Use the `mautic.integrations.auth_provider.basic_auth` service (`\Mautic\IntegrationsBundle\Auth\Provider\BasicAuth\HttpFactory`) to obtain a `GuzzleHttp\ClientInterface` that uses basic auth for all requests. +Use the ``mautic.integrations.auth_provider.basic_auth`` service (``\Mautic\IntegrationsBundle\Auth\Provider\BasicAuth\HttpFactory``) to obtain a ``GuzzleHttp\ClientInterface`` that uses basic auth for all requests. .. code-block:: php @@ -337,21 +337,21 @@ OAuth1A two legged does not require a user to login as would three legged. OAuth2 ^^^^^^ -Use the OAuth2 factory according to the grant type required. `\Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\HttpFactory` supports `code` and `refresh_token` grant types. `\Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\HttpFactory` supports `client_credentials` and `password`. +Use the OAuth2 factory according to the grant type required. ``\Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\HttpFactory`` supports ``code`` and ``refresh_token`` grant types. ``\Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\HttpFactory`` supports ``client_credentials`` and ``password``. -The OAuth2 factories leverages [https://github.com/kamermans/guzzle-oauth2-subscriber] as a middleware. +The OAuth2 factories leverages https://github.com/kamermans/guzzle-oauth2-subscriber as a middleware. Client Configuration """""""""""""""""""" -Both OAuth2 factories leverage `\Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface` object to configure things such as configuring the signer (basic auth, post form data, custom), token factory, token persistence, and token signer (bearer auth, basic auth, query string, custom). Use the appropriate interfaces as required for the use case (see the interfaces in `plugins/IntegrationsBundle/Auth/Support/Oauth2/ConfigAccess`). +Both OAuth2 factories leverage ``\Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface`` object to configure things such as configuring the signer (basic auth, post form data, custom), token factory, token persistence, and token signer (bearer auth, basic auth, query string, custom). Use the appropriate interfaces as required for the use case (see the interfaces in ``plugins/IntegrationsBundle/Auth/Support/Oauth2/ConfigAccess``). -See [https://github.com/kamermans/guzzle-oauth2-subscriber] for additional details on configuring the credentials and token signers or creating custom token persistence and factories. +See https://github.com/kamermans/guzzle-oauth2-subscriber for additional details on configuring the credentials and token signers or creating custom token persistence and factories. Integration Token Persistence """"""""""""""""""""""""""""" -For most use cases, a token persistence service to fetch and store the access tokens generated by using refresh tokens, etc will be required. The integrations bundle provides one that natively uses the `\Mautic\PluginBundle\Entity\Integration` entity's api keys. Anything stored through the service is automatically encrypted. +For most use cases, a token persistence service to fetch and store the access tokens generated by using refresh tokens, etc will be required. The integrations bundle provides one that natively uses the ``\Mautic\PluginBundle\Entity\Integration`` entity's API keys. Anything stored through the service is automatically encrypted. -Use the `mautic.integrations.auth_provider.token_persistence_factory` service (`\Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\TokenPersistenceFactory`) to generate a `TokenFactoryInterface` to be returned by the `\Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenFactoryInterface` interface. +Use the ``mautic.integrations.auth_provider.token_persistence_factory`` service (``\Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\TokenPersistenceFactory``) to generate a ``TokenFactoryInterface`` to be returned by the ``\Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenFactoryInterface`` interface. .. code-block:: php @@ -382,13 +382,13 @@ Use the `mautic.integrations.auth_provider.token_persistence_factory` service (` } }; -The token persistence service will automatically manage `access_token`, `refresh_token`, and `expires_at` from the authentication process which are stored in the `Integration` entity's api keys array. +The token persistence service will automatically manage ``access_token``, ``refresh_token``, and ``expires_at`` from the authentication process which are stored in the ``Integration`` entity's API keys array. Token Factory """"""""""""" -In some cases, the 3rd party service may return additional values that are not traditionally part of the oauth2 spec and these values are required for further communication with the api service. In this case, the integration bundle's `\Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\IntegrationTokenFactory` can be used to capture those extra values and store them in the `Integration` entity's api keys array. +In some cases, the third party service may return additional values that are not traditionally part of the oauth2 spec and these values are required for further communication with the API service. In this case, the integration bundle's ``\Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\IntegrationTokenFactory`` can be used to capture those extra values and store them in the ``Integration`` entity's API keys array. -The `IntegrationTokenFactory` can then be returned in a `\Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenFactoryInterface` when configuring the `Client`. +The ``IntegrationTokenFactory`` can then be returned in a ``\Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenFactoryInterface`` when configuring the ``Client``. .. code-block:: php @@ -418,7 +418,7 @@ OAuth2 Two Legged Password Grant """""""""""""" -Below is an example of the password grant for a service that uses a scope (optional interface). The use of the token persistence is assuming the access token is valid for a period of time (i.e. an hour). +Below is an example of the password grant for a service that uses a scope (optional interface). The use of the token persistence is assuming the access token is valid for a period of time (that is an hour). .. code-block:: php @@ -504,8 +504,8 @@ Below is an example of the password grant for a service that uses a scope (optio $response = $client->get('https://api-url.com/fetch'); Client Credentials Grant -""""""""""""""""""""""" -Below is an example of the client credentials grant for a service that uses a scope (optional interface). The use of the token persistence is assuming the access token is valid for a period of time (i.e. an hour). +"""""""""""""""""""""""" +Below is an example of the client credentials grant for a service that uses a scope (optional interface). The use of the token persistence is assuming the access token is valid for a period of time (that is an hour). .. code-block:: php @@ -578,9 +578,9 @@ Below is an example of the client credentials grant for a service that uses a sc OAuth2 Three Legged ^^^^^^^^^^^^^^^^^^^ -Three legged OAuth2 with the code grant is the most complex to implement because it involves redirecting the user to the 3rd party service to authenticate then sent back to Mautic to initiate the access token process using a code returned in the request. +Three legged OAuth2 with the code grant is the most complex to implement because it involves redirecting the user to the third party service to authenticate then sent back to Mautic to initiate the access token process using a code returned in the request. -The first step is to register the integration as a [`\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface`](#registering-the-integration-for-authentication). The `authenticateIntegration()` method will be used to initiate the access token process using the `code` returned in the request after the user logs into the 3rd party service. The integrations bundle provides a route that can be used as the redirect or callback URIs through the named route `mautic_integration_public_callback` that requires a `integration` parameter. This redirect URI can be displayed in the UI by using [`ConfigFormCallbackInterface`](https://github.com/mautic/plugin-integrations/wiki/2.-Integration-Configuration#mauticpluginintegrationsbundleintegrationinterfacesconfigformcallbackinterface). This route will find the integration by name from the `AuthIntegrationsHelper` then execute its `authenticateIntegration()`. +The first step is to register the integration as a :ref:`\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface`. The ``authenticateIntegration()`` method will be used to initiate the access token process using the ``code`` returned in the request after the user logs into the third party service. The integrations bundle provides a route that can be used as the redirect or callback URIs through the named route ``mautic_integration_public_callback`` that requires a ``integration`` parameter. This redirect URI can be displayed in the UI by using [`ConfigFormCallbackInterface`](https://github.com/mautic/plugin-integrations/wiki/2.-Integration-Configuration#mauticpluginintegrationsbundleintegrationinterfacesconfigformcallbackinterface). This route will find the integration by name from the ``AuthIntegrationsHelper`` then execute its ``authenticateIntegration()``. .. code-block:: php @@ -610,7 +610,7 @@ The first step is to register the integration as a [`\Mautic\IntegrationsBundle\ } } -The trick here is that the `Client`'s `authenticate` method will configure a `ClientInterface` then make a call to any valid API url (*this is required*). By making a call, the middleware will initiate the access token process and store it in the `Integration` entity's api keys through the [`TokenPersistenceFactory`](#integration-token-persistence). The URL is recommended to be something simple like a version check or fetching info for the authenticated user. +The trick here is that the ``Client``'s ``authenticate`` method will configure a ``ClientInterface`` then make a call to any valid API URL (*this is required*). By making a call, the middleware will initiate the access token process and store it in the ``Integration`` entity's API keys through the :ref:`TokenPersistenceFactory`. The URL is recommended to be something simple like a version check or fetching info for the authenticated user. Here is an example of a client, assuming that the user has already logged in and the code is in the request. diff --git a/docs/plugins/integrations/integrations.rst b/docs/plugins/integrations/integrations.rst index 03df1b3f..35514692 100644 --- a/docs/plugins/integrations/integrations.rst +++ b/docs/plugins/integrations/integrations.rst @@ -45,13 +45,13 @@ If the integration includes a builder integration (email or landing page) Basics ====== -Each integration provides its unique name as registered with Mautic, icon, and display name. When an integration is registered, the integration helper classes will manage the `\Mautic\PluginBundle\Entity\Integration` object through `\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`. It handles decryption and encryption of the integration's API keys so the implementing code never has to. +Each integration provides its unique name as registered with Mautic, icon, and display name. When an integration is registered, the integration helper classes will manage the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the integration's API keys so the implementing code never has to. Registering the Integration ___________________________ -All integrations whether it uses the config, auth or sync interfaces must have a class that registers itself with Mautic. The integration will be listed no the `/s/plugins` page. +All integrations whether it uses the config, auth or sync interfaces must have a class that registers itself with Mautic. The integration will be listed no the ``/s/plugins`` page. -In the plugin's `Config/config.php`, register the integration using the tag `mautic.basic_integration`. +In the plugin's ``Config/config.php``, register the integration using the tag ``mautic.basic_integration``. .. code-block:: php diff --git a/docs/plugins/integrations/migrations.rst b/docs/plugins/integrations/migrations.rst index fd898cd4..9be1915e 100644 --- a/docs/plugins/integrations/migrations.rst +++ b/docs/plugins/integrations/migrations.rst @@ -35,7 +35,7 @@ Plugin Migrations Each migration file should be stored in the plugin's ``Migration`` folder with a name that matches ``Version_X_Y_Z.php`` where ``X_Y_Z`` matches the semantic versioning of the plugin. Each file should contain the incremental schema changes for the plugin up to the latest version which should match the version in the plugin's ``Config/config.php`` file. -There are two methods. ``isApplicable`` should return true/false if the migration should be ran. `up` should register the SQL to execute. +There are two methods. ``isApplicable`` should return true/false if the migration should be ran. ``up`` should register the SQL to execute. .. code-block:: php diff --git a/docs/plugins/integrations/sync.rst b/docs/plugins/integrations/sync.rst index cf74d11c..a40535d6 100644 --- a/docs/plugins/integrations/sync.rst +++ b/docs/plugins/integrations/sync.rst @@ -4,7 +4,7 @@ Integration Sync Engine .. contents:: Table of Contents -The sync engine supports bidirectional syncing between Mautic's contact and companies with 3rd party objects. The engine generates a "sync report" from Mautic that it converts to a "sync order" for the integration to process. It then asks for a "sync report" from the integration which it converts to a "sync order" for Mautic to process. +The sync engine supports bidirectional syncing between Mautic's contact and companies with third party objects. The engine generates a "sync report" from Mautic that it converts to a "sync order" for the integration to process. It then asks for a "sync report" from the integration which it converts to a "sync order" for Mautic to process. When building the report, Mautic or the integration will fetch the objects that have been modified or created within the specified timeframe. If the integration supports changes at the field level, it should tell the report on a per field basis when the field was last updated. Otherwise, it should tell the report when the object itself was last modified. These dates are used by the "sync judge" to determine which value should be used in a bi-directional sync. @@ -16,7 +16,7 @@ The sync is initiated using the ``mautic:integrations:sync`` command. For exampl Registering the Integration for the Sync Engine =============================================== -To tell the IntegrationsBundle that this integration provides a syncing feature, tag the integration or support class with `mautic.sync_integration` in the plugin's `app/config.php`. +To tell the IntegrationsBundle that this integration provides a syncing feature, tag the integration or support class with ``mautic.sync_integration`` in the plugin's ``app/config.php``. .. code-block:: php From 8b6f3a7c8d554d9f6e0853f50bbdae585c1736fd Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Mon, 17 Oct 2022 08:52:48 +0530 Subject: [PATCH 05/29] Added ConfigFormNotesInterface page. --- docs/plugins/integrations/configuration.rst | 10 ++- .../integrations/configuration_form_notes.rst | 83 +++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 docs/plugins/integrations/configuration_form_notes.rst diff --git a/docs/plugins/integrations/configuration.rst b/docs/plugins/integrations/configuration.rst index c578029e..cb719596 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -116,4 +116,12 @@ Contact/Company Syncing Interfaces ---------------------------------- The integrations bundle provides a sync framework for third party services to sync with Mautic's Contacts and Companies. The ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface`` determines the configuration options for this sync feature. Refer to the method DocBlocks in the interface for more details. -Read more about how to leverage the [sync framework](#integration-sync-engine). \ No newline at end of file +Read more about how to leverage the [sync framework](#integration-sync-engine). + +Config Form Notes Interface +--------------------------- + +The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``, provides a way to put notes, either info or warning, on the plugin configuration form. + +Read more about to how-tos :doc:`here` +[here](#integration-configuration-form-notes) diff --git a/docs/plugins/integrations/configuration_form_notes.rst b/docs/plugins/integrations/configuration_form_notes.rst new file mode 100644 index 00000000..be5dbd2c --- /dev/null +++ b/docs/plugins/integrations/configuration_form_notes.rst @@ -0,0 +1,83 @@ +.. It is a reference only page, not a part of doc tree. + +:orphan: + +************************************ +Integration Configuration Form Notes +************************************ + +The integration framework lets developer define their custom messages for the plugin's configuration form. + +The ``ConfigSupport`` class should implement the:: + + \Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface + +_____ + + .. code-block:: php + + Date: Tue, 31 May 2022 14:31:54 +0300 Subject: [PATCH 06/29] add email testing --- docs/components/images/test-connection.png | Bin 0 -> 40401 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/components/images/test-connection.png diff --git a/docs/components/images/test-connection.png b/docs/components/images/test-connection.png new file mode 100644 index 0000000000000000000000000000000000000000..fa0c04f6671f513d4fc0ce4c0bfd16f819eb4bf5 GIT binary patch literal 40401 zcmeFZWmH^U(>53qBzS-T!9%d%?iL`yEx5Zo35~mkpn>3;(70>k9ta+!8>ex1ccvkE zZh5|0Yu-OIvu1tstNV1HZKrDQs=Df`6RIdLfsR6m^61ecbScTVN{=2r$$#_+F5@X8 z?3X6vuXL~vxQ|K_qK`@ji8mfSdihA|t%!=N-uA4A&Y0@r-GTaQCI(^VD`{y;J;iqI zJWIP?msqQOwU*-eRpyG<_W9YXRQ7J#V{uBo9PANX#bEmdkP&_1yQ`6RVjOZ*UpX2x zUp=PH3-8%GNP>I!j^u}-f$NZd!$HzVw{%Qm=*Pjeez{!vG`eL%y`Z2V8)$PH|6bz} z;as*1)k`>J$v2O&qkZ8ie}7fSuazP{*_5mo{pYXuH}p!pA+PpKRSo;kt^a8?L7o|H zg%rc|<*!bD|KoRvr6|crW)6P!--9?meGQ!Q1Iqjv^FJL)W_rRuSL*B5tp4|$VE>am zM#*=6xq zp?dCrsjG@CO!%L9nXKaet&)Cpw3*RbR3W-)@&8syMOK98O7yF>d8+@VE(JheQL8v& zXMonf)#c5P=ZccWiA&$rr2e%_u>Ut%#0uSGw>shuSzVXd!ehyszmEyn^AkhR5eDMr zl)GARNV!CrZTLP4F;e^4jv5=fNhT&e;beq#$y*bOv`D~b$Oy;WZ|vlO6C#`@(#cOP z-W{2ndj&3RhHRDUEuz;N4N1S!m(A>%03}ZK3?8CH#yoFHK3j~H>M5U?GqLMT`?eqe z1`)wIYfNayGR6K=rhUjfw7}KMlTi)4(#fu7VpO~I6HDypz4PWBPa7BRe21uLnbJA# z97t{V9G4LH->^A)Y~sufV1kI$HzSzy1;niM2ptP!4v_c6#u0U>U|q2rJ~5vP!rnIB zs*wpCYvhO@2sZiCrM;cdD8zY^!B>IahJe-pHnB<|eQh9?ZP~AL1e`cuIx>v&_q;A`mAsx3Pxd~5p z;Oa9TAS{xEV_AHOZ&ED-@s{meFWr3DrI)i4)%;oZnISO(h52? zKW@c1e_mYku4JpVsJX5YB;ZG~d-tQmvyEmKt5Y5|yM$Fdh~Rqb2XdqN=Fqf{eWx%* z_)c@In@@E4EO$m)Mpp%;H&Fw7gzAj=48M(l*i^~_%1JuerDsh1&`Ua@fX4547FPI{ zsD)iu2}Nc%r)U)inU)PK!2(3ycG>%$1b8FZMs8lJ71ijjkX-QW4S_Jr#MG^EiaJh{ zz?WjABgdoDENiG_a0pj>u>;XC7ArBvPJqIuRx{M&>*%g`;BkbBsK)STyM^}V%B9yI^B&nXkOyU#L*8#Yi+A57lYO%k_k}PzHqOlGDSVc zi?;PLaR}~`zaa8kX3ajx6N#Rh#C^-*B$19+MfABkRh*|4H)EEV)iS*21E7`<*fW3FGYk%zu$Ec`=aHZv|{GR1EAcKC-WmCk9+b&mkcU_L0 zIXI_1qBet$aGk~6$Yst~8k(Ly%T#P!MzjbDgT}W3C&h3dCfnmW?S-Y&vn@QBejOjY zp0h}Jvm6%6v4M+0v?Rr6(Kf&xEs;Sa3dMT|SLJFDu`@`aendY%Cy<)Y?6kW7Is2ME zmk1{(2rHUUrc(x)xGNwX$5#9MNz6Yt3_R0w2Wd&uA*)yVpJ__p@dTu!P!?BG50>gHl>;u|?`c>#!p zGgn8DqclescIEM}gNv-<4GpYDimxM3M>MKUsHD=fi;^|fv5a{k#vICIMG9?SIFz1J zHhOvSl*O-g7g07SQ!ZU0I+z(VFqiU3548*#JA9-7iXUZD`RW-{Y;0j_Lqp9@#W90h zSd$!>r{gDoy0BTCxKcg|kiD*VrV1N3Rpf3wjwZZV)WWz5W;4`betd+TBn053jlXpA z@#YBdHyDkXd~1JTd_6LJ%u-`yPIFWWxTvzi_f4Qm8aFGUF1~p>=kx7}-D$821F?+G z+sOC!!&-?TzU4i4DUC+Vpg!{|@O?@4n*mL9*ni(L?L`TP@{BH7u{~1`hj$@`7lbs!IP~k?q@vCBJUd( z-imVE;E`sZ34eFpJjLE)mZYX*3*IAiGTvWmTAysbty|MN>y|4kVZ-#XZsc@QN^Ea5 zH>(`bK=xuApGpYKB=_VfsoW7vNjUsjWb2esZ1& znoW{wf*xm`f42R2HPG$N!uv%%;tr$BGN<&N8-&nd0XE?TIMke@^ROV?{Lt%Bh9*Sc=689LtqaRML=DmC4YLI#}+P2D_4K2rJchlJ*h3H z&*rRYe|bu0kvd#}3?+ilJAC0AF9DjyGgI{Z5bhE)#~DO#-smAqiq6$FV@q!ev4RcH zH!ZG(c9t|dc=03VpD+D}O9m-*iIpPaw#|*gYOd1trP40WB&U{XSlrsBBhS(YiJFLX zD}p9pmU}vULx}T1+3DI!E6YzI#F#qF|n%rJxPT;Qyz$ zsv-)P8=_LExhcI>>F(FHz_I&|K4wV4%pvSZWQRI72~lsgFa43W!K`$WeCgVgix1vx zwmAdFZ==fzU52GMf6zz#2vBRpc29C=elfiWt|=@55`LpP8;Kjk4#Fj#?D8mOG6MTy z^m}q+XPi;*%QhkftiBtm?xYB#cEQ|CR%V*0*C7rZ3L1N<&7*BYTV9|M-Cy-7k-i8R zf9)Pu7^w2`m2n8uiexTUKLG@e4D@|X(0*Oos;9K5ZryD}sRg5})(nl+t!c3MHeN^S z#z5!gRz$ScjQEp*3AkIJkHoB~bBbJmKwL_^wVPw+_p)z*4@a#>c%|;!`Yzv|@ z$VbB}EHC`G2Vc(+sl^$LM(ghMGFq;^NGU$6Mbj@p(K$8$siGJ-Fsn0rv7yvl0sm|p z`l)`og58^{4>WFGb|9M%Dre&g-~ri%zjz^?Nt4PG(`E>I;|}kpb89V)qWp<~FGX%_ zEis?{C@cfN@g)$^!F9*xRRVCdU_EIJM`Gk>17~>p0DFnXUJym}Py)LIb`y8v0(0PK z=dy^wk>?x{Zvdy0!LL@9wPMA6w`GS8$nJ=^^Ch#dFSi!b2Y9&z@-(igO+ zY~{j+RIK_r`U^>8)=%?uss)6ds3rx2S+YT77x@Pa8Zke;@3u6g7%vUl1wEg3>~Se~ z@IO1VXTH@_^$?{amI+@-x}b$W{%n$2UR?uQQ}p-Z8VGp9e&CoGPpTBLu1LT9tLpR- zGQ(86wSWv^d2jzbe!HcWCyT*hX3x%vZ7!F-jO35@!4+j!a-l4XyqgymHnFEQwsp77 zuYr(VoZQZI3G+fHV>mtQ1ZSP=?G?Gx2_fCi zY_>h>b6iF_U55jHS*c|&pxFeHLw;W%JI!5NZ#T8{w439Bx)MF?Ne2g*X00E^);By( zqOAnFEmL=|R_i(B6A6cNT$mqoF`0UW^>perHXX-2a<1NKzOeCP4qiWhzJ**Z$A4nv z>+|Y85r5^eR)!SkR2@t3c#9mUrg9wkdU~UIPiITIfIQw_@a1MT&$MMv4bB&e7o=KF zi;m80VQbU!dp|~_qV)`2iz8OX%U*A>_jhwKtpwGD5;O6Nr<1(6JHtBu$(}yVNU+S@ zGCS_vLSj05J^9whW2}B>+~hQq>%wGh8oR(lk<2Ut-KF&mZi_kPpJrG`3?>UO=lU3G z!n29WWOF@fYS(R7M9^88*TA5kYgeRZ#B`-S3-|H&(xmdjQM^-o`T4B#w_H1d(xhgM zcIcThQ7rC3h!zN7CNW*gzvE{`RanGZxcRdX$_2MY+p*!4!IRw9rT%DzglcCxPF6o% z{>*YsffO&{pOc|f6oQ55D@#RMbOkUw+3Z0RZwj5N@lme2xlW(EA^5d%t^P!HY_(AL zUy%^Cq}(ZEWt{&TZ)U+3w3Fr)K5pZ@nb+zw&~U3_+m9Y+vUDl`7A((xRyp zd5Gl=QEz#c;%)YL;exonk1!&awbpH1p*XV2OIXmSLXnxyinN!bhE*ym_^l4}D39^Nf^-|4hwA~>xjd?2KGU=(1xjluspvkJ zI#2k1lmhIGRXpmDZO}03LG>zM;OUZmM@6j+3^Hpj)v6jqb{;T6KYElGh559!v?%%Y z3cac@lt-tIUe**PAJ^>l*fW}^JdD5^D=>=f5up83q4a~Dt+j(Qp57Zwa_TZZk|kUq&;Y$;tv>ZSUBOU9=%HkREzd|y=L|r7|&i? zP^bt~WLztru@?`$=KZ|CbaT=H*`nl^@YkqsCW%^siCs>ns57Gr_(gszu0bvgI!sfn z-wuZ1PIHQb(#naVR@nq}oMUjX<`{2OzdQ^&pA5Sy;Vr>!QipZw6-YcP6r|E!TQJrO z!$Y-ryQGT0-UnWOC@*1`aERYfxJ1aSQ84&mSDf9tO*P1`ft&JwL^uHhtgL9hqUx$? z?5M>9S`vQsGh197ku%7zYn!6x4`|H|^!kqW>!ox`n5RBk1=86cVH&#iRW{zrv5}>n znyohN)E)hzzOCmB^WU~I1R=Tkj64=9pXphy^P>Hf)Q%`)@bQn}t!i(*~E9 zQnzGLs{PMPJf3NDdH?)n>@)LJFE`=+C+?$$yP(|=p>G|`-Klfwi-=!2frKJgLAz-i z`Zur38!#%)iimCjTZ_);=6ZsjO9_0XFO;k=4xG=XC1=kWKSri8rW`~t6xuM>9nXTF zE7^18_ZDhB#^nK~n{tp8HeM&cSFD zo41JgGx=`XMg(7oW)qNNL6A5(Tw3^ns>QtipMBcRAY#f9pJ+$mHv%c+v8TQ03TX$eHwSw5Z z?;9oQw04RMpEIz#Ze?s{XMM^m(?uLTk7ze;S zX&HUF!uBg**=cG>{oFv(R6VN;q@?R$h5@n)2I^ur8hlGZOjv;q%CK5Pjd_KvJUQMHW6Zz@u^zzrf;oDMH?R0u z*=XU52iwJKJARL?6O*}GJJ$?r|DZi1z15@41KGQeX)^Xy6dGM_2jkjY`^G;dyIdGB z(rBc#s(epCpw)FB`GZbCph{LnljBih(#bO!3T-__m(}r7b~6v|Il}jscx#RNIGT-Y zU!dW24Qdp-JJSgv+GGChPIKwURG@cN%elD7HaD{g-DHLDW)dP21!#|F=!&rXGw#Ns zEMr9vYIQU$^(T0ST+dZwBYo!w`CSXW}-$`gNYMlTFA{FfVB^j7Et6xUD2zU0LsM;A$4w^&D$es#92DR!|?>5U&aR#9`~ zo^Hu#Ey)O^j&j;;Kd(n9nvUyjhq7Jl%sjRNxYx9#XbRNSAk)#6WemjY zvJ7K!$(<2+=rvl7{oIe7%5;IzW^_!o>F(AO{)@U~?#g;l?$sn3-tNSZp7N7)OE01} zaFi~#l6`?%T5!a48DA~NR;V%&T8ZMiSwpu4vV8Y6Ck*I1s+_Y71-!Kpch-I+4 z4`N*oM7*TDLo$=S4nopva-ahIELggB`}s2cX~C0=mNHieoh(+YTRpf5K+=B@-y`$g27Q!VnlK2zW*em7T2#{SWfE9ynzP<43pC6^SaYGvbR z$WvA|!wB$3XvRFNC)!Q&iwNN!7uL!P0Sn4eG4Cc%O*0DMpveKL@a;?scN9DrOjjav zJE@pUsaS^~pT_s2!nBXa`}(@lmD3tR$;O85a)x^QU4P`gI$D03mT)`fErP7%c_1&% zx3Xl$ud@J_bO^1P+O7KQvy9t_1mze_r}IQW_NXh^!ikxeWV1$(-~B&afQrVixGGC) z)ET7Lcpr$gz1=ffwuOCcWXY=uT+XukOK#a+ilMFIOLaq5=x(_lJW=WAh#6!q3)blQ z-0XMLm;`ag9!yTopcdBMZOn5YP{0#FgNE4Vx350KoC6vi1%rYv*W6o#Ylq8I%o^|a z6t=g*exh&N2@6I?wjWCU=PEL#K!WoRG zM`;^Z^)oAiRkB2ltB=1x7~Q(YW8G6JE$^6Cw|U;WbLy``J&+hN>{m-u*ozB`KnPAo zfUwH`TLtWc-J0m+xI=V>T!N_As#zT*E|s;8q@xh(qih$Jj3AErwG0URhn6p%g;<{5 zvyD406=MOhGlbPk!X%sfQZTr9xQ1T~I#4mq*m#=}(P-_Ns8^*5(YUzLmPR+bQ75WT z6>8U}we)m4uLXcdT;tT#q;$CVUfzO~#gvAsi}@Bv8rZAF`Mj`Q`~WLQW^;F^+Ij=5 zcAH%c^&rmMtIwGH1>$F?vlkMx6ydTHcC)X1EHBU};;YijLx!xVPLlU*nnWHa;OCW* zgKOAa@$JK(*0I2AJ;}h5^;t}>3s|?Sla;&?`h>7lpQg>r**k`%1u>!np4auO1scPXHs6Pfm14PaVQG?%+ z11?_+3#DIRrHiGnEd)njTx|8<-n1ln4Kd5Q{kV;GzfBt3Ox(H{IUT~>*T9F$$c68; zYPRg3A!+8tR=lbkkL+o-CXDc_KB`nE;SDTR(~^PuiPm^J4OeOCq}-jPU8~CEcn82+ZI9zM zx>;5vfvRay<0ubIWB@~xDSpj8Wzo>&sT7qk{0Qm72875O}Jw<0b ztv*xr8Phe^&xe1t;E;iYtj#4EQT&^!s;mYdh`PD>3wuP8fm(HUD+)ME-QEgi;F#wOyojRI@oXrTgrDn+3bfsT2 z|A%6)ow-|u>#W`P+LD}Z2XD7l1vJLf+_{N_OT^PshANoc1dZ9BE0ta%so+b_6T;Kk zNH$GLZwB&C9;Zxaa3yF<&5qP{Lex=(8Pj^Y8M|066!3&n_(L!&8uNX24~E#6GmaO( zI0n>?$Fv5@h%V#4{WyBzz9KDDHPZ)`2%Og>R_X>&2-^t_$KO%cD*)zvE+_;f1iGJO z$J;~M^PQs#S6&EvH6?T3DTjzKpLHLNy2)PLqMg{&^7-~m?0X(k4qSyBx=K>pD`*9| zIUE#NzGu7=GCp36MN2<{y80YZjHu*_|!nbV^Q{G%9m;u4g8s?_nZ$N zy-e3>`%1F`*X1t~+XcT)MCmltm2OZH_dB>dHSPHJ99DVqbB98L`F7=!Mw{sa20~o7 z3ro-SGYU(Aq!@J(WYU^aoN5$n`eiZh^nGG`Yvs_EA8XyuVOc5y(ao#5E;>8o8k;Nb zA@el-+xm@dOK<*-E(eNdP)~2F@i|z242VGo(;X5eTQ9&;d4mT_!6oUXPfGW`$KHHR zhqfE*$r-lD3Bx3f!uxr*m1rw%sp6rWGJ4SkW1lW{7-sHJ!N>|4^? zA1Gk*5Mq18Wi}64w`yT?04$TxsX>DnE?Uf$=de7tlx`mhU6# znXG%Hhl~d`XEo7LxT1xwYLipLUXZ?s@+%-AeLl?g)z7}5hAQ{6XNQ!q0tu%eH91H7 zGq-`;<+f*Oc14SXcQy?nDqt1STzhmIf4(j zZJ}2$0V-q&jpZx%dMZkf>cB$ixsl)j-RY&Ix+It%&hz2aj1W>HVR-5+B(&81)Q8=J zt#ZO+)l5`7m<>wb;l4kjH6;86l1wmAub7lh?v4>RoNNi;4yG~FMti~5|ww)kp2RRu&=xl6JALnH` zcWowv-|Kb1xB_!EpD|U9P(b9k#D8Xo(Rx2KIv zc&Qq^QN=2MW@{uMT$i?9p0U7h1>CtKYH(q`NFR$Kuf}lGR%0VC;Zq1-xAj28LFLuV zWf?CFm{t|rlw$K9$Z;3bZ!8`vBsG`NVJHP6vU=&fZl+RG6676JFYRCJ5Ti~$A?UGK zdPap5G?wbMu{<;K)CW`BhYi%p%74m(*>cS>J4DIqF^G9CF24U3by~Rdup-hhUmbs_ zFG!_z;)9^E2crPy_(FNY)yULqzAY3}T44Qt1`0s#aS5%WqM~m~$_+Gc=QBC@#U$b! zIfLC~>Xl&s*!2=xh&-{uDWo)%*NeZ8&v~_c%FJrcGdL9|sWJ8tJqshF%x;*(X|1XM z^t3Z^56}KG`dbpdH|_vqx(GvaRz$VVkg$9uH+$$6_f0$V*mAn>Aj7`3Y-Sx|?qSsa zk90Cu=e>(jufB?y%U#X9jk&KaAiE*s69G$$s#zWp*KuxvYGM2WaE`*fpse+_TxLFB zE(`{7-J%KaZ48x%^H~k>?K)TIAHeiz&g?7EN=V(+j<4aT04jB)IJzKWn|VrX4m?kb zZU|9#*G6n*kwq#KaaU8dft2p`Z-aglx`QiqSCiH@g7^VEJs~|%wzZ#{)&xu1mm}aTL_42r5lAS55y);K zU=%%Rs+{T!QX&$OayNe-sv{)F+RV51^`vTcq_^IH6RhE7OY%lT&^TDM%~7C z&3P`B>Hq|ibuI<-8-#ok#+!Mbg5dWjYHzD#+++JnfrR7(?Q?re=WA;%4!6F62|Q_WsC}#KE4nz zb7hQ`)1)gNV9^R=jy*iFc`bvr$wrg97|3^&pb3CMhmrVsGvwd{p@KHn$J~qal-*Ub z*?dX{#;%DJVd|6EdA}ax3INqC_&Mbt;OT-y7*!FMXdh)nH)srn?b*r<8% zg>^>X&66MZ8;6IxzifS}(nbwqY_^FY!BF|t+QM-zSY~Jz`QetAaQv{0)K1`tDYwcS z@{`~XYRT=eHUo_mfTDcNUi=CVKL-+h6Nd^FP8g5X&PJaFr|Hod08pC!de+FQ}b@=$Pqpz>e60W-T zow5@5fwMauf!2G##&p$tbasWSY6JH$e&&s}n6cT;?7i{N1*mgyCswt?eWk1cT0G{U zVAPlbnK+euCP<8X{^Xyh{TFLt{tZkBNQ(s)fBo`@`&|o@&Y1Pl2>AbR@gLgx|2Ll% zT}vVAA#XeqjD=Pkr(awk^(UQ`@kWoSn?-qi)&zP||A#nqpX0KU$`hmy^)sc=es3|CG0}olx%&f|0jjMvPRQyY@|@hdo29>Y?via`cSj03 zM0Pd|y81I(D|)cLTEg!~x|BEzDj^L)&5#x+;=} zi>6G|zV08MbaV@5ba=4;ta)*yW-hKkBpQXN>gF)!@;hURb)TW7;bMAaw9Azoc=FAj zACrOCl5)rLEmUOILdgO(-=2)cv8#!wl<9M(s-ps`(kBAKfy>7~8>%tO-+zlv?lMGu zZenk6a1FoC8rb%YwAQda*X@^TE z+$#*3+m-61v+G5==WOS>OUN^&-k&6T{3+jLlzwIj=>|ubiCR!h3uB{1R@g~#3~#aV z7-N`?`41l-*JFv5vOS12QwaqI7;!z$Aa3)WW+a}AZS|m!>DlG*pTOr+yyD}pJ{e$@ z?ce@!6t?YjCR3gx6H&J&?|J*KGur9QLdcV;_G@&qWUSso8EZN) z0B^?giQ91<`LXSL4gxY5?>Pos1Wgy=>QbAzryt9gVG&n?%54^;o2zd7!-tCfj!C%u znUL@f+S}0~EVf=r{Opw`8jP|OX6tCTX`!}__VpCa9`(ckf+%me=zxFwUi|~*G=?aQbb#$Ta!Xg z_R&KS-15fC?otbyNJik`oL&1C#K1im0cWD|2?>$O{!l)<#>!jfO;;d3EJR z3+0OFOJ%QmG~%{(7U3B#W-24KSCi`V5+t!*=f@iV?0D>dW z^M><@neWkB4V_j+Cj=9VaT7b`$0_xGt>vh&s)tkBgU~>WOGW|dJ)gH&H9s`S2eai$ zZ_zOJWJj^5KNPP}!t2!fYk67&yGHi1;kTCct`9eOlR$_Ft$yeX-kkA1dz|TTj~hE& z0bj}in#{lH4l$4M1!JwegsR+LQ*@o?hIz1!zA}>BU`;xU;O%rJ2{m8;1x`%YZS`lV zWCZ3QvU?9db$`BRM%O>D&n!PLI25L)80KrspE)Aq;PK1L<~a^u5^tj$)iCOK1!woE zv5Rr;2rB-LN9UK3^pim^k1%JN2nP;YvP12}54m!~Su{B9SHt zHH1?9=;t+B`yFdk8wmfz;7W-!DOq+r)Sji@i%H6nUK1~)zm%6cd2TjjxI+}#A%H%3 z40}3nFlfx#@S5srO^I$FwK0*yK_3rG!7|s{7KvMv!h?0B%Y8GU?z?B1hwFKw&Kz(YpayC`m1Zm3AOkZh%;RIBzzdtcawf8wS?q zR&seixf;6j?OGVsYjJjFB@tV$U9miIZ5sG!EI)#l;k_}^n;<%Ims7~@1=cR=#k;`TEYvD@2t4_j} z)$SA@k=kAmutqd~I*HJ~Vae+<;&`BtEJXSPMr&35lp+!Fn^Z#cROe9EDwON_^HN~s z+Kq@`4cT+AXPc+Xk5`2)w56IjfTSz>%#n55(sYx^;Ut88e^WX^jE{(-vL^ISF8ZKr ztlxuhbX2k*7(un+_viSaLc(Djm9N2__!w0}+5x?0bCXa2zHCl{OHVYRY+Kj6;}mP+ z>b>q(gi*I+$oDnNR4k=p!w-no>#do7h8$FBsxn4WAND$kj@rpt(~Ir=;O92|roU9b z9gu6?ah$F!4Oz;0R{km@eMsUEpP5}q#2eoeA<}X2fc+zP>h$5X*#uEPdi675L(0+} zx_W411FN+jrAzz@J)+JkL{;M6QiI(u?b^6j3H14wW;3!Owk7gt`N0QY zOyU_eI})ZxZF8>>0ZaLYUQZAFs}!fCwJ@?_=89a;X!v@r z+jVd=n|LWsNse#BUlV<5i55FIX@*)r&91lW@QMan)6~nJxG*aI%)3xtwy{}TckjN` zWB%1+SVF3c_LQJVo77rx*2PE2^};1{uM+E+$+3Pfn;iGQNtqzj4l`H{%|M3SF-XD| zx@P-dd`0GIm+JbT&+>^LA_PUQ`}J3FPF6#`jD`?zNitgNcL<0F1E$akhgWxDG(^D5 zH}rWWg6ErQ5Bw))1H-#jc%jbQS0-z332Y#7T*)hH1x^1CC-`mM_U@%3N&(cSa4sDd@qeg~d^jidPQ;Py|r1lwE%f%nydFU$bHgX2Fg4$Mrg zJaf>J9O&(RbsygO`}dM(|6b0UIK1D%UD$o#2o^cYRlZQId)QGrA+6Pe-(a3&7ko-W zLP%5;Ca2X{ZKU1ipNdw$LtGxOOAd)h;x-#De#ShjTLvhry6uA`9E{;h02lxmvAW5F zTP~U7{hKCM;}WIc{#+`2xRf)r*^9#(Qj}`dSVlOIA#bRj{k!24_!85HhULac#oC}| zI9*k&hkste#&cLDUhS{%Q`fBiT~9`jGQl4O1IQNF3l$!2nrDIYpwW=vXiWm}e;1}x zemF@B;(eQrU_?1^J@c6M!!vxH<=cc~Lr_S2vNKpTjdk<>!JJYmR>NA6$exmpoN3Ul&w9d*N2)knX$9uXCfdT*EVB-n#UuojE#p7B5$qE|hBqtnWHD2k}k|Q=6?&j~unBlv4SGt#@RkBo^T> z+bGOuWY`%=KpYvy7HgtIUd(VmwOE6`Tv4=j z&JC7qZWP;W9!e~m3UkcbZC3^rJxD@@??Kv5+x1(63QJ2r96j}iFpuJGcHQdUN)E^w zoff_Iq(C}&8>Y2vqxmBoyno#1WogSdsE|PuJ^kvdM>53k+G);f#nyX^EvCn(1T}uA zk?Ry`H}#4`H%z&Jg0JL{l&J3Yg3exY;3f#uNO+;)hNj%0oj(sr;iLui?zRCOwi|s~odA zWSl2DeAaP=h#S-wyM42Y6Yq6^nuNy!KFpSZHXoxF7&rMHddnFc^rBxokK`c<1nip6 zc;&i!44d2Z7RYD0S36jb1UP;nM=4}FpwE?u2~+jmgD^?QbwMjHg>6ga3o~Wsh*Km} zu*o_1u0RAAOKA)*hA~{ShJ(7#ZFWVyJsboA0+{5|TZ)q4hYKL_x?^aNq=0qm2FcSK5o| zCr>KZC>KdsKjlXJvfz~xs%A;ad`X`c0KVBj^Y!;a?)O|#zZKZBtWRM^&lgi8SsM~V zJP3R*e#^{|UX7yMX_9UScMry!0RZWw;|bB)Hs6lpeoP}1oDgH4Pf(xFVn!OeDl)c; z%I^{g2wJtpYrJZ)=xKD+k^(@OLpEVtJxWD9n0`!Dj5*ew#n;(w^{xex1I7y`8CH?M zUtOjbp5{&vcuu&!7>xjr#FT!2)Uedj#Zqs~$TAp~SQjY}wF$vA88~^oK6gb9=ABt3Npac3ICqF$TY|Wb9bEdM^keK92%2Ck?Z?}*;G1=(R zH~RMMbTK#k$d$i^Na3t0tOh+IQ|nks5^P^^Z@HuyVcUP}lYD&Lu9E0xsQI(^ zXWTsF%d_-ml|QfHhnt3w)R&%&2k0|XZ05eC4&M|%P(V3USTw8gdYMv36**ZhQR_1E zQQ+4vtjB*2O1jEFVx3lJby6jvmh+gOp$s)e9cHE+afW>4xHBTHR5&$z|F-p=vBkoC zRK~%=U3RRo;Nk*hQcL>6oRqONX1+Gx5#-b2s2Ah4N$Tyi5nAk>$wFn_@OJH_L^aOiLPI z*rW|K;@ce+eLmUwJzH}zP6ZXI#VujozR}HntfPuczr=+`^R8F68JSh+7uc$RVi)e= zSj=QYh1m$z!D#n&cIw6$xfabEW=$zC3p;p_*AX8qy~}^+DK{*M2P`o%*(SJ!<_S3! zn^&YupPvGw_M7{qH-D!j=agfC@5Bw7ET{k*HbQyapYrC`gd>E`dJcPIk3D$(qg8Cr zU=!E)J@I=RBpG)v8>_h}d-lVtFj(16>+Tgb>&&G{Q4qA*J0X!$9}xz_uO^`9iAO~p zf$1wJ$4s%8>c39BTYif6=R}W_hn;>!A7HLEg(ugUT+EcvKl5D=}nwGv_cU6>DsXG?ejbl-X*hloUFtFeKB3dh?ai=h` z><~>Me9PX88tmE(7_=51cK%upIHybs8Jr(3i`=m`E83YH6zr-jy%MfKToHjN(cd>e$NRcKz7a7+kCeQqR$o?fLtMGnR`Llt^ zTLSxTs&e)%w8YI{<##NI2&RXV-=oei&o*5X5U~I{NO$5L^B{Y$8H(m~B7bKz4a;_^h@e%Rk5@|S@V?wh`3EN#x z{tnDPio?0LX7I8}TTF~VE#WG;`D)zI?b{o)9PQ-8@lVY&_5$By;(mS&%_zJ2KA31# zZF)R&3%`32&PyM2KH!>>&$k-P=+^4mm>sJ2o`m)GVMo0&#V>OqEfgTHu88_H#r$Aw znaCe@?jT>Koy4((>2EIsj19XFmfp}h3v#D_xXtuAY(9*}oY~E@|1($Sy(9oW^S8SI zmkV=?Lg;{efJBwwuAB{O~xlOXjc?jUePKfaK+Y9y~n7 zd<6Lnp@C+vD~tPrlFM%wqqN%vDi2PPoo{P(|Gmemsj0cVva+zWl$GiJ1X1VRZ;yHj zA-w10=NF2^0kiw!nsXGH{Cj_QLN))voA~;EUTaMeRxOhi)%7+dElo~DM1-KB2=o9S zz?k36^Q)`D2?+_KkF6XrIeqxNi&O(18qR3=S*MAm;VIeMfK-zoe)`LWheNLQht+J* zhkw8P+e?3NgInLhGE%xGFTv=4x%I#AD1lXFY@%f43H_s!`>zgIurvuifh0ieVX=St z_P+G6u%f>GbphsYKlwp_?!W51g{8*cAu7%Oj=KLE239)*D-U7$y!m_je-DEXdn*p$ zi=PVj-+e!<=6{1F6smBe{3k=9njcwM`R7^UKl|CY^1^JhK1C;!ebXNn!#;&cK_ZVY zzuy>n_$?DRt5MricURtgEQ~@(dvhlzgLbUs$ea#?xF9uVa?MtkFI>~Qdm%Mn$Z(;- zy#(a3%F66k6~BLQFL%=8k?YMyzwo@;74kKT21d&P|j)6jkyh5;>Y%@F<+@i2GhuE ziNXetgBf3gO*_3#ib}3%a}={<*h>!>E->%5-%?}f_ELu>?czWU+LK<8Cdb-b{o4DU zkiYa)h+xabPN+ui+e77{2lOeTOM%Z)_u_xvu2O1|Aj=y#=I;b47L+bT;-6O@%kl~> z3?15Q^(}j3mGCVgVKAwV^2d+ZRkFdX9l>73ir6`=_~_)nvMp5y|H`(w|H-zAK$ zTu!>wdlV`(BJ5LQLj<*<*ZTC}6B&6zDG$P77e(&m=ToAa(@3(Zj(c5&VXrJy`_-aF z`+r$L@ZZc8dwq&P2^!(jycx_S8^@Em=)|bBK zKu!j|1d%QCHCCD;YIoZi<$4lyM^s&wl&x``c@Ca;XQ&2S| zL$y0ARD=`=vA5 z?|Ga&B?Ze_gK6}+S#M{_1808j@;DAQewnS!EZV}zuK{oTJF`P){;$lA-SK912a5{q zEfVt<_ejuN_VWE@fz^bk@Bf#a&b80!t`Ji%-|*dCSTj1VhVSQK>TXKi@T~u% zy|0X_YiZiNaaI(b&qKs3_kFLGVInI)AT-d2{k0#xQK09bKy7_gW8F&*<6G-=FCkA5+ zOkY)-a!dDCHZ*&q1`#n0IgM9dQYH+SDwejQCq8=U!rW2C;z$$_8xz9pX#UtIxAfOf zFj_Igd04S4>Y_|c_(YFVBYrlLinP!?PFLWYV*l90u|WT_5~o&zU=_FZ5fe1{WfC~f zgw9@0B@9`ym{c||b#eXu>$BK-wy;pnS*z0-%KlB-F;li~oiwvfb0mBt@@~-c&GpM+ zu`r~11615f!i>j!0vGQh-lRHYsLh)H_mQk_st2k1NgT?CXVx;4FQE4&i6vZ3@r@}qd(BNB(?3>mT zTGM2f;GZQ#buoB$R}SS;Xe1DNs!W#AJ#EE#J(b$`IX-Pn}QN0b4 z68kc^X1mcBd!T>*DqY8g1)JRfRT8S!{Xr}EXjg?TtV<=xM!0qJjC3V2tyhUuCIow| zZ(F?ivlVU4Knhb*NQqUCl*F=)AfKMre5TkC`|i(yCvfW%5$MZ(LO4XS{HayMBblnCs;Vw5t? zI$&*eV3)Sx=}Goawlf9J+r)^oe8cmt6(yxFNYQmz4p$W>vylE73DmI)Wh1NBD zbT|6eS0599Xw9^lqC99wN{?l!=Exe?-=9Xj5>3Pv^}mYmUS@SiN(>WgSypZsE}Ey} zN_ZTf$)D~mi(>HN$QngYEe-p~72^^0?sqv`h%ng9LceWN?0T9n`IOwD6&EpuK3O2% z0*N6q|MiN%tia}RpyIjodo6Wx(LJo8lQYOxOJ2Q`#7}0U_gG~0+OZ&3ZyIi9Cp|N! zM;hY#rUoRC%%aIxE#I=4kapl|=f$-KQTUdk`-!QWoU$za2p@@{Xlc68WJ$)Z6HEqG z_?7yf$q|r&fPzl}q$M91XP?FX>Z>}t%+R6Tlh_hRq%~FI@p&iWJS`HUV$S@~038+C zpUldUk}misqk|}YG*}10mT~P!LDPMi@{vKWwvb4K+l$oa7cSHSIX<5`Uc^`XLEL8m8ivy}x^=L+ko`=S(y z9N&KVeQaiVtFuz&rIxyU_ksq+K})Nq#kj`GP&anQZlcc3ULlw8VfRl;E~y`zPiQ8V z_UdGe^ZMf6q~H;QWAQ}yCwam(`j?Xzj~uV!zI99Xd@ zu7J0~VAh5YSs2gRI3p9uoa_*@g(5gP9?m0*Ojg)Gp;8e?%KtUzkT2dP;a=M=Fit@9 zSkWQ8;&nn==duz8^eaW)Fn9f1p(m4%(y|AR^? zxSR8RRcQ+IiMFHFq?ABPh~D-xxqYE1*$zJk7Jbkne9EhHvw3NRp*0p85ys(|_>Em_ zsL7UQ56yW_avdXcIGPMqXWm;O7=o|yri%KzlnoQ>$Sh)rd?c@KRa}@BC~+=cTv>w2 z&44s)zY0~D8`tC6FFqrE==xk4Z$fJwCuia=N0QwO0gI|&>T_GdWJgf4w>v1fQK|xK z8JFFgbNOO=p3mVe{fgAl4{UTXGpL2Cftp_@BXe(C>J06LSXs0bG*o$ZUW}flE)e~4 z{kUbT$HZ7XCG3C{o8#;z+0#i2M7FMfWah!S{@3&U*Pb2l*-dBloO#MgtrQ}8k8OAR z{KE!=(LrlT(`-90fEr;hkBkY`q5c^mcHAu5rAFK~1heCkx>9Pw(uMh=<@40}NN+gu zrg<8O20<-AAr;%Sz}maB7G-@WyUX0_)%V0(z0kW+n%&Mew=9D3>JVJNh9kW&e#+LfSxa@-4-KrbIp}eR*E48?j;(C3Mb1k} zTg4ofF;=GIwA7n7Qbl!~$Z;8ABI_j=G`%f~q|;2YXa2dT@E`JCo-)DY$LN zm-Iv7?);Y1eLx=$}_85i@Fgpzu7ClXx+vgi$h-AE_9DFT9!na!)tiufY5 z7}h4a3%{mRJTf?lwt9KqW-s56+$AECNK)q+R@cxHJC;v)Ys@FpYHDeiW95!nWbJ?c zA|WY^&dt?b%;#BeL8;$1WGKO)*tYw_iIx!0n<>|DD2$R>1dGM)J-*=PgO?vtUY`g0 z=O@0k{h;6JZ}Hx#OlM36nTTzM^MyfBBhzQ+E8F>Z7cQl*NqjBSm+5R~Vr`FQKtFrq zz{;W+78xj3rq;TRO7SA&I6b24i*P=b+m)?N4gK%b;SdYNPwTb59;l}mPD!kfWI!7> zC&3*Z?~f)&1`iy3mpe**Qa?mEHpeQfjs7?(UwR{$ zW0GRth^4A;IvIQjZJRVTB<$Y@ z1V!PMt_9aLogIvfkUn=gvpnBZg3$Kpoj&O-bPG~4kFZEyS?x;E9~B3nB;{{|0>&RXxx# z&0OJnqMG7-t5O7XnyHi?B)M~adMP(((YV|TT!Nbn(fw&E=EM>9$3KI9n!B2ANMdd|s98X8 zW%A6j91XP7NFWksYCC81E^LwF9MS!X?ng_mARCF({9A2Vk&QXTVlq`q?Rl!^ZcB(Q zTD2!-ZBt=0qqkPz7ud+x&;xBIxZ+e;Asyg(@`@KSHF;_QGL=+AaMz7EpPfpNW)$gc5_Xb@&nQqK>F zN!IkFmEXY_Nw(7rhIEpQs|>BwWlLGuO$Aq`EY6lY9?|F$b4K@iVrHnk^mv$T%*P!h z*SUU%l^_E=lI+UuOEqEF9o>#u6Pu@G1ti%gf0@seL$kJycqgxRC6Z$k#OjKDCpevF zTHNV7pUQQpjlUc}uL^{iQ`ipCcGzdPoiLvbkT>b&6dytN&Lg6bPbYvSj=-?qeOy#0 zX?vga!r#;V7J*@bckIzlBE<>f`t$+sPp*}M>}qiGHC)SR#pl~Wn_ z-~oB8c3c?}OSR0&mPR%XiNf-iSH4B6N8p^|X#2H3l5Doo${f&9X4nW7B)XbMz4O}t zRVR4UA`_OmT|>qrytt0<5bF< zn~pE;n2O^c51k4+#HYco(B;;9%COTFLYBeBEBy}eJ~IohPy4LrNnUP^qlvBQe7LV2j0l0|RzWZx!RJZ~#@T5Fv3?2NKKK`U4OdDPEu09ONZFl1ed)p?=*D!qcFyb< z57WCCg}HYYZ9uqn7ccmjKY#S7vR%c$W&O=uvvEMb;Vn zRh9{vu80pYLdQ{{U&&^;kQh@1ST&Ffsaw4m~O6*U^(+Ud7w}T1L*GPHrxPD(uFT*!GPgB2pw| zACl@k)%v96%xh?0JguoZV^}1~tbYRDyJr!EscM(M%6t>D*N$YI4uXgU{1R{mY5cKP zGVr*yQw4=2xl~^wBpun9!)0f2^mf#ZwKRz`U6M9UU{Lwb7Oc@Skm@ZFR}3T+r<1H} z@{lcff7w@WCzVr}&pGe4e>YwzS{_p$8v2$gkTtV`YaHUMAm)<~4EYPc+5CPF(_z<+ zdKIzLMW#SF7AqcxfRR2C3&jZMfX282m&m{!=UHVAuE~TWvQV$26T$zAf5@P$SjdqCny{Ry@~Wr?OyaA z(WK+H*t;sL-S;YlB-y^>8CTVid}zi3Ka(Cg;YDy233(fHks6S$fNe&r?R7M1*8%l? zfPcZFR}e>$1D`z+Tw*IL6HQU?UGuHd_ijY;FSW+-T0)FB;Q(; z6gxAo^bbx{NGcV6C

OP(JS|VOVDkNy@B2e%a1UN?1YdMf7&@v+)@0ulkGJ zjCFYq>Vb_SG%8IzyVunrgyj3H{>>ww4=-thHw3SN;l-7db=iSa4Ct>@2kK;3mGe`n z%!%oh9|Xz<9DEYx=lQ@H6N*7OX(7o@XD%%?} z-WKLd(*cQcJ9C?hX|H_TMNn5O^;^bzVHU)EN=a_#MQD@Aji51=(poppQF1|B<~r1y zFzl>Bh#h2KYozqmCyhtPZCB!C@+Om!Ck-5nBMPPypza{~7_A#>)8xqW-eZ-P!^Wr! zRvH*+5So=?QMR|~0Z&M$>twSfZ`C)Lievuk2199f~2W#gc+{Npt7%)#4JClZTHczxq9N5^bjo6wvb2)&RaBEJkl1!up1F2>Twk$2uU&WwUtfeRN zRBZFMZ!jAUlKbyo5#wM?Mxfc5Rubh_{-zjhsx);UG_D=Il>nvSQ)MFQq=cfKBl+uo z6!I7TxRs#M8S^OtDAL3(?-lh@#ugGQSRDs3XJaBhJAam5D+uQGSfb35d?PFv;?sUK z_A@yO*LcRb^n1tQnA3kUk17FblUbhrRLZ;FM4tDTg4l1EeT(arH59@#38jNzf->M) zlQh8)$|VXRbv|*C4+9bUe{%td%8LtVlNA08MVQk8fs3)etd`#qlfR<~`?LUL-ft{9 zD*WfK0n}Fo5eQt^9cAzT2HXFRQ1E;J0vDxIGUflM+rKad1TM^HU8(E-5oGCq{t*nz zJCQZ}yUX7Z4?wNceMW57pi}KCSqqjD)Lj^H zPZV`P`))!wMM*TeuF0TQBNTu|SG848+aSmW!@7S#{#FFldS{sO{HWqvxhsR#ifSnN zUC_lr_*;RSVY>H-*E7_i8oMnMqS2n7E$E24$nOss@%q%KpPuGLM^C>RCPI+yS32Sf zhNZCKZeZStV4&=A!-w0xVF3&c-+=dy70b*d`#uENg}3)}(2JDw@!nW|5+QUelS&-% zU`MExxaaUV@PDHRy7n0v82Bg-tA7-fFL?J5e=9t8dNy#jLeMMeQ4e-imo2foo-EfM zZw~pfD}#5Y%4yCA7YbO%yL)Z{9e7T2E4>9F8?jkV-0fNjcpSN z@Lcnlh!4<#pj_uYJ<1pD`v=0D&?z*!G@E--i2%=e<%b~9K*s!Xg74{3$V4AwK(a*T zM9M;I_xce6Joik65K|tUk1B_MPZoP600{ECfU#hiKa}WhR0)9RP%1#wd7;XLIb8P~ zFjj9s7z3HO()li;ahC))+br=v7l|A-B5Hr6sJnQ%@x{MWRTQ+`y#KV<5B&JmxF*gS zbM3&hHxG&ncUHorBEXJ^*WGD~r-xG`jfYo0>FZlEgk&T4;sJO(h`^VdO%d5`KEq>; zg9{PWlzkadS^?Cl+rJl})x99#x1LKcvz%D_XsX8=kzYPAFv5`DN>{SIC+d9nxqVM^ z0%=4?+E;V)wtQAB9?@nl2uoj%Z+(!yS zngHEwOXhVkr?!ltSHtFUv@FNWzLheu9elP`!W3hk8L07WHH);}d(mX?$Q)a=Jb7^# zhU<6}z@nJ9aHNUdAMf#gUY@)Ne`O~w0Ui(I$4igP&k-6PUa$s~2H-v~8M2`|&7oWrpw+VwOH-yU`^ zM9tQ#Er|_x<4Dg$E}y5O=Z@?&p*ekiJ@h!)eE=@IaQ}Rfv7-?~d|v*e`?}yNBx--y zNB{g>k+aC1%4qE=BMo~Z|FjcGRHcKEE0Ea8vbMw6<@Z9kDYbC8(o=T?0bvz9dL>4a6C-7V<(H}vyu)j_zJb{-=c5orJ!X`qw%I-#kwTfR5mTHbBEiyP>62lSW+WRP-{IEwNMr{nDe8y_` zzymJfM;&3AEqi!WKg-L5xku?>96a#bj)!BsjJ}bn&;g@Hmdgj8RuR9Hritg0ql1jL zZQUok4o(i$lmh25+GgU=DKpuB*%iLHovwyxo@r^s?x(f$YTV;2{XBw-iha2FRchd* zF~XE9!q8Z!I4zbmc#i5o$eco3w-N4sKtf9qiUh*6=$y-n8EvZpQ$^{ZhQ4-CGIWA* z?Z<|K6KQ7DeoITr@YzHsDI`i$9b4RCtq`?9O^A9Bf`V!uYCj@+w=8^HeDW#{)h;B| z_@Pc4tCb+)l7X-#@eZ;*E-nSHb8<)wWWPN`ohVgg<#L7uH(cowCaI*gtBaaV2;%W^ z;UeE~NS#D6BlLezZ(R_VU*jpW5 zsI+t=LDO-pWv9OASU}>{x%oqt*|p4=z^`<7>u)(GB?Cj}bnPLL0b6~)nP5(FVmoN( z6ixoaUpI|c=&VpToQ79T$BOBwW!BazSVuQ#O&`X`Ao4Gy%?y57c2R>odV4?cq1c=D z>T>ogDL?<-IXz-awQ+j8>&&Qs16_PPMaX`Wc_pEAMi$b+DUeg_wHIcF>u4~XZ-1Ik z58<*urJE@7w0lBPjP1uV+n?Tl8D0g#G*O+sK9_oOr+y14@KrWow(}1<>5$1A+#lZK ziT$E%=S{H5yo*aD`E*29`_rInDmTKsiP_5dP~%u&`SMdDJj!Z)iNELZ4PP=rV8l2gTLF;a>mcmSz%VzzxOLrOWgbY*5Z+}#VzUa zwdX3?-G-$LYy|(Ow@AQV@HIqFx@WBL09z}*shg}KQ8`G~?tX(Zg!8O91_;o|gT!2Z zcZ-%Iu(bkv<@X}MruhEbrWioG{YG3e2my~{s2_o|zsp{IB@vG;%Dq9>gpXe3ddA!t zLbwK4Rn;V&xm#~XJjUe23S;gBl1U8g{AB>nP;IkDNkfxvHeE4e)6Xm3F1~Va2*A%s zFHBoBuJp#S{QC84AV)4&I86Fxl5(5to*Fch$**=XAPk=^j2{$S0fBfQ9v)8TmCW5! zwx56Zcwk{+@$lipS9IUxM?PK|Tve7s@5ww4@^>a7U^c0k;RC4W@BV%pbUbeXL-z%c zN&TPyCS4!`3|)eu&>Y`8k^KHOpc#^Y!4C75m&L!+l6$N0_R`TGAQ}0wLUQg+mVb5A z^a3Cm@up(RJ-e6GtvK$D8e+g!Cqm9P{&(B=w%TLBR+ltA8E0g*6Klk^o^S6yq0Wsa zUKpD@3dZ!(^*bo&da6XL`22Z>6u;YTN#F;w!wIW8?&CrNtF}+i=R63K@ z#mDfI_{t)_bDFG+D2-r{j69@sFWba!879$or0-p0S8i@;sM}H9K!02LY5I*A{@i9Y zQF-KNxq#T(vq;{=_=jHb2OLt?Y5v1l%Y>hQb&V*s@wvOX0J(L$OF-G=qzLst4%Ql| zd;E$DqC8ot50c?XPTPqH<9UfL5Ktn0vO|})vw)p8H7`sO`IE^`6(wnvoTZ}23@;1C z0yYu~c-`pBLx@!AY;GMCq1fm@V^>wwd!vsrq$?7azDjbaE5|qS$KV2@Ew6P1K6DMD z@W-)4tPHpDGXv{5R;GaA`Xwh5Yf0r9L-XmbnRVIVcYP!}LPg7!9v@b+0wmF4TcjNz z(+o<62#)c+p9@MfzbZ3L8Vxf=u#Tn@X%XlrC^8=A;~eLZLeunDUlK6Ht61ui{tFdk4asfA?+7f~uXp-I`+Z)Aiwlc>U0j1% zAQ!e>-)p`6ThzN%+KTkDu-`aP9camc$1tR?f(D1gpr~L?C`&#SpNtSusu1sP z=h-D@?EIVVCY)02!NH#sKi7olBQkZZQbkx_JoU5e|H2cUY2o){gO(6S;ES8M5xUt$ zJM+ca$6qQ>Wrj~buFeNlWiuTgFlfUPW!Wzj>QP3R!j-)(6fvv>U4}|XLq_z@B4G*8 z8u-)&9uLt%>`YYT#qMA+btRQveuGb$x)hDG+NmZXXb5@ufPwJNn; zQpeCpa&<$m@rLGRyEdTE5?L2h5wKd>xDfw^Ia1)Zu;E&=cS_&SNwRmx_=Nrb^dGIO z{6C8go zx6CS>X6LhBVw*r6@1li1tKON>gmIQ$+xiES=+bmZB_W3oE410`czopM^h;KSH77&C zy~=z`bJof8^5DaOfQexXf;f^SlJ@anr>hQd3{c3@J-x?;ij2aF*yxQwPWhD1Gfv>( zFmo{t^!`s@#gk49wB0!grz5^`o{=P%=;*c(fsAgDD~gQ#qBOKKzlm_lKa(!Y@M0-oY>-i<%|UZ zGl0W_TP@9DSXq(m9f{6|?wW3dAXDb*WD*?uH6<6LvB${Trx zXK}oFPHs=OB#(8QU6{NT(4}`sD!1GUa7AZN&V^RhIl?y&;o|nNrD+J?+PHy%s_8$2 zMT1iddtRuBHMo}nLp5TBlemG|zjVNBw6)_}c!bV!5;lUz_neuUz{2KguP2wgf}s*F zJ4&JJ42WCMc4<3wDgx9owQA6WeWU|9RR5~0dmO~>Ocn4<89%-48TC#5@P0R=#idC( zQTfqcRqkwz>eW2X0{4T$J-$cW9=`dI5E6faYn+PowIa#Wt83#o9M7lY4DdWHIz2cu zpUxlI)SdHr9Dz60)V`TKRKL#0^Qo;QNyPIq(^xUge0hU%CC@ollf3SwkYbS3dEmPs zRLAAoe=t|;wSUf@lC?Dn1sGN0mLA-Ij5v-Rp0;v?gefDsF=vEhaa8(H@l zu!C&@XZbkA%k#J|0SLY%)sPH}MlJD8y5QN4VscnV*vR44ndzHFkyHA7BPsVt*a!L0 zai-{bF&luVlYvj;Xw`LMBI8~gq5*xb!$PG~WAzS`TJauPS95wlC`_-47ZO&hFd;18#cq@zrb8JXY z5@PUII0R1kZV;!#WXjJEfwX7L2=zaXY#HdiwamE2%&<&*_04t(P?F0(b@@f_)H1b9 za8@9zP9+EqC2($owkDJ~j4c^!haH}u=b%fUhH_CvG$vB6hK~yI*M58&9B3tWT8>$& zr$lXG&f85UgXX1A&S`%-o6wpwTJ7qbHgfISUx4oAk2{xuG8a7S7PQeYE-bLO4>tZl zvcJ<$W@bnKGDhbzWL|Hlqj%Zj}dw1## zQusKx{pk*H0+N@TwJ7iN#Y;E4&IUPfpZ?UAlf6T3m!DtF@tV)UAavGSjqic{SN06% zqgmeIZ^nm2O)u%yiY8JLjJMBmIXIHPpU}6qjs`(q$(XgKe6Bx;N+9Dsrgq?U^v=CW zc+3m>Sx`G>Wj9`Hd1F}TEX194Vc)jJx7ws2v(0_w4E)j-rN*hoqMdSQtlDvw@0Byi zV;G!MZ+|lDnc~y)*0O^WziJe(&e`7U^IEan(PMSVy07DnIoP5*Cqxcs7R#IS()%@g zPMw@kQqAcj)b#`II@e0g3m1gQ`49zu5!1Dbx9vYRku~Unb!vNBe)J9yxSvMfqxhG7 zMwBrn8H^Dg{=aS96ZOT?u}nt$x@ zj*DHMnc(fw0$yFi%Z*rxi&2UR<2SAMB9t6 zVf2kxssak$6+yQW^_sG>_0to4#5B34FA)myn{B# zNxp?n>Bb8@`A4YiHQ>gpJ^JJG?=A;)0ZAo@$$WqP&pW^&GbaCr6MlCc9|%+vh%iFH z{4YmU19ke4|MUHT9J+{G7nfN)_47aP0N6yqy%PTM{)WteBPO0nC5!eCXaW%9DBHhq zhra;}abcjSLKmwK!hbo{83>6y{liziyZqJ#h>JZg^W)op-r*;b8~+yy^0yRNaex90 zj!ul{)HyGzgiFLYq&{x=Gp^*xvlq(SZUPsK3Qf0(awKB~A3AaQKb z?-~Bfz~}>q6;075s73vWjNIDKtUzVQ1pV6YvVV{3-x{6)Xn3iqz1%-f*B%CHJetMt zq)GpQ5Zpdv7!7=SU&*B0KaU6(zda({s3PEw0sY5J`lbZnGVDG23K@R_Jb-$$iUT^A zIZ03b#~N}srT_2vi1!%1&;R-hUqw{tt?d1L`d|2?nN0UrmjOI@zs=3JFJ6H$hs^7$yyjE zbJdjdm}}kMoO{g4TB*JpN`gaDTCY3tiw8+AERO51pYqPV+KZPgQ229s1LD!IC?L^h z2Ex4v5+b)KktNyBIWgb9Nr_W{nd@4I?usc*GuZQWqR5@XEZujY5*dJ>YVZL1;*9yW zN<#ZNz6hs%Du%1q2KNuK85SarpwMKfq~0bjH>X)*YZhD5)J3z83?7#W)3xWoB0Mb) zUfMV1T=s#0KNhwbwOb)QNg`s?-BclEKMfnfEies&jIE~ca)nPNxaD$K*SJv(ZPpm&Gby-mBc$5YSJQ#oIvv3CPuo8XE4RY=SAUfvoM5OmOuVei+0T8{pqI3#K+Wy4kT$gP% zXx=&DK=Nk0Qkh@;8}G_V-c@mr?AwUTo3x4?7XH40{_fJ7vIck90@dJPsmI6Gh2Fc> zW5)c{c(9m)#P{^)M2G2hS!1k)_}gQAbV&i-P&e6vKQehn0VJ*-hh@%`kdrNVjSE)I zPGmP=$!EWYZFi6=Q}~uNFywU`K^5LkY=sqtA3gskse%LjG=c_B+sXdYG4CtIj;)?R zYZ!&OH0z=-Q-*5|mlqrN*Ejlstmn^OfPRR5cJm%uOwj+TyUyz}QFZmbKIlGcs#52? zEI*cRgGRAF!~ZCA%r<2G%juXZ3aqx>c7C%rTkrxO+H#q@!^7@C#o!~S8VEJ9&1KS_ ziqGnx*#GQ7PzVIHuKYLJPCxhAkcXFyBa9cAR9-hoE&jPWeN5+H_8s}5R0raTbxCZe zolocrKy*+~*mVIh_`Hn4C;QL*hajnpBI0mu>0iv+SbM=p#gZaHlZsz0luU%_QlR`d z0@6VKfPo|w0ERvYl_xD#0;yC%R9_V+jyoH-F*K6*Qw@YD>2{iFW0nDIk<%G%)-9yu zpid0M^p5khxf41~RxgF$bz8`nh15ghu`C0r1;V~V2$*Yx4Z_v$u+n8nj)dZQ#R;je zjTn}D`W*3z1v#^gh)Hq+UMuQVNXaz}31p_Tnjd#n7$=nYfHL#mT0Gs;pu) zTNFMJ-m8D;WmpMK>;?`z660sH|1Rsq4ki2D<6~@rnSqaWAwR>T8}JJ$MW$uTbk>iG z1KnoYcG6*I^{B}IrG ztCj5)!m1e)ll2BqvdJD?c7T1{I*2Ek>e1(_UzJFinJLa(2j#XMfL~z{g=aNSQPK%=NOAQ3kE~zRc|<9J9wf?KMh+R z1x)TAV#wn&Mt}DW54AdSUU#QygV|I^MxoKu5@)* zXuf~c@@i?e4lSYBR!2qq3ny4!F}&1c9~<(+Y3QKsYlGdy)2?UtLx$jK2qxwW*hg;$T9R1?Wr zkJnij8Pigv*N${;_ng5tZyIcT7BAFcaZIwCzahja0&nx2B3u{~mm+FUw}3OM<-xEL zM+cmVCCC9ZnCpg@dK5^aS^26yvOUcG1iclxF}~$J^;P#BC0|;2;?Zo-35V&q%vz|i z*7^{sdJ*Ax%j%OCqMR?fN=Cpg(~u~k1Nlv~PJJoA(j>FeL~q^=nPe;OW21h#N*g(`bZnwjvr;??$?(?u<#1HIkui47`?1+Nj z*%5N(R^i#sM<}?)gYE{0ac9=#8Zdt?AWg{Da`r)&lZ;M6&l-?HHL0#A(j}_V+?}6g zdC3)9+_T2?1AApGj-$FRm-``G5I%X4)4yo}O z3BSvMq(qzxAe(UtbF2CFZdL8Oc8!H7L43j~)wkeojaKtTA$u-c1PB-ucYh>eqKK+ioNzCBKrTzm#R{lk14>g64%C{UTn zXj)JBXxhSnML*_~$~&Eyq@`gt8gS0UfNuif+vQi%xdftWKs+O3JP1M%Y%6BN=3YRE zQwI{76p^;MRL!*F5)K*8aef?RLkb)CODAjmbxe0GoZ+&=@Ug1SPA2cbJT>I}CySlx zQv>JrQpdVtrGDhK0vm8S;|R8Rg$06cV7CNmMw=dKIqg@DOkqtzM*HiseT$$yWDYcQ z^@~l~7#2vfI-5_I$|d%h@xy)`6Xn8b^cvq#u9klDlA%pzE{#RyKJaFKZ^&roRX1H? zWxU~riyO8Bn&M==F@g+-g0IwY#6cZjKE;WS1&>`{HwKpA>dZAq2H0p_jc5cx>`eT+CFENpz<%yN*5nR&^ zM*^-dNvX)kOP78>+tv* zMoK}SE{xt}M_5|qvCaRC-kz%zp&aByH)Sy&e?~inDR!xPB)(;_6SETMsxQ{Hz@oE9 zsvSyf=M)r(Kb+hhOdTD_sL1D}gpHYjj*i^|3rLZ1C)N8r%|iz# zcRl1`w~Hykc8NLB`5&wG!AUyYJ}>|*6HMgpZ^@|VB(0BYHP_)-vQHI4M$vaTmk`j% zcXpT;ti~ZNblBFO&K^g5dhs*dne=lY1l(y}R=~HCHBrJIz~#6S|g>M7bVf?KZq~Qp!_q zjBvZ<^ZTk`z_X~aqg#)a0@%=@5J3U}bmI~j{D%R)*4%6pygJm%%`oMv0 zC_X3OjMID?Q6r=VrN$2Au#A|+G#`;-VL84y$j}WID;GoV{?#NpIdjRiw8WaF(WhI`2@aHlF zxR;h10Kju<#vP^l??&#t&Q1VVa#oGV5%|N2y1krH0DJ_uX$i7FQ7FK1Td)EgT7q#% zPUxTe4B%XxVgkir<||~n{7Iv6 z`jNsjSD{dSs}@6cODOB*Z%+9pDew$zq1mgJp`B#GOhLzkJ2##HXUlH!a?lX1EALem2Rw_~Z( z0{{2-b()?Vwmb~Nk`@#c1k#}3HL$7g3;WE_Y6fOvVPR22HkW?G4C)Q&lQ32)OL=U++uCo8M}Qy?ExsJ1Oz&+#;3H zbH0 z#Ny4qA9CGMqlR_KF+%L_+I-7XM)uW-R571zqcYrp_C`-fc0l7@t0bN1k z$pSHQ{RGFw44}BW-oqXz6jwVRkFAkR`Q{vn_0b@3G{MSI;UlNKRyR8?U9Y-dHP$Me z)i;!xeu8kUqn0N+gb}g$O=U3+As%!HIibpkr1b#q2}GX&z$YzE?!L;ap{^%6u5|zP znRw>*PQS`QWvsUHIoTF7pZ@elPgA|^_29>j%Pb~l!;IHD=+j)hLHz}EuI{_Id1DtF zOmE_u+@{`C(TSYBIeG*FxehW3;9m1i$hYaRm?!VLsPSJ=hfK!3JbN4S{Q9HiS41qs zaRbK2bu0h*yqm;32DB%mJ34n{L;k0l1_Da%$o}p;*XuK6OARzWJg&$TJFBNZH@mNz zq~?;V2~vt>fNVt#%+Tm+pGO)R&Bo!pk>mlBS!y^OG|wbqt_CgzeLg40tch?eaEokL#Ga6<~%+9Ndo-5fsw1@Z~}_jO$y;{LWQNE zXHN_RKosAzJXmTs$!I*CbNTY+OY?Z;xeq>&25lWA%y_Hp(SbS&Ts;1t1E1u%Xd+PwCgm!abxU#{U8e&MDu#&c=uN&T+VGJI8t$g9Wo*%!h^2q{UXBo?mM$EMHu_eORPM=4 zeyFu#T4Nq_9)KZ=d2mu@DyEZl)FTnh5xv)~eVO@s6&lzyZ?_D&B1CIE0jbAdu;QDo zojGulSn09AD!KHt~;7neMD@t#Yr7{mr7YJ`FRhJ9x$_#;rV$Bhkm z``0|6`lB~9{jmyZPTTv=+Vb1&m7`ZPY6odF^O|lWaM{f#q34UOGO?##7x<0k_$&2s zgbNnlBl5M1JWkw_w6<|2##iBG*$KHbv!}pmNnZ++R$@N*4vo4m1YB;E>w<0?Swz3X z8Ff&u+_;A5>eOeUOK@9Cdr+>TPH%oy z>r53fwVvgs|Kee(-wZXLZBOf(yeyUb3pCDCs9C+ol-cJR&t5<@Y9DTgIN$m_sfjYh z_4zRLFhIfg@H_LSnFqGrUw2h%s57rXhd)SoF1Aa{xvB+>^J9;J^v2tzy2%W_N(5Yw z#adbv91o=&_9M-!YncFOvGgwRZCCPVgF;K$_ILB%`#OQR0)cs_kZ+tiimjY+Y zXN`~$JoBj0Aj6K5O3=$QjB^iA1qV$mK*awdAMW)3CCMjq@#{_fk5ga z_vD$Y6;z$ZmB2Mn%1V+ub68$scPsP4z4ayT-13gv{KB3CArG2Z%Ei}-vF7dAT@&tB z3Yz(44!^>Mqk+FNJ3UhPx112zO*Pe;MQJ;pnAN0+q@0mixhVt5(M;?q%1tI~Ps4fg z`8>@pAAB6V9ncYApDm|XcFw;y78YvZfrb2hHYeSIEwG8hwT3v3&rjUiTm8-mHYC5@ zDj}(I;&;+8lm!AD!YdB_5ivVApC)l)a8BN{E{@wPTld#|AGx(jHvtl|@=LnEU$1G)RGGfPCk90AU`#k?_^glDLTVr=p^PTvQ&TYB4c7WoJcWm3)9~k?{{|fr-wQ8PwrowpBIr;8Jxf7yz$SR zO40>iKz8O6hQIffe|s&GfT0f?iI%JXBcgD7`41cOU;ads3?Llday_}<<&f{AKR|ot zlz`^EomZy!(uS)jDn=4d;{u_v8m;u~AOC9m-ty#;yS46Ak63(AzaPg%WR#sDqbm-XcGlYO+LadZB1tv_Y0#Rt*#Vy80MINkXlo^~kzBShD% zP~+0cDreYDxrQJ6Uw;uH{pE3!+V!mY;YTNIRZ)~t=X&G^4 Date: Tue, 31 May 2022 14:33:49 +0300 Subject: [PATCH 07/29] add the insturctions --- docs/components/emails.rst | 123 +++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/docs/components/emails.rst b/docs/components/emails.rst index cbfaf1cf..da991d43 100644 --- a/docs/components/emails.rst +++ b/docs/components/emails.rst @@ -433,3 +433,126 @@ Email stat helpers ------------------ This section is in progress. See ``\Mautic\EmailBundle\Stats\Helper\StatHelperInterface`` + +.. vale off + +Testing Email transports +######################## + +.. vale on + +This document targets software developers who write Email transports based on ``Symfony Mailer`` which is available to Mautic from Mautic 5.0. + +This document describes Manual steps for testing and the items that you need to verify before submitting your PR for approval in case you want to add a new transport. + +Email components +**************** + +Each Email sent out by Mautic includes the following components: + +#. **Email Address:** (``FROM``, ``TO``, ``CC``, ``BCC``, ``REPLY-TO``): ``Unicode`` Email address in this format ``email@example.com`` or ``email+test@example.com``. Make sure that you always use the Unicode email address to accommodate special characters in languages like Arabic, Hebrew, or Chinese. +#. **Email Name:** (``FROM``, ``TO``, ``CC``, ``BCC``, ``REPLY-TO``): ``Unicode`` Human-readable name, make sure that you always use Unicode Email address to accommodate special characters in languages like Arabic, Hebrew, or Chinese. +#. **Subject:** ``Unicode`` string that might include emojis. +#. **Text:** ``Unicode`` string that might include emojis. +#. **HTML:** ``Unicode`` string that might include emojis, in HTML format. +#. **Headers:** ``ANSI`` string pairs, ``Symfony/Mailer`` adds most of the headers, but for some transports, you need to add your own headers, so you can use the methods mentioned here: https://symfony.com/doc/current/mailer.html#message-headers, referenced in this file https://github.com/symfony/symfony/blob/HEAD/src/Symfony/Component/Mime/Header/Headers.php. +#. **Priority:** sets the Email priority based on ``enum`` +#. **Attachments:** a file with a variety of mime types, the file size shouldn't exceed a specific size provided by the transport provider, usually nothing more than 10 MB and go up to 40 MB (for the whole message, including the text, HTML, and anything embedded within the HTML) + +Preparing Mautic for testing +**************************** + +#. Create 10 Contacts with any Email address you need +#. Create a Segment that includes the 10 Contacts + +.. vale off + +Testing Email transport +*********************** + +.. vale on + +In order to test the Email transport you need to go through the following steps: + +Testing the connection +====================== + +Go to Mautic Configuration -> Email Settings -> Click on Test Connection. If the connection works you should see **success** otherwise you should see an **error** + +.. image:: images/test-connection.png + :width: 600 + :alt: Screenshot showing testing the connection + +.. vale off + +Sending a sample Email +====================== + +.. vale on + +From the same screen where you test the connection, you can send a sample Email. Mautic sends the sample Email to the address of the currently logged in Mautic User. Check that the Email arrives. + +.. vale off + +Upload an Asset +=============== + +.. vale on + +Go to Components -> Assets and then upload a sample file and make sure the filename uses one of the Unicode languages - such as Arabic, Russian, German, etc. + +.. vale off + +Create a template Email +======================= + +.. vale on + +Go to Channels -> Emails -> New -> Template Email -> Select Blank Theme +Use the builder to do the following: + +- Embed an image +- Add Unicode text, you can use this "نحن نحب ان نقوم ببناء Mautic" +- Close the builder +- Go to the Advanced tab +- Complete the ``From Name`` & ``From Address``, ``BCC``, ``Reply-To``, ``Add Attachment``, ``custom headers``, and Click on ``Auto Generate`` to create a text version of the Email +- Save the Email and send a sample test, you should get everything you filled + +Create a Segment Email +====================== + +Go to Channels -> Emails -> New -> Segment Email -> Select Blank Theme +Use the builder to do the following: + +- Embed an image +- Add Unicode text, you can use this "نحن نحب ان نقوم ببناء Mautic" +- Close the builder, +- Go to the Advanced tab +- Complete the ``From Name`` & ``From Address``, ``BCC``, ``Reply-To``, ``Add Attachment``, ``custom headers``, and Click on ``Auto Generate`` to create a text version of the Email +- Save the Email and send a sample test, you should get everything you filled + +Send an individual Email +======================== + +Go to the Contacts section and select a Contact, then click Send an Email. You should be able to send an Email directly to that specific Contact's Email address. + +Send a Report Email +=================== + +Create a Report with any data and set it on a schedule, it should send an Email with the Report as an attachment + +Other Email features +==================== + +There are other places like Forget Password: they need to work as well. Please make sure you verify them. + +Testing transport callback +************************** + +Each transport should include a callback URL which Webhooks should be ``POSTed`` to, which marks Contacts who bounce as ``Do Not Contact``. + +To test these callbacks you need to do the following: + +#. Configure an Email transport and make it the default transport +#. Go to the URL on the following format ``/mailer/{transport}/callback`` +#. You should get a message that says ``success`` and there should be a callback logic to handle the Webhook \ No newline at end of file From 1987862802662406e70e2c1d06289d038a27a45d Mon Sep 17 00:00:00 2001 From: Ruth Cheesley Date: Thu, 6 Oct 2022 16:34:39 +0100 Subject: [PATCH 08/29] Fix indentation --- docs/components/emails.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/components/emails.rst b/docs/components/emails.rst index da991d43..53baa80f 100644 --- a/docs/components/emails.rst +++ b/docs/components/emails.rst @@ -262,7 +262,7 @@ To do this, the Plugin needs to add an event listener for three events: } Email transports -**************** +---------------- Mautic supports quite some Email providers out of the box (Amazon Simple Email Service, SendGrid, etc.). If you want to add your own Email transport, that's certainly possible. @@ -555,4 +555,4 @@ To test these callbacks you need to do the following: #. Configure an Email transport and make it the default transport #. Go to the URL on the following format ``/mailer/{transport}/callback`` -#. You should get a message that says ``success`` and there should be a callback logic to handle the Webhook \ No newline at end of file +#. You should get a message that says ``success`` and there should be a callback logic to handle the Webhook From 9218d79d29f9d3b4addbdadff5f94ea340a9a77c Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Mon, 17 Oct 2022 08:52:48 +0530 Subject: [PATCH 09/29] Added ConfigFormNotesInterface page. --- docs/plugins/integrations/configuration.rst | 10 ++- .../integrations/configuration_form_notes.rst | 83 +++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 docs/plugins/integrations/configuration_form_notes.rst diff --git a/docs/plugins/integrations/configuration.rst b/docs/plugins/integrations/configuration.rst index ff369074..065f11f4 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -116,4 +116,12 @@ Contact/Company Syncing Interfaces ---------------------------------- The integrations bundle provides a sync framework for third party services to sync with Mautic's Contacts and Companies. The ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface`` determines the configuration options for this sync feature. Refer to the method DocBlocks in the interface for more details. -Read more about how to leverage the :doc:`sync framework`. \ No newline at end of file +Read more about how to leverage the :doc:`sync framework`. + +Config Form Notes Interface +--------------------------- + +The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``, provides a way to put notes, either info or warning, on the plugin configuration form. + +Read more about to how-tos :doc:`here` +[here](#integration-configuration-form-notes) diff --git a/docs/plugins/integrations/configuration_form_notes.rst b/docs/plugins/integrations/configuration_form_notes.rst new file mode 100644 index 00000000..be5dbd2c --- /dev/null +++ b/docs/plugins/integrations/configuration_form_notes.rst @@ -0,0 +1,83 @@ +.. It is a reference only page, not a part of doc tree. + +:orphan: + +************************************ +Integration Configuration Form Notes +************************************ + +The integration framework lets developer define their custom messages for the plugin's configuration form. + +The ``ConfigSupport`` class should implement the:: + + \Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface + +_____ + + .. code-block:: php + + Date: Mon, 17 Oct 2022 09:26:42 +0530 Subject: [PATCH 10/29] Removed dead reference --- docs/plugins/integrations/configuration.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/plugins/integrations/configuration.rst b/docs/plugins/integrations/configuration.rst index 065f11f4..68417179 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -124,4 +124,3 @@ Config Form Notes Interface The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``, provides a way to put notes, either info or warning, on the plugin configuration form. Read more about to how-tos :doc:`here` -[here](#integration-configuration-form-notes) From 3792327187490b272ebfd55f0d950403658d2c99 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Tue, 18 Oct 2022 18:50:25 +0530 Subject: [PATCH 11/29] Apply suggestions from code review Co-authored-by: Ruth Cheesley --- docs/plugins/integrations/builder.rst | 10 +++---- docs/plugins/integrations/configuration.rst | 18 +++++------ .../integrations/configuration_form_notes.rst | 6 ++-- docs/plugins/integrations/integrations.rst | 30 +++++++++---------- docs/plugins/integrations/migrations.rst | 12 ++++---- docs/plugins/integrations/sync.rst | 30 +++++++++---------- 6 files changed, 53 insertions(+), 53 deletions(-) diff --git a/docs/plugins/integrations/builder.rst b/docs/plugins/integrations/builder.rst index b630d521..3e576b3a 100644 --- a/docs/plugins/integrations/builder.rst +++ b/docs/plugins/integrations/builder.rst @@ -1,16 +1,16 @@ ******************** -Integration Builders +Builder integrations ******************** .. contents:: Table of Contents -Builders can register itself as a "builder" for email and/or landing pages. +Builders can register itself as a "builder" for Email and/or Landing Pages. ---- -Registering the Integration as a Builder +Registering the integration as a builder ======================================== -To tell the IntegrationsBundle that this integration has configuration options, tag the integration or support class with ``mautic.config_integration`` in the plugin's ``app/config.php``. +To tell the IntegrationsBundle that this integration has configuration options, tag the integration or support class with ``mautic.config_integration`` in the Plugin's ``app/config.php``. .. code-block:: php @@ -39,4 +39,4 @@ The ``BuilderSupport`` class must implement:: \Mautic\IntegrationsBundle\Integration\Interfaces\BuilderInterface -The only method currently defined for the interface is ``isSupported`` which should return a boolean if it supports the given feature. Currently, Mautic supports `email` and `page` (landing pages). This will determine what themes should be displayed as an option for the given builder/feature. +The only method currently defined for the interface is ``isSupported`` which should return a boolean if it supports the given feature. Currently, Mautic supports `email` and `page` (Landing Pages). This will determine what themes should be displayed as an option for the given builder/feature. diff --git a/docs/plugins/integrations/configuration.rst b/docs/plugins/integrations/configuration.rst index 68417179..513f4c0c 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -1,14 +1,14 @@ -Integration Configuration +Integration configuration ######################### -.. contents:: Table of Contents +.. contents:: Table of contents -The integration plugin provides interfaces to display and store configuration options that can be accessed through the ``\Mautic\PluginBundle\Entity\Integration`` object. +The Integration Plugin provides interfaces to display and store configuration options that can be accessed through the ``\Mautic\PluginBundle\Entity\Integration`` object. -Register the Integration for Configuration +Register the Integration for configuration ============================================= -To tell the IntegrationsBundle that this integration has configuration options, tag the integration or support class with ``mautic.config_integration`` in the plugin's ``app/config.php``. +To tell the IntegrationsBundle that this Integration has configuration options, tag the integration or support class with ``mautic.config_integration`` in the plugin's ``app/config.php``. .. code-block:: php @@ -74,7 +74,7 @@ Interfaces There are multiple interfaces that can be used to add form fields options to the provided configuration tabs. -Enabled/Auth Tab +Enabled/auth tab ---------------- These interfaces provide the configuration options for authenticating with the third party service. Read more about how to use integrations bundle's [auth providers here](#integration-authentication). @@ -94,7 +94,7 @@ ConfigFormCallbackInterface ^^^^^^^^^^^^^^^^^^^^^^^^^^^ If the integration leverages an auth provider that requires a callback URL or something similar, this interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormCallbackInterface``, provides a means to return a translation string to display in the UI. For example, OAuth2 requires a redirect URI. If the admin has to configure the OAuth credentials in the third party service and needs to know what URL to use in Mautic as the return URI, or callback URL, use the ``getCallbackHelpMessageTranslationKey()`` method. -Feature Interfaces +Feature interfaces ------------------ ConfigFormFeatureSettingsInterface @@ -112,13 +112,13 @@ ConfigFormFeaturesInterface ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Currently the integrations bundle provides default features. To use these features, implement this, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeaturesInterface``, interface. ``getSupportedFeatures`` returns an array of supported features. For example, if the Integration syncs with Mautic Contacts, ``getSupportedFeatures()`` could ``return [ConfigFormFeaturesInterface::FEATURE_SYNC];``. -Contact/Company Syncing Interfaces +Contact/Company syncing interfaces ---------------------------------- The integrations bundle provides a sync framework for third party services to sync with Mautic's Contacts and Companies. The ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface`` determines the configuration options for this sync feature. Refer to the method DocBlocks in the interface for more details. Read more about how to leverage the :doc:`sync framework`. -Config Form Notes Interface +Config form notes interface --------------------------- The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``, provides a way to put notes, either info or warning, on the plugin configuration form. diff --git a/docs/plugins/integrations/configuration_form_notes.rst b/docs/plugins/integrations/configuration_form_notes.rst index be5dbd2c..98675ed1 100644 --- a/docs/plugins/integrations/configuration_form_notes.rst +++ b/docs/plugins/integrations/configuration_form_notes.rst @@ -3,10 +3,10 @@ :orphan: ************************************ -Integration Configuration Form Notes +Integration configuration form notes ************************************ -The integration framework lets developer define their custom messages for the plugin's configuration form. +The integration framework lets developer define their custom messages for the Plugin's configuration form. The ``ConfigSupport`` class should implement the:: @@ -77,7 +77,7 @@ _____ } } -.. admonition:: Additional Information +.. admonition:: Additional information - The trait ``Mautic\IntegrationsBundle\Integration\ConfigFormNotesTrait`` helps define the default ``null``. - Instead of plain string, one can pass the translation key which holds the message. for example ``new Note('helloworld.config.auth_tab', Note::TYPE_INFO);`` diff --git a/docs/plugins/integrations/integrations.rst b/docs/plugins/integrations/integrations.rst index a41da25d..f71b743e 100644 --- a/docs/plugins/integrations/integrations.rst +++ b/docs/plugins/integrations/integrations.rst @@ -1,10 +1,10 @@ ****************************************** -Getting started with Integration Framework +Getting started with the Integration Framework ****************************************** -.. contents:: Table of Contents +.. contents:: Table of contents -The IntegrationsBundle is meant to be a drop in replacement for Mautic's PluginBundle's AbstractIntegration class. It provides cleaner interfaces for configuring, authenticating and syncing contacts/companies with third party integrations. +The IntegrationsBundle is meant to be a drop in replacement for Mautic's PluginBundle's AbstractIntegration class. It provides cleaner interfaces for configuring, authenticating and syncing Contacts/Companies with third party Integrations. An example HelloWorld plugin is available https://github.com/mautic/plugin-helloworld. @@ -13,48 +13,48 @@ An example HelloWorld plugin is available https://github.com/mautic/plugin-hello Using the Integration Framework =============================== -Registering the Integration for Authentication +Registering the Integration for authentication _______________________________________________ -If the integration requires authentication with the third party service +If the Integration requires authentication with the third party service: 1. :ref:`Register the integration` as an integration that requires configuration options. 2. Create a custom Symfony form type for the required credentials and return it as part of the :ref:`config interface`. 3. Create a custom service that builds and configures the Guzzle client required to authenticate and communicate with the third party service. Use an [existing supported factory or create a new one](#authentication-providers). -Registering the Integration for Configuration +Registering the Integration for configuration _____________________________________________ -If the integration has extra configuration settings for features unique to it +If the Integration has extra configuration settings for features unique to it: -1. :ref:`Register the integration` as an integration that requires configuration options. +1. :ref:`Register the integration` as an Integration that requires configuration options. 2. Create a custom Symfony form type for the features and return it as part of the :ref:`config form feature setting interface`. The sync engine ________________ -If the integration syncs with Mautic's contacts and/or companies +If the integration syncs with Mautic's Contacts and/or Companies: 1. Read about :doc:`the sync engine`. Registering the Integration as a Builder ________________________________________ -If the integration includes a builder integration (email or landing page) +If the Integration includes a Builder (Email or Landing Page): 1. :ref:`Register the integration` as an integration that provides a custom builder. -2. Configure what featured builders the integration supports (Mautic currently supports "email" and "page" builders). +2. Configure what featured builders the integration supports (Mautic currently supports 'Email' and 'Page' builders). Basics ====== -Each integration provides its unique name as registered with Mautic, icon, and display name. When an integration is registered, the integration helper classes will manage the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the integration's API keys so the implementing code never has to. +Each Integration provides its unique name as registered with Mautic, an icon, and a display name. When an Integration is registered, the Integration helper classes will manage the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the Integration's API keys so the implementing code never has to. -Registering the Integration +Registering the integration ___________________________ -All integrations whether it uses the config, auth or sync interfaces must have a class that registers itself with Mautic. The integration will be listed no the ``/s/plugins`` page. +All Integrations whether using the config, auth or sync interfaces must have a class that registers itself with Mautic. The Integration will be listed on the ``/s/plugins`` page. -In the plugin's ``Config/config.php``, register the integration using the tag ``mautic.basic_integration``. +In the Plugin's ``Config/config.php``, register the Integration using the tag ``mautic.basic_integration``. .. code-block:: php diff --git a/docs/plugins/integrations/migrations.rst b/docs/plugins/integrations/migrations.rst index 9be1915e..4dd41430 100644 --- a/docs/plugins/integrations/migrations.rst +++ b/docs/plugins/integrations/migrations.rst @@ -1,17 +1,17 @@ ************* -Plugin Schema +Plugin schema ************* -.. contents:: Table of Contents +.. contents:: Table of contents -The integration framework provides a means for plugins to better manage their schema. Queries are in migration files that match the plugin's versions number in it's config. When the a plugin is installed or upgraded, it will loop over the migration files up to the latest version. +The Integration Framework provides a means for Plugins to better manage their schema. Queries are in migration files that match the Plugin's versions number in its config. When the a Plugin is installed or upgraded, it will loop over the migration files up to the latest version. ____ AbstractPluginBundle ==================== -The plugin's root bundle class should extend:: +The Plugin's root bundle class should extend: MauticPlugin\IntegrationsBundle\Bundle\AbstractPluginBundle @@ -30,10 +30,10 @@ The plugin's root bundle class should extend:: } -Plugin Migrations +Plugin migrations ----------------- -Each migration file should be stored in the plugin's ``Migration`` folder with a name that matches ``Version_X_Y_Z.php`` where ``X_Y_Z`` matches the semantic versioning of the plugin. Each file should contain the incremental schema changes for the plugin up to the latest version which should match the version in the plugin's ``Config/config.php`` file. +Each migration file should be stored in the Plugin's ``Migration`` folder with a name that matches ``Version_X_Y_Z.php`` where ``X_Y_Z`` matches the semantic versioning of the Plugin. Each file should contain the incremental schema changes for the Plugin up to the latest version which should match the version in the Plugin's ``Config/config.php`` file. There are two methods. ``isApplicable`` should return true/false if the migration should be ran. ``up`` should register the SQL to execute. diff --git a/docs/plugins/integrations/sync.rst b/docs/plugins/integrations/sync.rst index a40535d6..14de2945 100644 --- a/docs/plugins/integrations/sync.rst +++ b/docs/plugins/integrations/sync.rst @@ -1,12 +1,12 @@ ************************** -Integration Sync Engine +Integration sync engine ************************** -.. contents:: Table of Contents +.. contents:: Table of contents -The sync engine supports bidirectional syncing between Mautic's contact and companies with third party objects. The engine generates a "sync report" from Mautic that it converts to a "sync order" for the integration to process. It then asks for a "sync report" from the integration which it converts to a "sync order" for Mautic to process. +The sync engine supports bidirectional syncing between Mautic's Contact and Companies with third party objects. The engine generates a "sync report" from Mautic that it converts to a "sync order" for the integration to process. It then asks for a "sync report" from the integration which it converts to a "sync order" for Mautic to process. -When building the report, Mautic or the integration will fetch the objects that have been modified or created within the specified timeframe. If the integration supports changes at the field level, it should tell the report on a per field basis when the field was last updated. Otherwise, it should tell the report when the object itself was last modified. These dates are used by the "sync judge" to determine which value should be used in a bi-directional sync. +When building the report, Mautic or the Integration will fetch the objects that have been modified or created within the specified timeframe. If the Integration supports changes at the field level, it should tell the report on a per field basis when the field was last updated. Otherwise, it should tell the report when the object itself was last modified. These dates are used by the "sync judge" to determine which value should be used in a bi-directional sync. The sync is initiated using the ``mautic:integrations:sync`` command. For example:: @@ -14,9 +14,9 @@ The sync is initiated using the ``mautic:integrations:sync`` command. For exampl ------ -Registering the Integration for the Sync Engine +Registering the Integration for the sync engine =============================================== -To tell the IntegrationsBundle that this integration provides a syncing feature, tag the integration or support class with ``mautic.sync_integration`` in the plugin's ``app/config.php``. +To tell the IntegrationsBundle that this Integration provides a syncing feature, tag the Integration or support class with ``mautic.sync_integration`` in the Plugin's ``app/config.php``. .. code-block:: php @@ -50,28 +50,28 @@ To tell the IntegrationsBundle that this integration provides a syncing feature, Syncing ======= -The Mapping Manual +The mapping manual __________________ -The mapping manual tells the sync engine which integration should be synced with which Mautic object (contact or company), the integration fields that were mapped to Mautic fields, and the direction the data is supposed to flow. +The mapping manual tells the sync engine which Integration should be synced with which Mautic object (Contact or Company), the Integration fields that were mapped to Mautic fields, and the direction the data is supposed to flow. See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/Mapping/Manual/MappingManualFactory.php -The Sync Data Exchange +The sync data exchange ______________________ -This is where the sync takes place and is executed by the ``mautic:integrations:sync`` command. Mautic and the integration will build their respective reports of new or modified objects then execute the order from the other side. +This is where the sync takes place and is executed by the ``mautic:integrations:sync`` command. Mautic and the Integration will build their respective reports of new or modified objects then execute the order from the other side. See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/SyncDataExchange.php -Building Sync Report +Building sync report ____________________ -The sync report tells the sync engine what objects are new and/or modified between the two timestamps given by the engine (or up to the integration discretion if it is a first time sync). Objects should be processed in batches which can be done using the ``RequestDAO::getSyncIteration()``. The sync engine will execute ``SyncDataExchangeInterface::getSyncReport()`` until a report comes back with no objects. +The sync report tells the sync engine what objects are new and/or modified between the two timestamps given by the engine (or up to the Integration's discretion if it is a first time sync). Objects should be processed in batches which can be done using the ``RequestDAO::getSyncIteration()``. The sync engine will execute ``SyncDataExchangeInterface::getSyncReport()`` until a report comes back with no objects. -If the integration supports field level change tracking, it should tell the report so that the sync engine can merge the two data sets more accurately. +If the Integration supports field level change tracking, it should tell the report so that the sync engine can merge the two data sets more accurately. See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/ReportBuilder.php -Executing the Sync Order +Executing the sync order ________________________ -The sync order contains all the changes the sync engine has determined should be written to the integration. The integration should communicate back the ID of any objects created or adjust objects as needed such as if they were converted from one to another or deleted. +The sync order contains all the changes the sync engine has determined should be written to the Integration. The Integration should communicate back the ID of any objects created or adjust objects as needed such as if they were converted from one to another or deleted. See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/OrderExecutioner.php From 4b0d80e21eaca2bc4f4cb7c8c6b1dec43125cf0c Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Tue, 25 Oct 2022 15:17:34 +0530 Subject: [PATCH 12/29] Updated structure for docs --- docs/plugins/integrations/authentication.rst | 123 +++++++++++-------- docs/plugins/integrations/builder.rst | 11 +- docs/plugins/integrations/configuration.rst | 44 ++++--- docs/plugins/integrations/index.rst | 4 +- docs/plugins/integrations/integrations.rst | 51 ++++---- docs/plugins/integrations/migrations.rst | 10 +- docs/plugins/integrations/sync.rst | 32 ++--- 7 files changed, 155 insertions(+), 120 deletions(-) diff --git a/docs/plugins/integrations/authentication.rst b/docs/plugins/integrations/authentication.rst index c1aaca21..3f861ea0 100644 --- a/docs/plugins/integrations/authentication.rst +++ b/docs/plugins/integrations/authentication.rst @@ -1,16 +1,21 @@ ************************** -Integration Authentication +Integration authentication ************************** -.. contents:: Table of Contents +.. contents:: Table of contents -The integrations bundle provides factories and helpers to create Guzzle Client classes for common authentication protocols. +The IntegrationsBundle provides factories and helpers to create Guzzle Client classes for common authentication protocols. ---------- -Registering the Integration for Authentication -============================================== -If the integration requires the user to authenticate through the web (OAuth2 three legged), the integration needs to tag a service with ``mautic.auth_integration`` to handle the authentication process (redirecting to login, request the access token, etc). This service will need to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface``. +Registering the Integration for authentication +############################################## + +If the Integration requires the User to authenticate through the web (OAuth2 three legged), the Integration needs to tag a service with ``mautic.auth_integration`` to handle the authentication process (redirecting to login, request the access token, etc). + +This service needs to implement:: + + \Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface. .. code-block:: php @@ -101,11 +106,16 @@ The ``AuthSupport`` class must implement ``\Mautic\IntegrationsBundle\Integratio } } -Authentication Providers ------------------------- -The integrations bundle comes with a number of popular authentication protocols available to use as Guzzle clients. New ones can be created by implementing ``\Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface.`` +Authentication providers +************************ + +The Integration bundle comes with a number of popular authentication protocols available to use as Guzzle clients. New ones should implement:: + + \Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface. -**The examples below use anonymous classes. Of course, use OOP with services and factories to generate credential, configuration, and client classes.** The best way to get configuration values such as username, password, consumer key, consumer secret, etc is by using the ``mautic.integrations.helper`` (``\Mautic\IntegrationsBundle\Helper\IntegrationsHelper``) service in order to leverage the configuration stored in the ``Integration`` entity's API keys. +**The examples below use anonymous classes. Of course, use Object Oriented Programming with services and factories to generate credential, configuration, and client classes.** + +The best way to get configuration values such as username, password, consumer key, consumer secret, etc is by using the ``mautic.integrations.helper`` ``(\Mautic\IntegrationsBundle\Helper\IntegrationsHelper)`` service to leverage the configuration stored in the ``Integration`` entity's API keys. .. code-block:: php @@ -125,14 +135,17 @@ The integrations bundle comes with a number of popular authentication protocols //... -API Key -^^^^^^^ +API key +======= + Use the ``mautic.integrations.auth_provider.api_key`` service (``\Mautic\IntegrationsBundle\Auth\Provider\ApiKey\HttpFactory``) to obtain a ``GuzzleHttp\ClientInterface`` that uses an API key for all requests. Out of the box, the factory supports a parameter API key or a header API key. -Parameter Based API Key -""""""""""""""""""""""" +Parameter based API key +----------------------- + +To use the parameter based API key, create a credentials class that implements:: -To use the parameter based API key, create a credentials class that implements ``\Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\ParameterCredentialsInterface``. + \Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\ParameterCredentialsInterface. .. code-block:: php @@ -171,8 +184,9 @@ To use the parameter based API key, create a credentials class that implements ` $response = $client->get('https://api-url.com/fetch'); -Header Based API Key -"""""""""""""""""""" +Header based API key +-------------------- + .. code-block:: php get('https://api-url.com/fetch'); -Basic Auth -^^^^^^^^^^ +Basic auth +========== + Use the ``mautic.integrations.auth_provider.basic_auth`` service (``\Mautic\IntegrationsBundle\Auth\Provider\BasicAuth\HttpFactory``) to obtain a ``GuzzleHttp\ClientInterface`` that uses basic auth for all requests. .. code-block:: php @@ -255,15 +270,17 @@ Use the ``mautic.integrations.auth_provider.basic_auth`` service (``\Mautic\Inte OAuth1a -^^^^^^^ -OAuth1a Three Legged -"""""""""""""""""""" +======= + +OAuth1a three legged +-------------------- This has not been implemented yet. -OAuth1a Two Legged -"""""""""""""""""" -OAuth1A two legged does not require a user to login as would three legged. +OAuth1a two legged +------------------ + +OAuth1a two legged does not require a User to login as would three legged. .. code-block:: php @@ -336,20 +353,23 @@ OAuth1A two legged does not require a user to login as would three legged. $response = $client->get('https://api-url.com/fetch'); OAuth2 -^^^^^^ +====== + Use the OAuth2 factory according to the grant type required. ``\Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\HttpFactory`` supports ``code`` and ``refresh_token`` grant types. ``\Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\HttpFactory`` supports ``client_credentials`` and ``password``. The OAuth2 factories leverages https://github.com/kamermans/guzzle-oauth2-subscriber as a middleware. -Client Configuration -"""""""""""""""""""" +Client configuration +-------------------- + Both OAuth2 factories leverage ``\Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface`` object to configure things such as configuring the signer (basic auth, post form data, custom), token factory, token persistence, and token signer (bearer auth, basic auth, query string, custom). Use the appropriate interfaces as required for the use case (see the interfaces in ``plugins/IntegrationsBundle/Auth/Support/Oauth2/ConfigAccess``). See https://github.com/kamermans/guzzle-oauth2-subscriber for additional details on configuring the credentials and token signers or creating custom token persistence and factories. -Integration Token Persistence -""""""""""""""""""""""""""""" -For most use cases, a token persistence service to fetch and store the access tokens generated by using refresh tokens, etc will be required. The integrations bundle provides one that natively uses the ``\Mautic\PluginBundle\Entity\Integration`` entity's API keys. Anything stored through the service is automatically encrypted. +Token persistence +----------------- + +For most use cases, a token persistence service to fetch and store the access tokens generated by using refresh tokens, etc are required. The IntegrationBundle provides one that natively uses the ``\Mautic\PluginBundle\Entity\Integration`` entity's API keys. Anything stored through the service is automatically encrypted. Use the ``mautic.integrations.auth_provider.token_persistence_factory`` service (``\Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\TokenPersistenceFactory``) to generate a ``TokenFactoryInterface`` to be returned by the ``\Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenFactoryInterface`` interface. @@ -382,11 +402,12 @@ Use the ``mautic.integrations.auth_provider.token_persistence_factory`` service } }; -The token persistence service will automatically manage ``access_token``, ``refresh_token``, and ``expires_at`` from the authentication process which are stored in the ``Integration`` entity's API keys array. +The token persistence service automatically manages ``access_token``, ``refresh_token``, and ``expires_at`` from the authentication process and stores them in the ``Integration`` entity's API keys array. + +Token factory +------------- -Token Factory -""""""""""""" -In some cases, the third party service may return additional values that are not traditionally part of the oauth2 spec and these values are required for further communication with the API service. In this case, the integration bundle's ``\Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\IntegrationTokenFactory`` can be used to capture those extra values and store them in the ``Integration`` entity's API keys array. +In some cases, the third party service may return additional values that are not traditionally part of the OAuth2 spec and these values are required for further communication with the API service. In this case, the integration bundle's ``\Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\IntegrationTokenFactory`` can be used to capture those extra values and store them in the ``Integration`` entity's API keys array. The ``IntegrationTokenFactory`` can then be returned in a ``\Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenFactoryInterface`` when configuring the ``Client``. @@ -413,12 +434,13 @@ The ``IntegrationTokenFactory`` can then be returned in a ``\Mautic\Integrations } }; -OAuth2 Two Legged -^^^^^^^^^^^^^^^^^ +OAuth2 two legged +================= + +Password grant +-------------- -Password Grant -"""""""""""""" -Below is an example of the password grant for a service that uses a scope (optional interface). The use of the token persistence is assuming the access token is valid for a period of time (that is an hour). +Below is an example of the password grant for a service that uses a scope (optional interface). The use of the token persistence is assuming the access token is valid for a period of time (that is an hour). .. code-block:: php @@ -503,9 +525,10 @@ Below is an example of the password grant for a service that uses a scope (optio $client = $factory->getClient($credentials, $config); $response = $client->get('https://api-url.com/fetch'); -Client Credentials Grant -"""""""""""""""""""""""" -Below is an example of the client credentials grant for a service that uses a scope (optional interface). The use of the token persistence is assuming the access token is valid for a period of time (that is an hour). +Client credentials grant +------------------------ + +Below is an example of the client credentials grant for a service that uses a scope (optional interface). The use of the token persistence is assuming the access token is valid for a period of time (that is an hour). .. code-block:: php @@ -576,11 +599,14 @@ Below is an example of the client credentials grant for a service that uses a sc $client = $factory->getClient($credentials, $config); $response = $client->get('https://api-url.com/fetch'); -OAuth2 Three Legged -^^^^^^^^^^^^^^^^^^^ +OAuth2 three legged +=================== + Three legged OAuth2 with the code grant is the most complex to implement because it involves redirecting the user to the third party service to authenticate then sent back to Mautic to initiate the access token process using a code returned in the request. -The first step is to register the integration as a :ref:`\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface`. The ``authenticateIntegration()`` method will be used to initiate the access token process using the ``code`` returned in the request after the user logs into the third party service. The integrations bundle provides a route that can be used as the redirect or callback URIs through the named route ``mautic_integration_public_callback`` that requires a ``integration`` parameter. This redirect URI can be displayed in the UI by using [`ConfigFormCallbackInterface`](https://github.com/mautic/plugin-integrations/wiki/2.-Integration-Configuration#mauticpluginintegrationsbundleintegrationinterfacesconfigformcallbackinterface). This route will find the integration by name from the ``AuthIntegrationsHelper`` then execute its ``authenticateIntegration()``. +The first step is to register the integration as a :ref:`\\Mautic\\IntegrationsBundle\\Integration\\Interfaces\\AuthenticationInterface`. The ``authenticateIntegration()`` method initiates the access token process using the ``code`` returned in the request after the user logs into the third-party service. The Integration bundle provides a route that can use as the redirect or callback URIs through the named route ``mautic_integration_public_callback`` that requires a ``integration`` parameter. This redirect URI can display in the UI by using ConfigFormCallbackInterface_. This route is to find the integration by name from the ``AuthIntegrationsHelper``and then execute its ``authenticateIntegration()``. + +.. _ConfigFormCallbackInterface: https://github.com/mautic/mautic/blob/5.x/app/bundles/IntegrationsBundle/Integration/Interfaces/ConfigFormCallbackInterface.php .. code-block:: php @@ -610,9 +636,9 @@ The first step is to register the integration as a :ref:`\Mautic\IntegrationsBun } } -The trick here is that the ``Client``'s ``authenticate`` method will configure a ``ClientInterface`` then make a call to any valid API URL (*this is required*). By making a call, the middleware will initiate the access token process and store it in the ``Integration`` entity's API keys through the :ref:`TokenPersistenceFactory`. The URL is recommended to be something simple like a version check or fetching info for the authenticated user. +The trick here is that the ``Client``'s ``authenticate`` method configures a ``ClientInterface`` and then calls to any valid API URL (*this is required*). The middleware initiates the access token process by making a call and storing it in the ``Integration`` entity's API keys through :ref:`TokenPersistenceFactory`. The URL is recommended to be something simple, like a version check or fetching info for the authenticated User. -Here is an example of a client, assuming that the user has already logged in and the code is in the request. +Here is an example of a client, assuming that the User has already logged in and the code is in the request. .. code-block:: php @@ -724,4 +750,3 @@ Here is an example of a client, assuming that the user has already logged in and /** @var $factory HttpFactory */ $client = $factory->getClient($credentials, $config); $response = $client->get('https://api-url.com/fetch'); - diff --git a/docs/plugins/integrations/builder.rst b/docs/plugins/integrations/builder.rst index 3e576b3a..7c0389de 100644 --- a/docs/plugins/integrations/builder.rst +++ b/docs/plugins/integrations/builder.rst @@ -2,15 +2,16 @@ Builder integrations ******************** -.. contents:: Table of Contents +.. contents:: Table of contents Builders can register itself as a "builder" for Email and/or Landing Pages. ---- -Registering the integration as a builder -======================================== -To tell the IntegrationsBundle that this integration has configuration options, tag the integration or support class with ``mautic.config_integration`` in the Plugin's ``app/config.php``. +Registering the Integration as a builder +######################################## + +To tell the IntegrationsBundle that this Integration has configuration options, tag the Integration or support class with ``mautic.config_integration`` in the Plugin's ``app/config.php``. .. code-block:: php @@ -39,4 +40,4 @@ The ``BuilderSupport`` class must implement:: \Mautic\IntegrationsBundle\Integration\Interfaces\BuilderInterface -The only method currently defined for the interface is ``isSupported`` which should return a boolean if it supports the given feature. Currently, Mautic supports `email` and `page` (Landing Pages). This will determine what themes should be displayed as an option for the given builder/feature. +The only method currently defined for the interface is ``isSupported`` which should return a boolean if it supports the given feature. Currently, Mautic supports ``email`` and ``page`` (Landing Pages). This determines what themes should be displayed as an option for the given builder/feature. diff --git a/docs/plugins/integrations/configuration.rst b/docs/plugins/integrations/configuration.rst index 513f4c0c..c7b42681 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -1,14 +1,15 @@ +************************* Integration configuration -######################### +************************* .. contents:: Table of contents The Integration Plugin provides interfaces to display and store configuration options that can be accessed through the ``\Mautic\PluginBundle\Entity\Integration`` object. -Register the Integration for configuration -============================================= +Registering the Integration for configuration +############################################# -To tell the IntegrationsBundle that this Integration has configuration options, tag the integration or support class with ``mautic.config_integration`` in the plugin's ``app/config.php``. +To tell the IntegrationsBundle that this Integration has configuration options, tag the Integration or support class with ``mautic.config_integration`` in the Plugin's ``app/config.php``. .. code-block:: php @@ -70,18 +71,19 @@ The ``ConfigSupport`` class must implement ``\Mautic\IntegrationsBundle\Integrat Interfaces -========== +********** -There are multiple interfaces that can be used to add form fields options to the provided configuration tabs. +There are multiple interfaces that can be used to add Form Fields options to the provided configuration tabs. Enabled/auth tab ----------------- -These interfaces provide the configuration options for authenticating with the third party service. Read more about how to use integrations bundle's [auth providers here](#integration-authentication). +================ + +These interfaces provide the configuration options for authenticating with the third party service. Read more about how to use IntegrationsBundle's [auth providers here](#integration-authentication). ConfigFormAuthInterface -^^^^^^^^^^^^^^^^^^^^^^^ +----------------------- -Used in the example above. This, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormAuthInterface``, interface provides the Symfony form type class that defines the fields to be stored as the API keys. +Used in the example preceding. This, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormAuthInterface``, interface provides the Symfony Form type class that defines the fields to be stored as the API keys. .. code-block:: PHP @@ -91,15 +93,17 @@ Used in the example above. This, ``\Mautic\IntegrationsBundle\Integration\Interf ConfigFormCallbackInterface -^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If the integration leverages an auth provider that requires a callback URL or something similar, this interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormCallbackInterface``, provides a means to return a translation string to display in the UI. For example, OAuth2 requires a redirect URI. If the admin has to configure the OAuth credentials in the third party service and needs to know what URL to use in Mautic as the return URI, or callback URL, use the ``getCallbackHelpMessageTranslationKey()`` method. +--------------------------- + +If the Integration leverages an auth provider that requires a callback URL or something similar, this interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormCallbackInterface``, provides a means to return a translation string to display in the UI. For example, OAuth2 requires a redirect URI. If the administrator has to configure the OAuth credentials in the third party service and needs to know what URL to use in Mautic as the return URI, or callback URL, use the ``getCallbackHelpMessageTranslationKey()`` method. Feature interfaces ------------------- +================== ConfigFormFeatureSettingsInterface -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeatureSettingsInterface``, provides the Symfony form type class that defines the fields to be displayed on the Features tab. These values are not encrypted. +---------------------------------- + +The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeatureSettingsInterface``, provides the Symfony Form type class that defines the fields to be displayed on the Features tab. These values are not encrypted. .. code-block:: PHP @@ -109,12 +113,14 @@ The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFea ConfigFormFeaturesInterface -^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Currently the integrations bundle provides default features. To use these features, implement this, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeaturesInterface``, interface. ``getSupportedFeatures`` returns an array of supported features. For example, if the Integration syncs with Mautic Contacts, ``getSupportedFeatures()`` could ``return [ConfigFormFeaturesInterface::FEATURE_SYNC];``. +--------------------------- + +Currently the IntegrationsBundle provides default features. To use these features, implement this, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeaturesInterface``, interface. ``getSupportedFeatures`` returns an array of supported features. For example, if the Integration syncs with Mautic Contacts, ``getSupportedFeatures()`` could ``return [ConfigFormFeaturesInterface::FEATURE_SYNC];``. Contact/Company syncing interfaces ----------------------------------- -The integrations bundle provides a sync framework for third party services to sync with Mautic's Contacts and Companies. The ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface`` determines the configuration options for this sync feature. Refer to the method DocBlocks in the interface for more details. +================================== + +The IntegrationsBundle provides a sync framework for third party services to sync with Mautic's Contacts and Companies. The ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface`` determines the configuration options for this sync feature. Refer to the method DocBlocks in the interface for more details. Read more about how to leverage the :doc:`sync framework`. diff --git a/docs/plugins/integrations/index.rst b/docs/plugins/integrations/index.rst index 521576b4..0800dd1b 100644 --- a/docs/plugins/integrations/index.rst +++ b/docs/plugins/integrations/index.rst @@ -1,6 +1,6 @@ -****************************************** +********************* Integration Framework -****************************************** +********************* .. toctree:: :caption: Integration Framework diff --git a/docs/plugins/integrations/integrations.rst b/docs/plugins/integrations/integrations.rst index f71b743e..066f565f 100644 --- a/docs/plugins/integrations/integrations.rst +++ b/docs/plugins/integrations/integrations.rst @@ -1,58 +1,59 @@ -****************************************** -Getting started with the Integration Framework -****************************************** +************************************************** +Getting started with the ``Integration Framework`` +************************************************** .. contents:: Table of contents -The IntegrationsBundle is meant to be a drop in replacement for Mautic's PluginBundle's AbstractIntegration class. It provides cleaner interfaces for configuring, authenticating and syncing Contacts/Companies with third party Integrations. +The IntegrationsBundle is meant to be a drop in replacement for Mautic's PluginBundle's AbstractIntegration class. It provides cleaner interfaces for configuring, authenticating, and syncing Contacts/Companies with third party Integrations. -An example HelloWorld plugin is available https://github.com/mautic/plugin-helloworld. +An example HelloWorld Plugin is available https://github.com/mautic/plugin-helloworld. --------- Using the Integration Framework -=============================== +############################### -Registering the Integration for authentication -_______________________________________________ +Register the ``Integration`` for authentication +*********************************************** If the Integration requires authentication with the third party service: -1. :ref:`Register the integration` as an integration that requires configuration options. -2. Create a custom Symfony form type for the required credentials and return it as part of the :ref:`config interface`. +1. :ref:`Register the Integration` as an Integration that requires configuration options. +2. Create a custom Symfony Form type for the required credentials and return it as part of the :ref:`config interface`. 3. Create a custom service that builds and configures the Guzzle client required to authenticate and communicate with the third party service. Use an [existing supported factory or create a new one](#authentication-providers). -Registering the Integration for configuration -_____________________________________________ +Register the ``Integration`` for configuration +*********************************************** If the Integration has extra configuration settings for features unique to it: -1. :ref:`Register the integration` as an Integration that requires configuration options. -2. Create a custom Symfony form type for the features and return it as part of the :ref:`config form feature setting interface`. +1. :ref:`Register the Integration` as an Integration that requires configuration options. +2. Create a custom Symfony Form type for the features and return it as part of the :ref:`Config Form feature setting interface`. The sync engine -________________ +*************** -If the integration syncs with Mautic's Contacts and/or Companies: +If the Integration syncs with Mautic's Contacts and/or Companies: 1. Read about :doc:`the sync engine`. -Registering the Integration as a Builder -________________________________________ +Register the ``Integration`` as a Builder +***************************************** If the Integration includes a Builder (Email or Landing Page): -1. :ref:`Register the integration` as an integration that provides a custom builder. -2. Configure what featured builders the integration supports (Mautic currently supports 'Email' and 'Page' builders). +1. :ref:`Register the Integration` as an Integration that provides a custom builder. +2. Configure what featured builders the Integration supports (Mautic currently supports 'Email' and 'Landing Page' builders). Basics -====== +****** -Each Integration provides its unique name as registered with Mautic, an icon, and a display name. When an Integration is registered, the Integration helper classes will manage the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the Integration's API keys so the implementing code never has to. +Each Integration provides its unique name as registered with Mautic, an icon, and a display name. When an Integration is registered, the Integration helper classes manages the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the Integration's API keys so the implementing code never has to. Registering the integration -___________________________ -All Integrations whether using the config, auth or sync interfaces must have a class that registers itself with Mautic. The Integration will be listed on the ``/s/plugins`` page. +*************************** + +All Integrations, whether using the config, auth, or sync interfaces, must have a class that registers itself with Mautic. The Integration should list on the ``/s/plugins`` page. In the Plugin's ``Config/config.php``, register the Integration using the tag ``mautic.basic_integration``. @@ -77,7 +78,7 @@ In the Plugin's ``Config/config.php``, register the Integration using the tag `` // ... ]; -The ``HelloWorldIntegration`` will need to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class then define the ``getName()``, ``getDisplayName()`` and ``getIcon()`` methods. +The ``HelloWorldIntegration`` needs to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class then define the ``getName()``, ``getDisplayName()`` and ``getIcon()`` methods. .. code-block:: php diff --git a/docs/plugins/integrations/migrations.rst b/docs/plugins/integrations/migrations.rst index 4dd41430..866c68f8 100644 --- a/docs/plugins/integrations/migrations.rst +++ b/docs/plugins/integrations/migrations.rst @@ -4,14 +4,14 @@ Plugin schema .. contents:: Table of contents -The Integration Framework provides a means for Plugins to better manage their schema. Queries are in migration files that match the Plugin's versions number in its config. When the a Plugin is installed or upgraded, it will loop over the migration files up to the latest version. +The Integration Framework provides a means for Plugins to better manage their schema. Queries are in migration files that match the Plugin's versions number in its config. When the a Plugin is installed or upgraded, it loops over the migration files up to the latest version. ____ -AbstractPluginBundle -==================== +``AbstractPluginBundle`` +######################## -The Plugin's root bundle class should extend: +The Plugin's root bundle class should extend:: MauticPlugin\IntegrationsBundle\Bundle\AbstractPluginBundle @@ -31,7 +31,7 @@ The Plugin's root bundle class should extend: Plugin migrations ------------------ +***************** Each migration file should be stored in the Plugin's ``Migration`` folder with a name that matches ``Version_X_Y_Z.php`` where ``X_Y_Z`` matches the semantic versioning of the Plugin. Each file should contain the incremental schema changes for the Plugin up to the latest version which should match the version in the Plugin's ``Config/config.php`` file. diff --git a/docs/plugins/integrations/sync.rst b/docs/plugins/integrations/sync.rst index 14de2945..5ac64992 100644 --- a/docs/plugins/integrations/sync.rst +++ b/docs/plugins/integrations/sync.rst @@ -1,12 +1,12 @@ -************************** +*********************** Integration sync engine -************************** +*********************** .. contents:: Table of contents -The sync engine supports bidirectional syncing between Mautic's Contact and Companies with third party objects. The engine generates a "sync report" from Mautic that it converts to a "sync order" for the integration to process. It then asks for a "sync report" from the integration which it converts to a "sync order" for Mautic to process. +The sync engine supports bidirectional syncing between Mautic's Contact and Companies with third party objects. The engine generates a "``sync report``" from Mautic that it converts to a "``sync order``" for the Integration to process. It then asks for a "``sync report``" from the Integration which it converts to a "``sync order``" for Mautic to process. -When building the report, Mautic or the Integration will fetch the objects that have been modified or created within the specified timeframe. If the Integration supports changes at the field level, it should tell the report on a per field basis when the field was last updated. Otherwise, it should tell the report when the object itself was last modified. These dates are used by the "sync judge" to determine which value should be used in a bi-directional sync. +When building the Report, Mautic or the Integration fetches the objects that have been modified or created within the specified timeframe. If the Integration supports changes at the field level, it should tell the Report on a per field basis when the field was last updated. Otherwise, it should tell the Report when the object itself was last modified. These dates are used by the "``sync judge``" to determine which value should be used in a bi-directional sync. The sync is initiated using the ``mautic:integrations:sync`` command. For example:: @@ -15,7 +15,8 @@ The sync is initiated using the ``mautic:integrations:sync`` command. For exampl ------ Registering the Integration for the sync engine -=============================================== +############################################### + To tell the IntegrationsBundle that this Integration provides a syncing feature, tag the Integration or support class with ``mautic.sync_integration`` in the Plugin's ``app/config.php``. .. code-block:: php @@ -40,38 +41,39 @@ To tell the IntegrationsBundle that this Integration provides a syncing feature, // ... ]; - -.. compound:: - - The ``SyncSupport`` class must implement:: +The ``SyncSupport`` class must implement:: \Mautic\IntegrationsBundle\Integration\Interfaces\SyncInterface. Syncing -======= +******* The mapping manual -__________________ +================== + The mapping manual tells the sync engine which Integration should be synced with which Mautic object (Contact or Company), the Integration fields that were mapped to Mautic fields, and the direction the data is supposed to flow. See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/Mapping/Manual/MappingManualFactory.php The sync data exchange -______________________ -This is where the sync takes place and is executed by the ``mautic:integrations:sync`` command. Mautic and the Integration will build their respective reports of new or modified objects then execute the order from the other side. +====================== + +This is where the sync takes place and is executed by the ``mautic:integrations:sync`` command. Mautic and the Integration builds their respective Reports of new or modified objects then execute the order from the other side. See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/SyncDataExchange.php Building sync report ____________________ -The sync report tells the sync engine what objects are new and/or modified between the two timestamps given by the engine (or up to the Integration's discretion if it is a first time sync). Objects should be processed in batches which can be done using the ``RequestDAO::getSyncIteration()``. The sync engine will execute ``SyncDataExchangeInterface::getSyncReport()`` until a report comes back with no objects. -If the Integration supports field level change tracking, it should tell the report so that the sync engine can merge the two data sets more accurately. +The sync report tells the sync engine what objects are new and/or modified between the two timestamps given by the engine (or up to the Integration's discretion if it is a first time sync). Objects should be processed in batches which can be done using the ``RequestDAO::getSyncIteration()``. The sync engine executes ``SyncDataExchangeInterface::getSyncReport()`` until a Report comes back with no objects. + +If the Integration supports field level change tracking, it should tell the Report so that the sync engine can merge the two data sets more accurately. See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/ReportBuilder.php Executing the sync order ________________________ + The sync order contains all the changes the sync engine has determined should be written to the Integration. The Integration should communicate back the ID of any objects created or adjust objects as needed such as if they were converted from one to another or deleted. See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/OrderExecutioner.php From 33dd30de332cdac64ad618f90ed7ca35c39a2b3e Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Tue, 25 Oct 2022 15:33:19 +0530 Subject: [PATCH 13/29] Corrected URLs Apply suggestions from the code review Co-authored-by: Ruth Cheesley --- docs/plugins/integrations/authentication.rst | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/plugins/integrations/authentication.rst b/docs/plugins/integrations/authentication.rst index 3f861ea0..b684fad4 100644 --- a/docs/plugins/integrations/authentication.rst +++ b/docs/plugins/integrations/authentication.rst @@ -181,7 +181,7 @@ To use the parameter based API key, create a credentials class that implements:: /** @var $factory HttpFactory */ $client = $factory->getClient($credentials); - $response = $client->get('https://api-url.com/fetch'); + $response = $client->get('https://example.com/api/fetch'); Header based API key @@ -221,7 +221,7 @@ Header based API key /** @var $factory HttpFactory */ $client = $factory->getClient($credentials); - $response = $client->get('https://api-url.com/fetch'); + $response = $client->get('https://example.com/api/fetch'); Basic auth @@ -266,7 +266,7 @@ Use the ``mautic.integrations.auth_provider.basic_auth`` service (``\Mautic\Inte /** @var $factory HttpFactory */ $client = $factory->getClient($credentials); - $response = $client->get('https://api-url.com/fetch'); + $response = $client->get('https://example.com/api/fetch'); OAuth1a @@ -297,7 +297,7 @@ OAuth1a two legged does not require a User to login as would three legged. $apiKeys = $configuration->getApiKeys(); $credentials = new class( - 'https://api-url.com/oauth/token', + 'https://example.com/api/oauth/token', $apiKeys['consumer_key'], $apiKeys['consumer_secret'] ) implements CredentialsInterface { @@ -350,7 +350,7 @@ OAuth1a two legged does not require a User to login as would three legged. /** @var $factory HttpFactory */ $client = $factory->getClient($credentials); - $response = $client->get('https://api-url.com/fetch'); + $response = $client->get('https://example.com/api/fetch'); OAuth2 ====== @@ -362,7 +362,7 @@ The OAuth2 factories leverages https://github.com/kamermans/guzzle-oauth2-subscr Client configuration -------------------- -Both OAuth2 factories leverage ``\Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface`` object to configure things such as configuring the signer (basic auth, post form data, custom), token factory, token persistence, and token signer (bearer auth, basic auth, query string, custom). Use the appropriate interfaces as required for the use case (see the interfaces in ``plugins/IntegrationsBundle/Auth/Support/Oauth2/ConfigAccess``). +Both OAuth2 factories leverage the ``\Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface`` object to manage things such as configuring the signer (basic auth, post form data, custom), token factory, token persistence, and token signer (bearer auth, basic auth, query string, custom). Use the appropriate interfaces as required for the use case (see the interfaces in ``plugins/IntegrationsBundle/Auth/Support/Oauth2/ConfigAccess``). See https://github.com/kamermans/guzzle-oauth2-subscriber for additional details on configuring the credentials and token signers or creating custom token persistence and factories. @@ -460,7 +460,7 @@ Below is an example of the password grant for a service that uses a scope (optio $apiKeys = $configuration->getApiKeys(); $credentials = new class( - 'https://api-url.com/oauth/token', + 'https://example.com/api/oauth/token', 'scope1,scope2', $apiKeys['client_id'], $apiKeys['client_secret'], @@ -523,7 +523,7 @@ Below is an example of the password grant for a service that uses a scope (optio /** @var $factory HttpFactory */ $client = $factory->getClient($credentials, $config); - $response = $client->get('https://api-url.com/fetch'); + $response = $client->get('https://example.com/api/fetch'); Client credentials grant ------------------------ @@ -548,7 +548,7 @@ Below is an example of the client credentials grant for a service that uses a sc $apiKeys = $configuration->getApiKeys(); $credentials = new class( - 'https://api-url.com/oauth/token', + 'https://example.com/api/oauth/token', 'scope1,scope2', $apiKeys['client_id'], $apiKeys['client_secret'] @@ -597,7 +597,7 @@ Below is an example of the client credentials grant for a service that uses a sc /** @var $factory HttpFactory */ $client = $factory->getClient($credentials, $config); - $response = $client->get('https://api-url.com/fetch'); + $response = $client->get('https://example.com/api/fetch'); OAuth2 three legged =================== @@ -668,8 +668,8 @@ Here is an example of a client, assuming that the User has already logged in and $code = $request->get('code'); $credentials = new class( - 'https://api-url.com/oauth/authorize', - 'https://api-url.com/oauth/token', + 'https://example.com/api/oauth/authorize', + 'https://example.com/api/oauth/token', $redirectUrl, 'scope1,scope2', $apiKeys['client_id'], @@ -749,4 +749,4 @@ Here is an example of a client, assuming that the User has already logged in and /** @var $factory HttpFactory */ $client = $factory->getClient($credentials, $config); - $response = $client->get('https://api-url.com/fetch'); + $response = $client->get('https://example.com/api/fetch'); From c5fca3d7383767c377efc73d8fdf63296d47bbae Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Tue, 25 Oct 2022 16:07:22 +0530 Subject: [PATCH 14/29] Added link files for external links. --- docs/links/plugin_helloworld.py | 7 +++++++ docs/links/plugin_helloworld_mapping_manunal.py | 7 +++++++ docs/links/plugin_helloworld_order_executioner.py | 7 +++++++ docs/links/plugin_helloworld_report_builder.py | 7 +++++++ docs/links/plugin_helloworld_sync_data_exchange.py | 7 +++++++ .../plugin_integration_config_form_callback_interface.py | 7 +++++++ .../links/plugin_integration_guzzle_oauth2_subscriber.py | 7 +++++++ docs/plugins/integrations/authentication.rst | 8 +++----- docs/plugins/integrations/integrations.rst | 2 +- docs/plugins/integrations/sync.rst | 9 +++++---- 10 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 docs/links/plugin_helloworld.py create mode 100644 docs/links/plugin_helloworld_mapping_manunal.py create mode 100644 docs/links/plugin_helloworld_order_executioner.py create mode 100644 docs/links/plugin_helloworld_report_builder.py create mode 100644 docs/links/plugin_helloworld_sync_data_exchange.py create mode 100644 docs/links/plugin_integration_config_form_callback_interface.py create mode 100644 docs/links/plugin_integration_guzzle_oauth2_subscriber.py diff --git a/docs/links/plugin_helloworld.py b/docs/links/plugin_helloworld.py new file mode 100644 index 00000000..042a44d1 --- /dev/null +++ b/docs/links/plugin_helloworld.py @@ -0,0 +1,7 @@ +from . import link + +link_name = "Plugin HelloWorld" +link_text = "Plugin HelloWorld" +link_url = "https://github.com/mautic/plugin-helloworld" + +link.xref_links.update({link_name: (link_text, link_url)}) diff --git a/docs/links/plugin_helloworld_mapping_manunal.py b/docs/links/plugin_helloworld_mapping_manunal.py new file mode 100644 index 00000000..73d61ad7 --- /dev/null +++ b/docs/links/plugin_helloworld_mapping_manunal.py @@ -0,0 +1,7 @@ +from . import link + +link_name = "MappingManualFactory" +link_text = "HelloWorldBundle/Sync/Mapping/Manual/MappingManualFactory.php" +link_url = "https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/Mapping/Manual/MappingManualFactory.php" + +link.xref_links.update({link_name: (link_text, link_url)}) diff --git a/docs/links/plugin_helloworld_order_executioner.py b/docs/links/plugin_helloworld_order_executioner.py new file mode 100644 index 00000000..ee7791ab --- /dev/null +++ b/docs/links/plugin_helloworld_order_executioner.py @@ -0,0 +1,7 @@ +from . import link + +link_name = "OrderExecutioner" +link_text = "HelloWorldBundle/Sync/DataExchange/OrderExecutioner.php" +link_url = "https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/OrderExecutioner.php" + +link.xref_links.update({link_name: (link_text, link_url)}) diff --git a/docs/links/plugin_helloworld_report_builder.py b/docs/links/plugin_helloworld_report_builder.py new file mode 100644 index 00000000..8720bf19 --- /dev/null +++ b/docs/links/plugin_helloworld_report_builder.py @@ -0,0 +1,7 @@ +from . import link + +link_name = "ReportBuilder" +link_text = "HelloWorldBundle/Sync/DataExchange/ReportBuilder.php" +link_url = "https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/ReportBuilder.php" + +link.xref_links.update({link_name: (link_text, link_url)}) diff --git a/docs/links/plugin_helloworld_sync_data_exchange.py b/docs/links/plugin_helloworld_sync_data_exchange.py new file mode 100644 index 00000000..ee6f1897 --- /dev/null +++ b/docs/links/plugin_helloworld_sync_data_exchange.py @@ -0,0 +1,7 @@ +from . import link + +link_name = "SyncDataExchange" +link_text = "HelloWorldBundle/Sync/DataExchange/SyncDataExchange.php" +link_url = "https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/SyncDataExchange.php" + +link.xref_links.update({link_name: (link_text, link_url)}) diff --git a/docs/links/plugin_integration_config_form_callback_interface.py b/docs/links/plugin_integration_config_form_callback_interface.py new file mode 100644 index 00000000..16001d0b --- /dev/null +++ b/docs/links/plugin_integration_config_form_callback_interface.py @@ -0,0 +1,7 @@ +from . import link + +link_name = "ConfigFormCallbackInterface" +link_text = "ConfigFormCallbackInterface" +link_url = "https://github.com/mautic/mautic/blob/5.x/app/bundles/IntegrationsBundle/Integration/Interfaces/ConfigFormCallbackInterface.php" + +link.xref_links.update({link_name: (link_text, link_url)}) diff --git a/docs/links/plugin_integration_guzzle_oauth2_subscriber.py b/docs/links/plugin_integration_guzzle_oauth2_subscriber.py new file mode 100644 index 00000000..ab2a10f9 --- /dev/null +++ b/docs/links/plugin_integration_guzzle_oauth2_subscriber.py @@ -0,0 +1,7 @@ +from . import link + +link_name = "Guzzle Oauth2 Subscriber" +link_text = "Guzzle Oauth2 Subscriber" +link_url = "https://github.com/kamermans/guzzle-oauth2-subscriber" + +link.xref_links.update({link_name: (link_text, link_url)}) diff --git a/docs/plugins/integrations/authentication.rst b/docs/plugins/integrations/authentication.rst index b684fad4..8b770876 100644 --- a/docs/plugins/integrations/authentication.rst +++ b/docs/plugins/integrations/authentication.rst @@ -357,14 +357,14 @@ OAuth2 Use the OAuth2 factory according to the grant type required. ``\Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\HttpFactory`` supports ``code`` and ``refresh_token`` grant types. ``\Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\HttpFactory`` supports ``client_credentials`` and ``password``. -The OAuth2 factories leverages https://github.com/kamermans/guzzle-oauth2-subscriber as a middleware. +The OAuth2 factories leverages :xref:`Guzzle Oauth2 Subscriber` as a middleware. Client configuration -------------------- Both OAuth2 factories leverage the ``\Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface`` object to manage things such as configuring the signer (basic auth, post form data, custom), token factory, token persistence, and token signer (bearer auth, basic auth, query string, custom). Use the appropriate interfaces as required for the use case (see the interfaces in ``plugins/IntegrationsBundle/Auth/Support/Oauth2/ConfigAccess``). -See https://github.com/kamermans/guzzle-oauth2-subscriber for additional details on configuring the credentials and token signers or creating custom token persistence and factories. +See :xref:`Guzzle Oauth2 Subscriber` for additional details on configuring the credentials and token signers or creating custom token persistence and factories. Token persistence ----------------- @@ -604,9 +604,7 @@ OAuth2 three legged Three legged OAuth2 with the code grant is the most complex to implement because it involves redirecting the user to the third party service to authenticate then sent back to Mautic to initiate the access token process using a code returned in the request. -The first step is to register the integration as a :ref:`\\Mautic\\IntegrationsBundle\\Integration\\Interfaces\\AuthenticationInterface`. The ``authenticateIntegration()`` method initiates the access token process using the ``code`` returned in the request after the user logs into the third-party service. The Integration bundle provides a route that can use as the redirect or callback URIs through the named route ``mautic_integration_public_callback`` that requires a ``integration`` parameter. This redirect URI can display in the UI by using ConfigFormCallbackInterface_. This route is to find the integration by name from the ``AuthIntegrationsHelper``and then execute its ``authenticateIntegration()``. - -.. _ConfigFormCallbackInterface: https://github.com/mautic/mautic/blob/5.x/app/bundles/IntegrationsBundle/Integration/Interfaces/ConfigFormCallbackInterface.php +The first step is to register the integration as a :ref:`\\Mautic\\IntegrationsBundle\\Integration\\Interfaces\\AuthenticationInterface`. The ``authenticateIntegration()`` method initiates the access token process using the ``code`` returned in the request after the user logs into the third-party service. The Integration bundle provides a route that can use as the redirect or callback URIs through the named route ``mautic_integration_public_callback`` that requires a ``integration`` parameter. This redirect URI can display in the UI by using :xref:`ConfigFormCallbackInterface`. This route is to find the integration by name from the ``AuthIntegrationsHelper``and then execute its ``authenticateIntegration()``. .. code-block:: php diff --git a/docs/plugins/integrations/integrations.rst b/docs/plugins/integrations/integrations.rst index 066f565f..1a97f492 100644 --- a/docs/plugins/integrations/integrations.rst +++ b/docs/plugins/integrations/integrations.rst @@ -6,7 +6,7 @@ Getting started with the ``Integration Framework`` The IntegrationsBundle is meant to be a drop in replacement for Mautic's PluginBundle's AbstractIntegration class. It provides cleaner interfaces for configuring, authenticating, and syncing Contacts/Companies with third party Integrations. -An example HelloWorld Plugin is available https://github.com/mautic/plugin-helloworld. +An example HelloWorld Plugin is available :xref:`Plugin HelloWorld`. --------- diff --git a/docs/plugins/integrations/sync.rst b/docs/plugins/integrations/sync.rst index 5ac64992..185f7973 100644 --- a/docs/plugins/integrations/sync.rst +++ b/docs/plugins/integrations/sync.rst @@ -53,14 +53,14 @@ The mapping manual The mapping manual tells the sync engine which Integration should be synced with which Mautic object (Contact or Company), the Integration fields that were mapped to Mautic fields, and the direction the data is supposed to flow. -See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/Mapping/Manual/MappingManualFactory.php +See :xref:`MappingManualFactory`. The sync data exchange ====================== This is where the sync takes place and is executed by the ``mautic:integrations:sync`` command. Mautic and the Integration builds their respective Reports of new or modified objects then execute the order from the other side. -See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/SyncDataExchange.php +See :xref:`SyncDataExchange`. Building sync report ____________________ @@ -69,11 +69,12 @@ The sync report tells the sync engine what objects are new and/or modified betwe If the Integration supports field level change tracking, it should tell the Report so that the sync engine can merge the two data sets more accurately. -See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/ReportBuilder.php +See :xref:`ReportBuilder`. + Executing the sync order ________________________ The sync order contains all the changes the sync engine has determined should be written to the Integration. The Integration should communicate back the ID of any objects created or adjust objects as needed such as if they were converted from one to another or deleted. -See https://github.com/mautic/plugin-helloworld/blob/mautic-4/Sync/DataExchange/OrderExecutioner.php +See :xref:`OrderExecutioner`. \ No newline at end of file From fe9804d858fb9554a3bf34473897d5a198446cf2 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Tue, 25 Oct 2022 16:20:54 +0530 Subject: [PATCH 15/29] Corrected RST. --- docs/plugins/integrations/authentication.rst | 4 ++-- docs/plugins/integrations/configuration.rst | 2 +- docs/plugins/integrations/integrations.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/plugins/integrations/authentication.rst b/docs/plugins/integrations/authentication.rst index 8b770876..e8e4cce3 100644 --- a/docs/plugins/integrations/authentication.rst +++ b/docs/plugins/integrations/authentication.rst @@ -113,9 +113,9 @@ The Integration bundle comes with a number of popular authentication protocols a \Mautic\IntegrationsBundle\Auth\Provider\AuthProviderInterface. -**The examples below use anonymous classes. Of course, use Object Oriented Programming with services and factories to generate credential, configuration, and client classes.** +**The examples below use anonymous classes. Use Object Oriented Programming with services and factories to generate credential, configuration, and client classes.** -The best way to get configuration values such as username, password, consumer key, consumer secret, etc is by using the ``mautic.integrations.helper`` ``(\Mautic\IntegrationsBundle\Helper\IntegrationsHelper)`` service to leverage the configuration stored in the ``Integration`` entity's API keys. +The best way to get configuration values such as username, password, consumer key, consumer secret, and so forth is by using the ``mautic.integrations.helper`` ``(\Mautic\IntegrationsBundle\Helper\IntegrationsHelper)`` service to leverage the configuration stored in the ``Integration`` entity's API keys. .. code-block:: php diff --git a/docs/plugins/integrations/configuration.rst b/docs/plugins/integrations/configuration.rst index c7b42681..ad0a9df6 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -78,7 +78,7 @@ There are multiple interfaces that can be used to add Form Fields options to the Enabled/auth tab ================ -These interfaces provide the configuration options for authenticating with the third party service. Read more about how to use IntegrationsBundle's [auth providers here](#integration-authentication). +These interfaces provide the configuration options for authenticating with the third party service. Read more about how to use IntegrationsBundle's :ref:`auth providers here`. ConfigFormAuthInterface ----------------------- diff --git a/docs/plugins/integrations/integrations.rst b/docs/plugins/integrations/integrations.rst index 1a97f492..cf59eb52 100644 --- a/docs/plugins/integrations/integrations.rst +++ b/docs/plugins/integrations/integrations.rst @@ -20,7 +20,7 @@ If the Integration requires authentication with the third party service: 1. :ref:`Register the Integration` as an Integration that requires configuration options. 2. Create a custom Symfony Form type for the required credentials and return it as part of the :ref:`config interface`. -3. Create a custom service that builds and configures the Guzzle client required to authenticate and communicate with the third party service. Use an [existing supported factory or create a new one](#authentication-providers). +3. Create a custom service that builds and configures the Guzzle client required to authenticate and communicate with the third party service. Use an :ref:`existing supported factory or create a new one`. Register the ``Integration`` for configuration *********************************************** From 1bf805d463570951fd6ab8b0a5320aa10c6772a9 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Tue, 25 Oct 2022 16:24:26 +0530 Subject: [PATCH 16/29] Change word to display. --- docs/plugins/integrations/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/integrations/configuration.rst b/docs/plugins/integrations/configuration.rst index ad0a9df6..59fccf47 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -127,6 +127,6 @@ Read more about how to leverage the :doc:`sync framework`. Config form notes interface --------------------------- -The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``, provides a way to put notes, either info or warning, on the plugin configuration form. +The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``, provides a way to display notes, either info or warning, on the plugin configuration form. Read more about to how-tos :doc:`here` From 7b8307c23050cbc7a97bf40d4dfdf0d88540e6c0 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Thu, 27 Oct 2022 12:04:10 +0530 Subject: [PATCH 17/29] Added basic page and corrected the structure. --- docs/plugins/integrations/basic.rst | 66 ++++++++++++++++ docs/plugins/integrations/configuration.rst | 2 +- docs/plugins/integrations/index.rst | 1 + docs/plugins/integrations/integrations.rst | 84 ++------------------- 4 files changed, 76 insertions(+), 77 deletions(-) create mode 100644 docs/plugins/integrations/basic.rst diff --git a/docs/plugins/integrations/basic.rst b/docs/plugins/integrations/basic.rst new file mode 100644 index 00000000..54b1756a --- /dev/null +++ b/docs/plugins/integrations/basic.rst @@ -0,0 +1,66 @@ +****** +Basics +****** + +Each Integration provides its unique name as registered with Mautic, an icon, and a display name. When an Integration is registered, the Integration helper classes manages the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the Integration's API keys so the implementing code never has to. + +---- + +Registering the integration +############################### + +All Integrations, whether using the config, auth, or sync interfaces, must have a class that registers itself with Mautic. The Integration should list on the ``/s/plugins`` page. + +In the Plugin's ``Config/config.php``, register the Integration using the tag ``mautic.basic_integration``. + +.. code-block:: php + + [ + // ... + 'integrations' => [ + 'helloworld.integration' => [ + 'class' => \MauticPlugin\HelloWorldBundle\Integration\HelloWorldIntegration::class, + 'tags' => [ + 'mautic.basic_integration', + ], + ], + // ... + ], + // ... + ], + // ... + ]; + +The ``HelloWorldIntegration`` needs to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class then define the ``getName()``, ``getDisplayName()`` and ``getIcon()`` methods. + +.. code-block:: php + + `. Config form notes interface ---------------------------- +=========================== The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``, provides a way to display notes, either info or warning, on the plugin configuration form. diff --git a/docs/plugins/integrations/index.rst b/docs/plugins/integrations/index.rst index 0800dd1b..10e3569c 100644 --- a/docs/plugins/integrations/index.rst +++ b/docs/plugins/integrations/index.rst @@ -7,6 +7,7 @@ Integration Framework :maxdepth: 3 :hidden: + basic integrations authentication builder diff --git a/docs/plugins/integrations/integrations.rst b/docs/plugins/integrations/integrations.rst index cf59eb52..3297817e 100644 --- a/docs/plugins/integrations/integrations.rst +++ b/docs/plugins/integrations/integrations.rst @@ -1,6 +1,6 @@ -************************************************** -Getting started with the ``Integration Framework`` -************************************************** +******************************* +Using the Integration Framework +******************************* .. contents:: Table of contents @@ -10,11 +10,8 @@ An example HelloWorld Plugin is available :xref:`Plugin HelloWorld`. --------- -Using the Integration Framework -############################### - Register the ``Integration`` for authentication -*********************************************** +############################################### If the Integration requires authentication with the third party service: @@ -23,7 +20,7 @@ If the Integration requires authentication with the third party service: 3. Create a custom service that builds and configures the Guzzle client required to authenticate and communicate with the third party service. Use an :ref:`existing supported factory or create a new one`. Register the ``Integration`` for configuration -*********************************************** +############################################## If the Integration has extra configuration settings for features unique to it: @@ -31,81 +28,16 @@ If the Integration has extra configuration settings for features unique to it: 2. Create a custom Symfony Form type for the features and return it as part of the :ref:`Config Form feature setting interface`. The sync engine -*************** +############### If the Integration syncs with Mautic's Contacts and/or Companies: 1. Read about :doc:`the sync engine`. Register the ``Integration`` as a Builder -***************************************** +######################################### If the Integration includes a Builder (Email or Landing Page): 1. :ref:`Register the Integration` as an Integration that provides a custom builder. -2. Configure what featured builders the Integration supports (Mautic currently supports 'Email' and 'Landing Page' builders). - -Basics -****** - -Each Integration provides its unique name as registered with Mautic, an icon, and a display name. When an Integration is registered, the Integration helper classes manages the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the Integration's API keys so the implementing code never has to. - -Registering the integration -*************************** - -All Integrations, whether using the config, auth, or sync interfaces, must have a class that registers itself with Mautic. The Integration should list on the ``/s/plugins`` page. - -In the Plugin's ``Config/config.php``, register the Integration using the tag ``mautic.basic_integration``. - -.. code-block:: php - - [ - // ... - 'integrations' => [ - 'helloworld.integration' => [ - 'class' => \MauticPlugin\HelloWorldBundle\Integration\HelloWorldIntegration::class, - 'tags' => [ - 'mautic.basic_integration', - ], - ], - // ... - ], - // ... - ], - // ... - ]; - -The ``HelloWorldIntegration`` needs to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class then define the ``getName()``, ``getDisplayName()`` and ``getIcon()`` methods. - -.. code-block:: php - - Date: Fri, 28 Oct 2022 11:42:13 +0530 Subject: [PATCH 18/29] Corrected heading and merged docs. --- docs/plugins/integrations/authentication.rst | 8 ++- docs/plugins/integrations/basic.rst | 66 -------------------- docs/plugins/integrations/configuration.rst | 2 +- docs/plugins/integrations/index.rst | 64 ++++++++++++++++++- docs/plugins/integrations/integrations.rst | 24 +++++-- docs/plugins/integrations/migrations.rst | 4 +- docs/plugins/integrations/sync.rst | 6 +- 7 files changed, 95 insertions(+), 79 deletions(-) delete mode 100644 docs/plugins/integrations/basic.rst diff --git a/docs/plugins/integrations/authentication.rst b/docs/plugins/integrations/authentication.rst index e8e4cce3..a0a5c32d 100644 --- a/docs/plugins/integrations/authentication.rst +++ b/docs/plugins/integrations/authentication.rst @@ -1,5 +1,5 @@ ************************** -Integration authentication +Authentication Integration ************************** .. contents:: Table of contents @@ -8,9 +8,13 @@ The IntegrationsBundle provides factories and helpers to create Guzzle Client cl ---------- -Registering the Integration for authentication +.. vale off + +Registering the Integration for Authentication ############################################## +.. vale on + If the Integration requires the User to authenticate through the web (OAuth2 three legged), the Integration needs to tag a service with ``mautic.auth_integration`` to handle the authentication process (redirecting to login, request the access token, etc). This service needs to implement:: diff --git a/docs/plugins/integrations/basic.rst b/docs/plugins/integrations/basic.rst deleted file mode 100644 index 54b1756a..00000000 --- a/docs/plugins/integrations/basic.rst +++ /dev/null @@ -1,66 +0,0 @@ -****** -Basics -****** - -Each Integration provides its unique name as registered with Mautic, an icon, and a display name. When an Integration is registered, the Integration helper classes manages the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the Integration's API keys so the implementing code never has to. - ----- - -Registering the integration -############################### - -All Integrations, whether using the config, auth, or sync interfaces, must have a class that registers itself with Mautic. The Integration should list on the ``/s/plugins`` page. - -In the Plugin's ``Config/config.php``, register the Integration using the tag ``mautic.basic_integration``. - -.. code-block:: php - - [ - // ... - 'integrations' => [ - 'helloworld.integration' => [ - 'class' => \MauticPlugin\HelloWorldBundle\Integration\HelloWorldIntegration::class, - 'tags' => [ - 'mautic.basic_integration', - ], - ], - // ... - ], - // ... - ], - // ... - ]; - -The ``HelloWorldIntegration`` needs to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class then define the ``getName()``, ``getDisplayName()`` and ``getIcon()`` methods. - -.. code-block:: php - - [ + // ... + 'integrations' => [ + 'helloworld.integration' => [ + 'class' => \MauticPlugin\HelloWorldBundle\Integration\HelloWorldIntegration::class, + 'tags' => [ + 'mautic.basic_integration', + ], + ], + // ... + ], + // ... + ], + // ... + ]; + +The ``HelloWorldIntegration`` needs to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class then define the ``getName()``, ``getDisplayName()`` and ``getIcon()`` methods. + +.. code-block:: php + + ` as an Integration that requires configuration options. +1. :ref:`Register the Integration` as an Integration that requires configuration options. 2. Create a custom Symfony Form type for the required credentials and return it as part of the :ref:`config interface`. 3. Create a custom service that builds and configures the Guzzle client required to authenticate and communicate with the third party service. Use an :ref:`existing supported factory or create a new one`. -Register the ``Integration`` for configuration +.. vale off + +Register the Integration for configuration ############################################## +.. vale on + If the Integration has extra configuration settings for features unique to it: 1. :ref:`Register the Integration` as an Integration that requires configuration options. @@ -34,9 +46,13 @@ If the Integration syncs with Mautic's Contacts and/or Companies: 1. Read about :doc:`the sync engine`. -Register the ``Integration`` as a Builder +.. vale off + +Register the Integration as a Builder ######################################### +.. vale on + If the Integration includes a Builder (Email or Landing Page): 1. :ref:`Register the Integration` as an Integration that provides a custom builder. diff --git a/docs/plugins/integrations/migrations.rst b/docs/plugins/integrations/migrations.rst index 866c68f8..a68fd634 100644 --- a/docs/plugins/integrations/migrations.rst +++ b/docs/plugins/integrations/migrations.rst @@ -30,8 +30,8 @@ The Plugin's root bundle class should extend:: } -Plugin migrations -***************** +The Plugin migrations +********************* Each migration file should be stored in the Plugin's ``Migration`` folder with a name that matches ``Version_X_Y_Z.php`` where ``X_Y_Z`` matches the semantic versioning of the Plugin. Each file should contain the incremental schema changes for the Plugin up to the latest version which should match the version in the Plugin's ``Config/config.php`` file. diff --git a/docs/plugins/integrations/sync.rst b/docs/plugins/integrations/sync.rst index 185f7973..40846337 100644 --- a/docs/plugins/integrations/sync.rst +++ b/docs/plugins/integrations/sync.rst @@ -1,6 +1,6 @@ -*********************** -Integration sync engine -*********************** +*********** +Sync engine +*********** .. contents:: Table of contents From 39901ec613d3c2e2fc542a0d1dd1ac3331981983 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Fri, 28 Oct 2022 11:57:23 +0530 Subject: [PATCH 19/29] Addressed vale warnings. --- .github/styles/Vocab/Mautic/accept.txt | 4 ++- docs/plugins/integrations/authentication.rst | 2 +- docs/plugins/integrations/builder.rst | 6 +++- docs/plugins/integrations/configuration.rst | 29 ++++++++++++++++++-- docs/plugins/integrations/index.rst | 10 +++++-- docs/plugins/integrations/integrations.rst | 6 +--- docs/plugins/integrations/sync.rst | 6 +++- 7 files changed, 49 insertions(+), 14 deletions(-) diff --git a/.github/styles/Vocab/Mautic/accept.txt b/.github/styles/Vocab/Mautic/accept.txt index daa5fa59..f8bb4e91 100644 --- a/.github/styles/Vocab/Mautic/accept.txt +++ b/.github/styles/Vocab/Mautic/accept.txt @@ -125,4 +125,6 @@ Zoho middleware URIs false -timeframe \ No newline at end of file +timeframe +OAuth1a +OAuth2 \ No newline at end of file diff --git a/docs/plugins/integrations/authentication.rst b/docs/plugins/integrations/authentication.rst index a0a5c32d..07b1bd82 100644 --- a/docs/plugins/integrations/authentication.rst +++ b/docs/plugins/integrations/authentication.rst @@ -1,5 +1,5 @@ ************************** -Authentication Integration +Authentication integration ************************** .. contents:: Table of contents diff --git a/docs/plugins/integrations/builder.rst b/docs/plugins/integrations/builder.rst index 7c0389de..0926a813 100644 --- a/docs/plugins/integrations/builder.rst +++ b/docs/plugins/integrations/builder.rst @@ -8,9 +8,13 @@ Builders can register itself as a "builder" for Email and/or Landing Pages. ---- -Registering the Integration as a builder +.. vale off + +Registering the Integration as a Builder ######################################## +.. vale on + To tell the IntegrationsBundle that this Integration has configuration options, tag the Integration or support class with ``mautic.config_integration`` in the Plugin's ``app/config.php``. .. code-block:: php diff --git a/docs/plugins/integrations/configuration.rst b/docs/plugins/integrations/configuration.rst index b6631ac3..d956fe0e 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -1,14 +1,18 @@ ************************* -Configuration Integration +Configuration integration ************************* .. contents:: Table of contents The Integration Plugin provides interfaces to display and store configuration options that can be accessed through the ``\Mautic\PluginBundle\Entity\Integration`` object. -Registering the Integration for configuration +.. vale off + +Registering the Integration for Configuration ############################################# +.. vale on + To tell the IntegrationsBundle that this Integration has configuration options, tag the Integration or support class with ``mautic.config_integration`` in the Plugin's ``app/config.php``. .. code-block:: php @@ -80,9 +84,14 @@ Enabled/auth tab These interfaces provide the configuration options for authenticating with the third party service. Read more about how to use IntegrationsBundle's :ref:`auth providers here`. + +.. vale off + ConfigFormAuthInterface ----------------------- +.. vale on + Used in the example preceding. This, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormAuthInterface``, interface provides the Symfony Form type class that defines the fields to be stored as the API keys. .. code-block:: PHP @@ -92,17 +101,25 @@ Used in the example preceding. This, ``\Mautic\IntegrationsBundle\Integration\In $username = $apiKeys['username']; +.. vale off + ConfigFormCallbackInterface --------------------------- +.. vale on + If the Integration leverages an auth provider that requires a callback URL or something similar, this interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormCallbackInterface``, provides a means to return a translation string to display in the UI. For example, OAuth2 requires a redirect URI. If the administrator has to configure the OAuth credentials in the third party service and needs to know what URL to use in Mautic as the return URI, or callback URL, use the ``getCallbackHelpMessageTranslationKey()`` method. Feature interfaces ================== +.. vale off + ConfigFormFeatureSettingsInterface ---------------------------------- +.. vale on + The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeatureSettingsInterface``, provides the Symfony Form type class that defines the fields to be displayed on the Features tab. These values are not encrypted. .. code-block:: PHP @@ -111,15 +128,23 @@ The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFea $featureSettings = $integrationHelper->get(HelloWorldIntegration::NAME)->getIntegrationConfiguration()->getFeatureSettings(); $doSomething = $featureSettings['do_Something']; +.. vale off ConfigFormFeaturesInterface --------------------------- +.. vale on + + Currently the IntegrationsBundle provides default features. To use these features, implement this, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeaturesInterface``, interface. ``getSupportedFeatures`` returns an array of supported features. For example, if the Integration syncs with Mautic Contacts, ``getSupportedFeatures()`` could ``return [ConfigFormFeaturesInterface::FEATURE_SYNC];``. +.. vale off + Contact/Company syncing interfaces ================================== +.. vale on + The IntegrationsBundle provides a sync framework for third party services to sync with Mautic's Contacts and Companies. The ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface`` determines the configuration options for this sync feature. Refer to the method DocBlocks in the interface for more details. Read more about how to leverage the :doc:`sync framework`. diff --git a/docs/plugins/integrations/index.rst b/docs/plugins/integrations/index.rst index d52d15ef..a98b8fef 100644 --- a/docs/plugins/integrations/index.rst +++ b/docs/plugins/integrations/index.rst @@ -1,5 +1,5 @@ ********************* -Integration Framework +Integration framework ********************* .. toctree:: @@ -19,8 +19,12 @@ Each Integration provides its unique name as registered with Mautic, an icon, an ---- -Registering the integration -############################### +.. vale off + +Registering the Integration +########################### + +.. vale on All Integrations, whether using the config, auth, or sync interfaces, must have a class that registers itself with Mautic. The Integration should list on the ``/s/plugins`` page. diff --git a/docs/plugins/integrations/integrations.rst b/docs/plugins/integrations/integrations.rst index 5a7b0d60..2fc4c84e 100644 --- a/docs/plugins/integrations/integrations.rst +++ b/docs/plugins/integrations/integrations.rst @@ -1,11 +1,7 @@ -.. vale off - ******************************* -Using the Integration Framework +Using the integration framework ******************************* -.. vale on - .. contents:: Table of contents The IntegrationsBundle is meant to be a drop in replacement for Mautic's PluginBundle's AbstractIntegration class. It provides cleaner interfaces for configuring, authenticating, and syncing Contacts/Companies with third party Integrations. diff --git a/docs/plugins/integrations/sync.rst b/docs/plugins/integrations/sync.rst index 40846337..ebe3415d 100644 --- a/docs/plugins/integrations/sync.rst +++ b/docs/plugins/integrations/sync.rst @@ -14,9 +14,13 @@ The sync is initiated using the ``mautic:integrations:sync`` command. For exampl ------ -Registering the Integration for the sync engine +.. vale off + +Registering the Integration for the Sync Engine ############################################### +.. vale on + To tell the IntegrationsBundle that this Integration provides a syncing feature, tag the Integration or support class with ``mautic.sync_integration`` in the Plugin's ``app/config.php``. .. code-block:: php From 20eeaed73e029ecd4cbaaba3c9654457edb0b0ae Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Fri, 28 Oct 2022 20:03:11 +0530 Subject: [PATCH 20/29] Reword and addressed a few doable vale's suggestions. --- .github/styles/Mautic/FeatureList.yml | 2 + docs/plugins/integrations/authentication.rst | 84 ++++++++++++++++---- docs/plugins/integrations/builder.rst | 2 +- docs/plugins/integrations/configuration.rst | 62 ++++++++++++++- docs/plugins/integrations/index.rst | 23 +++++- docs/plugins/integrations/integrations.rst | 12 ++- docs/plugins/integrations/migrations.rst | 10 ++- docs/plugins/integrations/sync.rst | 25 +++--- 8 files changed, 184 insertions(+), 36 deletions(-) diff --git a/.github/styles/Mautic/FeatureList.yml b/.github/styles/Mautic/FeatureList.yml index 3aa8701f..fdf383e2 100644 --- a/.github/styles/Mautic/FeatureList.yml +++ b/.github/styles/Mautic/FeatureList.yml @@ -54,6 +54,8 @@ swap: segments: Segments stage: Stage stages: Stages + sync engine: Sync Engine + sync report: Sync Report theme: Theme themes: Themes user: User diff --git a/docs/plugins/integrations/authentication.rst b/docs/plugins/integrations/authentication.rst index 07b1bd82..f4f83bba 100644 --- a/docs/plugins/integrations/authentication.rst +++ b/docs/plugins/integrations/authentication.rst @@ -15,11 +15,7 @@ Registering the Integration for Authentication .. vale on -If the Integration requires the User to authenticate through the web (OAuth2 three legged), the Integration needs to tag a service with ``mautic.auth_integration`` to handle the authentication process (redirecting to login, request the access token, etc). - -This service needs to implement:: - - \Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface. +If the Integration requires the User to authenticate through the web (OAuth2 three legged), the Integration needs to tag a service with ``mautic.auth_integration`` to handle the authentication process like redirecting to login, request the access token, and so forth. .. code-block:: php @@ -44,7 +40,23 @@ This service needs to implement:: ]; -The ``AuthSupport`` class must implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface``. +This service needs to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface``. + +.. php:interface:: Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface + +.. php:method:: public function isAuthenticated(): bool; + + :return: Returns true if the Integration has already been authorized with the third party service. + :returntype: bool + +.. php:method:: public function authenticateIntegration(Request $request): string; + + :param Request $request: The request object. + + :return: A message to render if succeeded. + :returntype: string + +Find the code snippet as follows, .. code-block:: php @@ -147,9 +159,21 @@ Use the ``mautic.integrations.auth_provider.api_key`` service (``\Mautic\Integra Parameter based API key ----------------------- -To use the parameter based API key, create a credentials class that implements:: +To use the parameter based API key, create a credentials class that implements ``\Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\ParameterCredentialsInterface``. + +.. php:class:: \Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\ParameterCredentialsInterface + +.. php:method:: public function getKeyName(): string; + + :return: Key name. + :returntype: string + +.. php:method:: public function getApiKey(): ?string; + + :return: API key or null. + :returntype: ?string - \Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\ParameterCredentialsInterface. +Find the code snippet as follows, .. code-block:: php @@ -191,6 +215,22 @@ To use the parameter based API key, create a credentials class that implements:: Header based API key -------------------- +To use the header based API key, create a credentials class that implements ``\Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\HeaderCredentialsInterface``. + +.. php:class:: \Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\HeaderCredentialsInterface + +.. php:method:: public function getKeyName(): string; + + :return: Key name. + :returntype: string + +.. php:method:: public function getApiKey(): ?string; + + :return: API key or null. + :returntype: ?string + +Find the code snippet as follows, + .. code-block:: php `. The ``authenticateIntegration()`` method initiates the access token process using the ``code`` returned in the request after the user logs into the third-party service. The Integration bundle provides a route that can use as the redirect or callback URIs through the named route ``mautic_integration_public_callback`` that requires a ``integration`` parameter. This redirect URI can display in the UI by using :xref:`ConfigFormCallbackInterface`. This route is to find the integration by name from the ``AuthIntegrationsHelper``and then execute its ``authenticateIntegration()``. +The first step is to register the integration as a :ref:`\\Mautic\\IntegrationsBundle\\Integration\\Interfaces\\AuthenticationInterface`. The ``authenticateIntegration()`` method initiates the access token process using the ``code`` returned in the request after the user logs into the third-party service. The Integration bundle provides a route that can use as the redirect or callback URIs through the named route ``mautic_integration_public_callback`` that requires a ``integration`` parameter. This redirect URI can display in the UI by using :xref:`ConfigFormCallbackInterface`. This route is to find the integration by name from the ``AuthIntegrationsHelper`` and then execute its ``authenticateIntegration()``. .. code-block:: php @@ -638,7 +694,7 @@ The first step is to register the integration as a :ref:`\\Mautic\\IntegrationsB } } -The trick here is that the ``Client``'s ``authenticate`` method configures a ``ClientInterface`` and then calls to any valid API URL (*this is required*). The middleware initiates the access token process by making a call and storing it in the ``Integration`` entity's API keys through :ref:`TokenPersistenceFactory`. The URL is recommended to be something simple, like a version check or fetching info for the authenticated User. +The trick here is that the ``Client``'s ``authenticate`` method configures a ``ClientInterface`` and then calls to any valid API URL (*this is required*). The middleware initiates the access token process by making a call and storing it in the ``Integration`` entity's API keys through :ref:`TokenPersistenceFactory`. The URL is recommended to be something simple, like a checking version or fetching info for the authenticated User. Here is an example of a client, assuming that the User has already logged in and the code is in the request. diff --git a/docs/plugins/integrations/builder.rst b/docs/plugins/integrations/builder.rst index 0926a813..1e156fe3 100644 --- a/docs/plugins/integrations/builder.rst +++ b/docs/plugins/integrations/builder.rst @@ -44,4 +44,4 @@ The ``BuilderSupport`` class must implement:: \Mautic\IntegrationsBundle\Integration\Interfaces\BuilderInterface -The only method currently defined for the interface is ``isSupported`` which should return a boolean if it supports the given feature. Currently, Mautic supports ``email`` and ``page`` (Landing Pages). This determines what themes should be displayed as an option for the given builder/feature. +The only method currently defined for the interface is ``isSupported`` which should return a boolean if it supports the given feature. Currently, Mautic supports ``email`` and ``page (Landing Pages)``. This determines what Themes should list as an option for the given builder/feature. diff --git a/docs/plugins/integrations/configuration.rst b/docs/plugins/integrations/configuration.rst index d956fe0e..7556218b 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -39,6 +39,25 @@ To tell the IntegrationsBundle that this Integration has configuration options, The ``ConfigSupport`` class must implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormInterface``. +.. php:interface:: \Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormInterface + +.. php:method:: public function getDisplayName(): string; + + :return: Return the Integration's display name. + :returntype: string + +.. php:method:: public function getConfigFormName(): ?string; + + :return: The name/class of the Form type to override the default or just return NULL to use the default. + :returntype: ?string + +.. php:method:: public function getConfigFormContentTemplate(): ?string; + + :return: The template to use from the controller. Return null to use the default. + :returntype: ?string + +Find the code snippet as follows, + .. code-block:: php label pairs for the features this Integration supports. + :returntype: array[] .. vale off diff --git a/docs/plugins/integrations/index.rst b/docs/plugins/integrations/index.rst index a98b8fef..ce733000 100644 --- a/docs/plugins/integrations/index.rst +++ b/docs/plugins/integrations/index.rst @@ -15,7 +15,7 @@ Integration framework sync -Each Integration provides its unique name as registered with Mautic, an icon, and a display name. When an Integration is registered, the Integration helper classes manages the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the Integration's API keys so the implementing code never has to. +Each Integration provides its unique name as registered with Mautic, an icon, and a display name. When an Integration registers, the Integration helper classes manage the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the Integration's API keys, so the implementing code never has to. ---- @@ -51,7 +51,26 @@ In the Plugin's ``Config/config.php``, register the Integration using the tag `` // ... ]; -The ``HelloWorldIntegration`` needs to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class then define the ``getName()``, ``getDisplayName()`` and ``getIcon()`` methods. +The ``HelloWorldIntegration`` needs to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class. + +.. php:class:: \Mautic\IntegrationsBundle\Integration\BasicIntegration + +.. php:method:: public function getName(): string; + + :return: Return the Integration's name. + :returntype: string + +.. php:method:: public function getDisplayName(): string; + + :return: Return the Integration's display name. + :returntype: string + +.. php:method:: public function getIcon(): string; + + :return: Get the path to the Integration's icon. + :returntype: string + + .. code-block:: php diff --git a/docs/plugins/integrations/integrations.rst b/docs/plugins/integrations/integrations.rst index 2fc4c84e..9b574b8b 100644 --- a/docs/plugins/integrations/integrations.rst +++ b/docs/plugins/integrations/integrations.rst @@ -4,9 +4,9 @@ Using the integration framework .. contents:: Table of contents -The IntegrationsBundle is meant to be a drop in replacement for Mautic's PluginBundle's AbstractIntegration class. It provides cleaner interfaces for configuring, authenticating, and syncing Contacts/Companies with third party Integrations. +The IntegrationsBundle is a drop-in replacement for ``PluginBundle``'s ``AbstractIntegration`` class. It provides cleaner interfaces for configuring, authenticating, and syncing Contacts/Companies with third-party Integrations. -An example HelloWorld Plugin is available :xref:`Plugin HelloWorld`. +An example HelloWorld Plugin is available :xref:`here`. --------- @@ -35,9 +35,13 @@ If the Integration has extra configuration settings for features unique to it: 1. :ref:`Register the Integration` as an Integration that requires configuration options. 2. Create a custom Symfony Form type for the features and return it as part of the :ref:`Config Form feature setting interface`. -The sync engine +.. vale off + +The Sync Engine ############### +.. vale on + If the Integration syncs with Mautic's Contacts and/or Companies: 1. Read about :doc:`the sync engine`. @@ -49,7 +53,7 @@ Register the Integration as a Builder .. vale on -If the Integration includes a Builder (Email or Landing Page): +If the Integration includes a Builder, Email or Landing Page: 1. :ref:`Register the Integration` as an Integration that provides a custom builder. 2. Configure what featured builders the Integration supports (Mautic currently supports 'Email' and 'Landing Page' builders). \ No newline at end of file diff --git a/docs/plugins/integrations/migrations.rst b/docs/plugins/integrations/migrations.rst index a68fd634..b5f7991f 100644 --- a/docs/plugins/integrations/migrations.rst +++ b/docs/plugins/integrations/migrations.rst @@ -4,12 +4,16 @@ Plugin schema .. contents:: Table of contents -The Integration Framework provides a means for Plugins to better manage their schema. Queries are in migration files that match the Plugin's versions number in its config. When the a Plugin is installed or upgraded, it loops over the migration files up to the latest version. +The Integration Framework provides a means for Plugins to better manage their schema. Queries are in migration files that match the Plugin's versions number in its config. When the a Plugin gets install or upgrade, it loops over the migration files up to the latest version. ____ -``AbstractPluginBundle`` -######################## +.. vale off + +AbstractPluginBundle +#################### + +.. vale on The Plugin's root bundle class should extend:: diff --git a/docs/plugins/integrations/sync.rst b/docs/plugins/integrations/sync.rst index ebe3415d..52b4d30f 100644 --- a/docs/plugins/integrations/sync.rst +++ b/docs/plugins/integrations/sync.rst @@ -4,9 +4,9 @@ Sync engine .. contents:: Table of contents -The sync engine supports bidirectional syncing between Mautic's Contact and Companies with third party objects. The engine generates a "``sync report``" from Mautic that it converts to a "``sync order``" for the Integration to process. It then asks for a "``sync report``" from the Integration which it converts to a "``sync order``" for Mautic to process. +The Sync Engine supports bidirectional syncing between Mautic's Contact and Companies with third party objects. The engine generates a "``sync report``" from Mautic that it converts to a "``sync order``" for the Integration to process. It then asks for a "``sync report``" from the Integration which it converts to a "``sync order``" for Mautic to process. -When building the Report, Mautic or the Integration fetches the objects that have been modified or created within the specified timeframe. If the Integration supports changes at the field level, it should tell the Report on a per field basis when the field was last updated. Otherwise, it should tell the Report when the object itself was last modified. These dates are used by the "``sync judge``" to determine which value should be used in a bi-directional sync. +When building the Report, Mautic or the Integration fetches the objects those either modified or created within the specified timeframe. If the Integration supports changes at the field level, it should tell the Report on a per-field basis when the field was last updated. Otherwise, it should tell the Report when the object itself was last modified. The "``sync judge``" uses these dates to determine which value to use in bi-directional sync. The sync is initiated using the ``mautic:integrations:sync`` command. For example:: @@ -55,30 +55,37 @@ Syncing The mapping manual ================== -The mapping manual tells the sync engine which Integration should be synced with which Mautic object (Contact or Company), the Integration fields that were mapped to Mautic fields, and the direction the data is supposed to flow. +The mapping manual tells the Sync Engine which Integration should sync with which Mautic object like Contact or Company, the Integration fields. These fields should get mapped to Mautic fields, and the direction in the data suppose to flow. See :xref:`MappingManualFactory`. The sync data exchange ====================== -This is where the sync takes place and is executed by the ``mautic:integrations:sync`` command. Mautic and the Integration builds their respective Reports of new or modified objects then execute the order from the other side. +This is where the sync takes place, and the ``mautic:integrations:sync`` executes it. Mautic and the Integration build their respective Reports of new or modified objects, then execute the order from the other side. See :xref:`SyncDataExchange`. -Building sync report +.. vale off + +Building Sync Report ____________________ -The sync report tells the sync engine what objects are new and/or modified between the two timestamps given by the engine (or up to the Integration's discretion if it is a first time sync). Objects should be processed in batches which can be done using the ``RequestDAO::getSyncIteration()``. The sync engine executes ``SyncDataExchangeInterface::getSyncReport()`` until a Report comes back with no objects. +.. vale on + +The Sync Report tells the Sync Engine what objects are new and/or modified between the two timestamps given by the engine (or up to the Integration's discretion if it is a first-time sync). Objects should process in batches and ``RequestDAO::getSyncIteration()`` takes care of this batching. The Sync Engine executes ``SyncDataExchangeInterface::getSyncReport()`` until a Report comes back with no objects. -If the Integration supports field level change tracking, it should tell the Report so that the sync engine can merge the two data sets more accurately. +If the Integration supports field level change tracking, it should tell the Report so that the Sync Engine can merge the two data sets more accurately. See :xref:`ReportBuilder`. +.. vale off -Executing the sync order +Executing the Sync Order ________________________ -The sync order contains all the changes the sync engine has determined should be written to the Integration. The Integration should communicate back the ID of any objects created or adjust objects as needed such as if they were converted from one to another or deleted. +.. vale on + +The Sync Order contains all the changes the Sync Engine has determined, and these should report to the Integration. The Integration should communicate back the ID of any objects created or adjust objects as needed, such as if they get converted from one to another or deleted. See :xref:`OrderExecutioner`. \ No newline at end of file From 49794a7701326d0421e5e45b2130796387e89785 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Fri, 28 Oct 2022 20:18:25 +0530 Subject: [PATCH 21/29] Updated form notes docs. --- docs/plugins/integrations/configuration.rst | 8 +++- .../integrations/configuration_form_notes.rst | 46 +++++++++++++++++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/docs/plugins/integrations/configuration.rst b/docs/plugins/integrations/configuration.rst index 7556218b..5c50fbea 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -205,9 +205,13 @@ The IntegrationsBundle provides a sync framework for third party services to syn Read more about how to leverage the :doc:`sync framework`. -Config form notes interface +.. vale off + +Config Form notes interface =========================== -The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``, provides a way to display notes, either info or warning, on the plugin configuration form. +.. vale on + +The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``, provides a way to display notes, either info or warning, on the plugin configuration Form. Read more about to how-tos :doc:`here` diff --git a/docs/plugins/integrations/configuration_form_notes.rst b/docs/plugins/integrations/configuration_form_notes.rst index 98675ed1..7713d45e 100644 --- a/docs/plugins/integrations/configuration_form_notes.rst +++ b/docs/plugins/integrations/configuration_form_notes.rst @@ -6,14 +6,54 @@ Integration configuration form notes ************************************ -The integration framework lets developer define their custom messages for the Plugin's configuration form. +The Integration framework lets developer define their custom messages for the Plugin's configuration Form. -The ``ConfigSupport`` class should implement the:: +The ``ConfigSupport`` class should implement the ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface`` - \Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface +.. php:interface:: \Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface + +.. php:method:: public function getAuthorizationNote(): ?Note + + :return: The message and type for Auth tab. + :returntype: :ref:`Note` + +.. php:method:: public function getFeaturesNote(): ?Note + + :return: The message and type for Features tab. + :returntype: :ref:`Note` + +.. php:method:: public function getFieldMappingNote(): ?Note + + :return: The message and type for Field Mapping tab. + :returntype: :ref:`Note` _____ +.. vale off + +Note Object +=========== + +.. vale:on + +.. php:class:: \Mautic\IntegrationsBundle\DTO\Note + +.. php:attr:: public note; + +.. php:attr:: public type; + +.. php:method:: public function getNote(): string + + :return: The string to display. + :returntype: string + +.. php:method:: public function getType(): string + + :return: The note type, this helps annotate the note. + :returntype: string + +The following code snippet shows the use of ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``. + .. code-block:: php Date: Sun, 30 Oct 2022 13:07:54 +0530 Subject: [PATCH 22/29] Update headings. --- docs/plugins/integrations/authentication.rst | 7 +- docs/plugins/integrations/builder.rst | 9 +- docs/plugins/integrations/configuration.rst | 7 +- .../integrations/configuration_form_notes.rst | 5 +- docs/plugins/integrations/index.rst | 108 ++---------------- docs/plugins/integrations/integrations.rst | 13 +-- docs/plugins/integrations/migrations.rst | 9 +- docs/plugins/integrations/register.rst | 78 +++++++++++++ docs/plugins/integrations/sync.rst | 7 +- 9 files changed, 109 insertions(+), 134 deletions(-) create mode 100644 docs/plugins/integrations/register.rst diff --git a/docs/plugins/integrations/authentication.rst b/docs/plugins/integrations/authentication.rst index f4f83bba..a26f29dd 100644 --- a/docs/plugins/integrations/authentication.rst +++ b/docs/plugins/integrations/authentication.rst @@ -1,8 +1,5 @@ -************************** Authentication integration -************************** - -.. contents:: Table of contents +########################## The IntegrationsBundle provides factories and helpers to create Guzzle Client classes for common authentication protocols. @@ -11,7 +8,7 @@ The IntegrationsBundle provides factories and helpers to create Guzzle Client cl .. vale off Registering the Integration for Authentication -############################################## +********************************************** .. vale on diff --git a/docs/plugins/integrations/builder.rst b/docs/plugins/integrations/builder.rst index 1e156fe3..93eaae40 100644 --- a/docs/plugins/integrations/builder.rst +++ b/docs/plugins/integrations/builder.rst @@ -1,17 +1,14 @@ -******************** Builder integrations -******************** +##################### -.. contents:: Table of contents - -Builders can register itself as a "builder" for Email and/or Landing Pages. +Builders can register itself as a "builder" for Email and/or Landing Pages. ---- .. vale off Registering the Integration as a Builder -######################################## +**************************************** .. vale on diff --git a/docs/plugins/integrations/configuration.rst b/docs/plugins/integrations/configuration.rst index 5c50fbea..0c4fb680 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -1,15 +1,12 @@ -************************* Configuration integration -************************* - -.. contents:: Table of contents +######################### The Integration Plugin provides interfaces to display and store configuration options that can be accessed through the ``\Mautic\PluginBundle\Entity\Integration`` object. .. vale off Registering the Integration for Configuration -############################################# +********************************************* .. vale on diff --git a/docs/plugins/integrations/configuration_form_notes.rst b/docs/plugins/integrations/configuration_form_notes.rst index 7713d45e..d931771e 100644 --- a/docs/plugins/integrations/configuration_form_notes.rst +++ b/docs/plugins/integrations/configuration_form_notes.rst @@ -2,9 +2,8 @@ :orphan: -************************************ Integration configuration form notes -************************************ +#################################### The Integration framework lets developer define their custom messages for the Plugin's configuration Form. @@ -32,7 +31,7 @@ _____ .. vale off Note Object -=========== +*********** .. vale:on diff --git a/docs/plugins/integrations/index.rst b/docs/plugins/integrations/index.rst index ce733000..0be23a45 100644 --- a/docs/plugins/integrations/index.rst +++ b/docs/plugins/integrations/index.rst @@ -1,102 +1,18 @@ -********************* Integration framework -********************* - -.. toctree:: - :caption: Integration Framework - :maxdepth: 3 - :hidden: - - integrations - authentication - builder - configuration - migrations - sync - +##################### Each Integration provides its unique name as registered with Mautic, an icon, and a display name. When an Integration registers, the Integration helper classes manage the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the Integration's API keys, so the implementing code never has to. ---- -.. vale off - -Registering the Integration -########################### - -.. vale on - -All Integrations, whether using the config, auth, or sync interfaces, must have a class that registers itself with Mautic. The Integration should list on the ``/s/plugins`` page. - -In the Plugin's ``Config/config.php``, register the Integration using the tag ``mautic.basic_integration``. - -.. code-block:: php - - [ - // ... - 'integrations' => [ - 'helloworld.integration' => [ - 'class' => \MauticPlugin\HelloWorldBundle\Integration\HelloWorldIntegration::class, - 'tags' => [ - 'mautic.basic_integration', - ], - ], - // ... - ], - // ... - ], - // ... - ]; - -The ``HelloWorldIntegration`` needs to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class. - -.. php:class:: \Mautic\IntegrationsBundle\Integration\BasicIntegration - -.. php:method:: public function getName(): string; - - :return: Return the Integration's name. - :returntype: string - -.. php:method:: public function getDisplayName(): string; - - :return: Return the Integration's display name. - :returntype: string - -.. php:method:: public function getIcon(): string; - - :return: Get the path to the Integration's icon. - :returntype: string - - - -.. code-block:: php - - `. .. vale off Register the Integration for Authentication -############################################### +******************************************* .. vale on @@ -26,7 +23,7 @@ If the Integration requires authentication with the third party service: .. vale off Register the Integration for configuration -############################################## +****************************************** .. vale on @@ -38,7 +35,7 @@ If the Integration has extra configuration settings for features unique to it: .. vale off The Sync Engine -############### +*************** .. vale on @@ -49,7 +46,7 @@ If the Integration syncs with Mautic's Contacts and/or Companies: .. vale off Register the Integration as a Builder -######################################### +************************************* .. vale on diff --git a/docs/plugins/integrations/migrations.rst b/docs/plugins/integrations/migrations.rst index b5f7991f..3450797c 100644 --- a/docs/plugins/integrations/migrations.rst +++ b/docs/plugins/integrations/migrations.rst @@ -1,8 +1,5 @@ -************* Plugin schema -************* - -.. contents:: Table of contents +############# The Integration Framework provides a means for Plugins to better manage their schema. Queries are in migration files that match the Plugin's versions number in its config. When the a Plugin gets install or upgrade, it loops over the migration files up to the latest version. @@ -11,7 +8,7 @@ ____ .. vale off AbstractPluginBundle -#################### +******************** .. vale on @@ -35,7 +32,7 @@ The Plugin's root bundle class should extend:: The Plugin migrations -********************* +===================== Each migration file should be stored in the Plugin's ``Migration`` folder with a name that matches ``Version_X_Y_Z.php`` where ``X_Y_Z`` matches the semantic versioning of the Plugin. Each file should contain the incremental schema changes for the Plugin up to the latest version which should match the version in the Plugin's ``Config/config.php`` file. diff --git a/docs/plugins/integrations/register.rst b/docs/plugins/integrations/register.rst new file mode 100644 index 00000000..585c31e6 --- /dev/null +++ b/docs/plugins/integrations/register.rst @@ -0,0 +1,78 @@ +Registering the integration +########################### + +All Integrations, whether using the config, auth, or sync interfaces, must have a class that registers itself with Mautic. The Integration should list on the ``/s/plugins`` page. + +In the Plugin's ``Config/config.php``, register the Integration using the tag ``mautic.basic_integration``. + +.. code-block:: php + + [ + // ... + 'integrations' => [ + 'helloworld.integration' => [ + 'class' => \MauticPlugin\HelloWorldBundle\Integration\HelloWorldIntegration::class, + 'tags' => [ + 'mautic.basic_integration', + ], + ], + // ... + ], + // ... + ], + // ... + ]; + +The ``HelloWorldIntegration`` needs to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class. + +.. php:class:: \Mautic\IntegrationsBundle\Integration\BasicIntegration + +.. php:method:: public function getName(): string; + + :return: Return the Integration's name. + :returntype: string + +.. php:method:: public function getDisplayName(): string; + + :return: Return the Integration's display name. + :returntype: string + +.. php:method:: public function getIcon(): string; + + :return: Get the path to the Integration's icon. + :returntype: string + + + +.. code-block:: php + + Date: Mon, 31 Oct 2022 09:30:45 +0530 Subject: [PATCH 23/29] Corrected namespace. --- docs/plugins/integrations/authentication.rst | 78 ++++++++++---------- docs/plugins/integrations/configuration.rst | 6 +- docs/plugins/integrations/migrations.rst | 4 +- docs/plugins/integrations/register.rst | 6 +- 4 files changed, 47 insertions(+), 47 deletions(-) diff --git a/docs/plugins/integrations/authentication.rst b/docs/plugins/integrations/authentication.rst index a26f29dd..ffd1d2ba 100644 --- a/docs/plugins/integrations/authentication.rst +++ b/docs/plugins/integrations/authentication.rst @@ -62,8 +62,8 @@ Find the code snippet as follows, use MauticPlugin\HelloWorldBundle\Connection\Client; use MauticPlugin\HelloWorldBundle\Integration\HelloWorldIntegration; - use MauticPlugin\IntegrationsBundle\Integration\ConfigurationTrait; - use MauticPlugin\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface; + use Mautic\IntegrationsBundle\Integration\ConfigurationTrait; + use Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface; use Symfony\Component\HttpFoundation\Request; class AuthSupport implements AuthenticationInterface @@ -134,7 +134,7 @@ The best way to get configuration values such as username, password, consumer ke getIntegration(HelloWorldIntegration::NAME); @@ -176,9 +176,9 @@ Find the code snippet as follows, getIntegration(HelloWorldIntegration::NAME); @@ -232,9 +232,9 @@ Find the code snippet as follows, getIntegration(HelloWorldIntegration::NAME); @@ -290,9 +290,9 @@ Find the code snippet as follows, getIntegration(HelloWorldIntegration::NAME); @@ -343,9 +343,9 @@ OAuth1a two legged does not require a User to login as would three legged. getIntegration(HelloWorldIntegration::NAME); @@ -435,9 +435,9 @@ Use the ``mautic.integrations.auth_provider.token_persistence_factory`` service getIntegration(HelloWorldIntegration::NAME); @@ -471,9 +471,9 @@ The ``IntegrationTokenFactory`` can then be returned in a ``\Mautic\Integrations .. code-block:: php getIntegration(HelloWorldIntegration::NAME); @@ -592,11 +592,11 @@ Below is an example of the client credentials grant for a service that uses a sc getIntegration(HelloWorldIntegration::NAME); @@ -669,7 +669,7 @@ The first step is to register the integration as a :ref:`\\Mautic\\IntegrationsB namespace MauticPlugin\HelloWorldBundle\Integration\Support; use GuzzleHttp\ClientInterface; - use MauticPlugin\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface; + use Mautic\IntegrationsBundle\Integration\Interfaces\AuthenticationInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -700,13 +700,13 @@ Here is an example of a client, assuming that the User has already logged in and Date: Mon, 31 Oct 2022 09:57:38 +0530 Subject: [PATCH 24/29] Corrected header for correct referencing. --- docs/plugins/integrations/authentication.rst | 4 ++-- docs/plugins/integrations/builder.rst | 4 ++-- docs/plugins/integrations/configuration.rst | 4 ++-- docs/plugins/integrations/integrations.rst | 18 +++++++++--------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/plugins/integrations/authentication.rst b/docs/plugins/integrations/authentication.rst index ffd1d2ba..573402aa 100644 --- a/docs/plugins/integrations/authentication.rst +++ b/docs/plugins/integrations/authentication.rst @@ -7,8 +7,8 @@ The IntegrationsBundle provides factories and helpers to create Guzzle Client cl .. vale off -Registering the Integration for Authentication -********************************************** +Register the Integration for Authentication +******************************************* .. vale on diff --git a/docs/plugins/integrations/builder.rst b/docs/plugins/integrations/builder.rst index 93eaae40..15d04bee 100644 --- a/docs/plugins/integrations/builder.rst +++ b/docs/plugins/integrations/builder.rst @@ -7,8 +7,8 @@ Builders can register itself as a "builder" for Email and/or Landing Pages. .. vale off -Registering the Integration as a Builder -**************************************** +Register the Integration as a Builder +************************************* .. vale on diff --git a/docs/plugins/integrations/configuration.rst b/docs/plugins/integrations/configuration.rst index 195ed29a..f8b5f24e 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/plugins/integrations/configuration.rst @@ -5,8 +5,8 @@ The Integration Plugin provides interfaces to display and store configuration op .. vale off -Registering the Integration for Configuration -********************************************* +Register the Integration for Configuration +****************************************** .. vale on diff --git a/docs/plugins/integrations/integrations.rst b/docs/plugins/integrations/integrations.rst index 07300f19..563879f7 100644 --- a/docs/plugins/integrations/integrations.rst +++ b/docs/plugins/integrations/integrations.rst @@ -9,27 +9,27 @@ An example HelloWorld Plugin is available :xref:`here`. .. vale off -Register the Integration for Authentication -******************************************* +Registering the Integration for Authentication +********************************************** .. vale on If the Integration requires authentication with the third party service: -1. :ref:`Register the Integration` as an Integration that requires configuration options. +1. :ref:`Register the Integration` as an Integration that requires authentication options. 2. Create a custom Symfony Form type for the required credentials and return it as part of the :ref:`config interface`. 3. Create a custom service that builds and configures the Guzzle client required to authenticate and communicate with the third party service. Use an :ref:`existing supported factory or create a new one`. .. vale off -Register the Integration for configuration -****************************************** +Registering the Integration for configuration +********************************************* .. vale on If the Integration has extra configuration settings for features unique to it: -1. :ref:`Register the Integration` as an Integration that requires configuration options. +1. :ref:`Register the Integration` as an Integration that requires configuration options. 2. Create a custom Symfony Form type for the features and return it as part of the :ref:`Config Form feature setting interface`. .. vale off @@ -45,12 +45,12 @@ If the Integration syncs with Mautic's Contacts and/or Companies: .. vale off -Register the Integration as a Builder -************************************* +Registering the Integration as a Builder +**************************************** .. vale on If the Integration includes a Builder, Email or Landing Page: -1. :ref:`Register the Integration` as an Integration that provides a custom builder. +1. :ref:`Register the Integration` as an Integration that provides a custom builder. 2. Configure what featured builders the Integration supports (Mautic currently supports 'Email' and 'Landing Page' builders). \ No newline at end of file From ebd767207aff71c698a0aa7c87b4e20ff018aed8 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Wed, 2 Nov 2022 13:31:40 +0530 Subject: [PATCH 25/29] Rearranged the docs for better fitment. --- docs/components/integrations.rst | 105 +++++++++++++++++- .../integrations_authentication.rst} | 6 +- .../integrations_builder.rst} | 4 + .../integrations_configuration.rst} | 4 + ...integrations_configuration_form_notes.rst} | 0 .../integrations_sync.rst} | 4 + docs/index.rst | 4 +- docs/plugins/integrations/index.rst | 18 --- docs/plugins/integrations/integrations.rst | 56 ---------- docs/plugins/integrations/migrations.rst | 73 ------------ docs/plugins/integrations/register.rst | 78 ------------- 11 files changed, 120 insertions(+), 232 deletions(-) rename docs/{plugins/integrations/authentication.rst => components/integrations_authentication.rst} (97%) rename docs/{plugins/integrations/builder.rst => components/integrations_builder.rst} (95%) rename docs/{plugins/integrations/configuration.rst => components/integrations_configuration.rst} (99%) rename docs/{plugins/integrations/configuration_form_notes.rst => components/integrations_configuration_form_notes.rst} (100%) rename docs/{plugins/integrations/sync.rst => components/integrations_sync.rst} (98%) delete mode 100644 docs/plugins/integrations/index.rst delete mode 100644 docs/plugins/integrations/integrations.rst delete mode 100644 docs/plugins/integrations/migrations.rst delete mode 100644 docs/plugins/integrations/register.rst diff --git a/docs/components/integrations.rst b/docs/components/integrations.rst index 10bc8ac1..783e05bd 100644 --- a/docs/components/integrations.rst +++ b/docs/components/integrations.rst @@ -1,20 +1,119 @@ Integrations ############ +Each Integration provides its unique name as registered with Mautic, an icon, and a display name. When an Integration registers, the Integration helper classes manage the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the Integration's API keys, so the implementing code never has to. + +---- + +Registering the integration +*************************** + +All Integrations, whether using the config, auth, or sync interfaces, must have a class that registers itself with Mautic. The Integration should list on the ``/s/plugins`` page. + +In the Plugin's ``Config/config.php``, register the Integration using the tag ``mautic.basic_integration``. + +.. code-block:: php + + [ + // ... + 'integrations' => [ + 'helloworld.integration' => [ + 'class' => \MauticPlugin\HelloWorldBundle\Integration\HelloWorldIntegration::class, + 'tags' => [ + 'mautic.basic_integration', + ], + ], + // ... + ], + // ... + ], + // ... + ]; + +The ``HelloWorldIntegration`` needs to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class. + +.. php:class:: \Mautic\IntegrationsBundle\Integration\BasicIntegration + +.. php:method:: public function getName(): string; + + :return: Return the Integration's name. + :returntype: string + +.. php:method:: public function getDisplayName(): string; + + :return: Return the Integration's display name. + :returntype: string + +.. php:method:: public function getIcon(): string; + + :return: Get the path to the Integration's icon. + :returntype: string + + + +.. code-block:: php + + ` as an Integration that requires authentication options. +2. Create a custom Symfony Form type for the required credentials and return it as part of the :ref:`config interface`. +3. Create a custom service that builds and configures the Guzzle client required to authenticate and communicate with the third party service. Use an :ref:`existing supported factory or create a new one`. + Integration configuration ************************* + +If the Integration has extra configuration settings for features unique to it: + +1. :ref:`Register the Integration` as an Integration that requires configuration options. +2. Create a custom Symfony Form type for the features and return it as part of the :ref:`Config Form feature setting interface`. + + Integration sync engine *********************** -Sync notification handlers -========================== +If the Integration syncs with Mautic's Contacts and/or Companies: -``\Mautic\IntegrationsBundle\Sync\Notification\Handler\HandlerInterface`` +1. Read about :ref:`the sync engine`. Integration Builders ******************** +If the Integration includes a Builder, Email or Landing Page: + +1. :ref:`Register the Integration` as an Integration that provides a custom builder. +2. Configure what featured builders the Integration supports (Mautic currently supports 'Email' and 'Landing Page' builders). diff --git a/docs/plugins/integrations/authentication.rst b/docs/components/integrations_authentication.rst similarity index 97% rename from docs/plugins/integrations/authentication.rst rename to docs/components/integrations_authentication.rst index 573402aa..e2c1f304 100644 --- a/docs/plugins/integrations/authentication.rst +++ b/docs/components/integrations_authentication.rst @@ -1,3 +1,7 @@ +.. It is a reference only page, not a part of doc tree. + +:orphan: + Authentication integration ########################## @@ -661,7 +665,7 @@ OAuth2 three legged Three legged OAuth2 with the code grant is the most complex to implement because it involves redirecting the user to the third party service to authenticate then sent back to Mautic to initiate the access token process using a code returned in the request. -The first step is to register the integration as a :ref:`\\Mautic\\IntegrationsBundle\\Integration\\Interfaces\\AuthenticationInterface`. The ``authenticateIntegration()`` method initiates the access token process using the ``code`` returned in the request after the user logs into the third-party service. The Integration bundle provides a route that can use as the redirect or callback URIs through the named route ``mautic_integration_public_callback`` that requires a ``integration`` parameter. This redirect URI can display in the UI by using :xref:`ConfigFormCallbackInterface`. This route is to find the integration by name from the ``AuthIntegrationsHelper`` and then execute its ``authenticateIntegration()``. +The first step is to register the integration as a :ref:`\\Mautic\\IntegrationsBundle\\Integration\\Interfaces\\AuthenticationInterface`. The ``authenticateIntegration()`` method initiates the access token process using the ``code`` returned in the request after the user logs into the third-party service. The Integration bundle provides a route that can use as the redirect or callback URIs through the named route ``mautic_integration_public_callback`` that requires a ``integration`` parameter. This redirect URI can display in the UI by using :xref:`ConfigFormCallbackInterface`. This route is to find the integration by name from the ``AuthIntegrationsHelper`` and then execute its ``authenticateIntegration()``. .. code-block:: php diff --git a/docs/plugins/integrations/builder.rst b/docs/components/integrations_builder.rst similarity index 95% rename from docs/plugins/integrations/builder.rst rename to docs/components/integrations_builder.rst index 15d04bee..b4a1b742 100644 --- a/docs/plugins/integrations/builder.rst +++ b/docs/components/integrations_builder.rst @@ -1,3 +1,7 @@ +.. It is a reference only page, not a part of doc tree. + +:orphan: + Builder integrations ##################### diff --git a/docs/plugins/integrations/configuration.rst b/docs/components/integrations_configuration.rst similarity index 99% rename from docs/plugins/integrations/configuration.rst rename to docs/components/integrations_configuration.rst index f8b5f24e..c7b7ee05 100644 --- a/docs/plugins/integrations/configuration.rst +++ b/docs/components/integrations_configuration.rst @@ -1,3 +1,7 @@ +.. It is a reference only page, not a part of doc tree. + +:orphan: + Configuration integration ######################### diff --git a/docs/plugins/integrations/configuration_form_notes.rst b/docs/components/integrations_configuration_form_notes.rst similarity index 100% rename from docs/plugins/integrations/configuration_form_notes.rst rename to docs/components/integrations_configuration_form_notes.rst diff --git a/docs/plugins/integrations/sync.rst b/docs/components/integrations_sync.rst similarity index 98% rename from docs/plugins/integrations/sync.rst rename to docs/components/integrations_sync.rst index 27d25694..d9b6ae8e 100644 --- a/docs/plugins/integrations/sync.rst +++ b/docs/components/integrations_sync.rst @@ -1,3 +1,7 @@ +.. It is a reference only page, not a part of doc tree. + +:orphan: + Sync engine ########### diff --git a/docs/index.rst b/docs/index.rst index 433058ab..ffcafd5f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -49,16 +49,14 @@ This is a work in progress. More to come soon. In the meantime, go to :xref:`Leg plugins/getting_started plugins/mautic_vs_symfony plugins/dependencies - plugins/event_listeners plugins/structure plugins/config + plugins/event_listeners plugins/installation plugins/data plugins/translations - plugins/integrations/index .. toctree:: - :maxdepth: 2 :caption: Components :hidden: diff --git a/docs/plugins/integrations/index.rst b/docs/plugins/integrations/index.rst deleted file mode 100644 index 0be23a45..00000000 --- a/docs/plugins/integrations/index.rst +++ /dev/null @@ -1,18 +0,0 @@ -Integration framework -##################### - -Each Integration provides its unique name as registered with Mautic, an icon, and a display name. When an Integration registers, the Integration helper classes manage the ``\Mautic\PluginBundle\Entity\Integration`` object through ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface``. It handles decryption and encryption of the Integration's API keys, so the implementing code never has to. - ----- - -.. toctree:: - :caption: Integration Framework - :hidden: - - register - integrations - authentication - builder - configuration - migrations - sync diff --git a/docs/plugins/integrations/integrations.rst b/docs/plugins/integrations/integrations.rst deleted file mode 100644 index 563879f7..00000000 --- a/docs/plugins/integrations/integrations.rst +++ /dev/null @@ -1,56 +0,0 @@ -Using the integration framework -############################### - -The IntegrationsBundle is a drop-in replacement for ``PluginBundle``'s ``AbstractIntegration`` class. It provides cleaner interfaces for configuring, authenticating, and syncing Contacts/Companies with third-party Integrations. - -An example HelloWorld Plugin is available :xref:`here`. - ---------- - -.. vale off - -Registering the Integration for Authentication -********************************************** - -.. vale on - -If the Integration requires authentication with the third party service: - -1. :ref:`Register the Integration` as an Integration that requires authentication options. -2. Create a custom Symfony Form type for the required credentials and return it as part of the :ref:`config interface`. -3. Create a custom service that builds and configures the Guzzle client required to authenticate and communicate with the third party service. Use an :ref:`existing supported factory or create a new one`. - -.. vale off - -Registering the Integration for configuration -********************************************* - -.. vale on - -If the Integration has extra configuration settings for features unique to it: - -1. :ref:`Register the Integration` as an Integration that requires configuration options. -2. Create a custom Symfony Form type for the features and return it as part of the :ref:`Config Form feature setting interface`. - -.. vale off - -The Sync Engine -*************** - -.. vale on - -If the Integration syncs with Mautic's Contacts and/or Companies: - -1. Read about :doc:`the sync engine`. - -.. vale off - -Registering the Integration as a Builder -**************************************** - -.. vale on - -If the Integration includes a Builder, Email or Landing Page: - -1. :ref:`Register the Integration` as an Integration that provides a custom builder. -2. Configure what featured builders the Integration supports (Mautic currently supports 'Email' and 'Landing Page' builders). \ No newline at end of file diff --git a/docs/plugins/integrations/migrations.rst b/docs/plugins/integrations/migrations.rst deleted file mode 100644 index e8e7233d..00000000 --- a/docs/plugins/integrations/migrations.rst +++ /dev/null @@ -1,73 +0,0 @@ -Plugin schema -############# - -The Integration Framework provides a means for Plugins to better manage their schema. Queries are in migration files that match the Plugin's versions number in its config. When the a Plugin gets install or upgrade, it loops over the migration files up to the latest version. - -____ - -.. vale off - -AbstractPluginBundle -******************** - -.. vale on - -The Plugin's root bundle class should extend:: - - Mautic\IntegrationsBundle\Bundle\AbstractPluginBundle - -.. code-block:: php - - getTable($this->concatPrefix($this->table))->hasColumn('is_enabled'); - } catch (SchemaException $e) { - return false; - } - } - - protected function up(): void - { - $this->addSql("ALTER TABLE `{$this->concatPrefix($this->table)}` ADD `is_enabled` tinyint(1) 0"); - - $this->addSql("CREATE INDEX {$this->concatPrefix('is_enabled')} ON {$this->concatPrefix($this->table)}(is_enabled);"); - } - } - diff --git a/docs/plugins/integrations/register.rst b/docs/plugins/integrations/register.rst deleted file mode 100644 index ccc7f03f..00000000 --- a/docs/plugins/integrations/register.rst +++ /dev/null @@ -1,78 +0,0 @@ -Registering the integration -########################### - -All Integrations, whether using the config, auth, or sync interfaces, must have a class that registers itself with Mautic. The Integration should list on the ``/s/plugins`` page. - -In the Plugin's ``Config/config.php``, register the Integration using the tag ``mautic.basic_integration``. - -.. code-block:: php - - [ - // ... - 'integrations' => [ - 'helloworld.integration' => [ - 'class' => \MauticPlugin\HelloWorldBundle\Integration\HelloWorldIntegration::class, - 'tags' => [ - 'mautic.basic_integration', - ], - ], - // ... - ], - // ... - ], - // ... - ]; - -The ``HelloWorldIntegration`` needs to implement ``\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`` and ``\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface`` interfaces. Most use cases can simply extend the ``\Mautic\IntegrationsBundle\Integration\BasicIntegration`` abstract class. - -.. php:class:: \Mautic\IntegrationsBundle\Integration\BasicIntegration - -.. php:method:: public function getName(): string; - - :return: Return the Integration's name. - :returntype: string - -.. php:method:: public function getDisplayName(): string; - - :return: Return the Integration's display name. - :returntype: string - -.. php:method:: public function getIcon(): string; - - :return: Get the path to the Integration's icon. - :returntype: string - - - -.. code-block:: php - - Date: Wed, 2 Nov 2022 13:53:48 +0530 Subject: [PATCH 26/29] Corrected doc references. --- docs/components/integrations_configuration.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/components/integrations_configuration.rst b/docs/components/integrations_configuration.rst index c7b7ee05..c921ebcf 100644 --- a/docs/components/integrations_configuration.rst +++ b/docs/components/integrations_configuration.rst @@ -204,7 +204,7 @@ Contact/Company syncing interfaces The IntegrationsBundle provides a sync framework for third party services to sync with Mautic's Contacts and Companies. The ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface`` determines the configuration options for this sync feature. Refer to the method DocBlocks in the interface for more details. -Read more about how to leverage the :doc:`sync framework`. +Read more about how to leverage the :doc:`sync framework`. .. vale off @@ -215,4 +215,4 @@ Config Form notes interface The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``, provides a way to display notes, either info or warning, on the plugin configuration Form. -Read more about to how-tos :doc:`here` +Read more about to how-tos :doc:`here` From f342a2e7bc8f01ea301b468d7c3ddf21c49e57f9 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Wed, 25 Jan 2023 18:28:42 +0530 Subject: [PATCH 27/29] Removed bad lines and fixed vale errors. --- docs/components/integrations_authentication.rst | 4 +--- docs/components/integrations_configuration.rst | 2 +- docs/components/integrations_configuration_form_notes.rst | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/components/integrations_authentication.rst b/docs/components/integrations_authentication.rst index 6b8a8310..c12efab0 100644 --- a/docs/components/integrations_authentication.rst +++ b/docs/components/integrations_authentication.rst @@ -275,7 +275,6 @@ Basic auth Use the ``mautic.integrations.auth_provider.basic_auth`` service - ``\Mautic\IntegrationsBundle\Auth\Provider\BasicAuth\HttpFactory`` - to obtain a ``GuzzleHttp\ClientInterface`` that uses basic auth for all requests. - To use the basic auth, create a credentials class that implements ``\Mautic\IntegrationsBundle\Auth\Provider\BasicAuth\CredentialsInterface``. .. php:class:: \Mautic\IntegrationsBundle\Auth\Provider\BasicAuth\CredentialsInterface @@ -343,7 +342,7 @@ Yet to implement in the core. OAuth1a two legged ------------------ -OAuth1a two legged does not require a User to login as would three legged. +OAuth1a two legged doesn't require a User to login as would three legged. .. code-block:: php @@ -427,7 +426,6 @@ Client configuration Both OAuth2 factories leverage the ``\Mautic\IntegrationsBundle\Auth\Provider\AuthConfigInterface`` object to manage things such as configuring the signer (basic auth, ``post form data``, custom), token factory, token persistence, and token signer (bearer auth, basic auth, query string, custom). Use the appropriate interfaces as required for the use case (see the interfaces in ``app/bundles/IntegrationsBundle/Auth/Support/Oauth2/ConfigAccess``). - See :xref:`Guzzle Oauth2 Subscriber` for additional details on configuring the credentials and token signers or creating custom token persistence and factories. Token persistence diff --git a/docs/components/integrations_configuration.rst b/docs/components/integrations_configuration.rst index 79c40311..5f06c172 100644 --- a/docs/components/integrations_configuration.rst +++ b/docs/components/integrations_configuration.rst @@ -221,6 +221,6 @@ Config Form notes interface .. vale on -The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``, provides a way to display notes, either info or warning, on the plugin configuration Form. +The interface, ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``, provides a way to display notes, either info or warning, on the Plugin configuration Form. Read more about to how-tos :doc:`here` diff --git a/docs/components/integrations_configuration_form_notes.rst b/docs/components/integrations_configuration_form_notes.rst index d931771e..ca1de86c 100644 --- a/docs/components/integrations_configuration_form_notes.rst +++ b/docs/components/integrations_configuration_form_notes.rst @@ -2,7 +2,7 @@ :orphan: -Integration configuration form notes +Integration configuration Form notes #################################### The Integration framework lets developer define their custom messages for the Plugin's configuration Form. From 2cacedfdc7fa4fedbe214c3915c5b6d1d2615ca9 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Wed, 25 Jan 2023 18:31:45 +0530 Subject: [PATCH 28/29] Fixed vale errors. --- docs/components/integrations_configuration_form_notes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/components/integrations_configuration_form_notes.rst b/docs/components/integrations_configuration_form_notes.rst index ca1de86c..d931771e 100644 --- a/docs/components/integrations_configuration_form_notes.rst +++ b/docs/components/integrations_configuration_form_notes.rst @@ -2,7 +2,7 @@ :orphan: -Integration configuration Form notes +Integration configuration form notes #################################### The Integration framework lets developer define their custom messages for the Plugin's configuration Form. From c8dfb6d068d77aeb6ad6f43973fe04b0dcee10a0 Mon Sep 17 00:00:00 2001 From: Rahul Shinde Date: Wed, 25 Jan 2023 18:59:33 +0530 Subject: [PATCH 29/29] Fixed vale errors. --- docs/components/integrations_configuration_form_notes.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/components/integrations_configuration_form_notes.rst b/docs/components/integrations_configuration_form_notes.rst index d931771e..c2d60bd5 100644 --- a/docs/components/integrations_configuration_form_notes.rst +++ b/docs/components/integrations_configuration_form_notes.rst @@ -2,9 +2,13 @@ :orphan: +.. vale off + Integration configuration form notes #################################### +.. vale on + The Integration framework lets developer define their custom messages for the Plugin's configuration Form. The ``ConfigSupport`` class should implement the ``\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface``