Initial commit: CloudOps infrastructure platform

This commit is contained in:
root
2026-04-09 19:58:57 +02:00
commit 1166a52f26
7762 changed files with 839452 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
# Workflow name:
name: Close Pull Requests
# Workflow triggers:
on:
pull_request_target:
types: [opened]
# Workflow jobs:
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: superbrothers/close-pull-request@v3
with:
comment: |
Thank you for submitting a pull request. :raised_hands:
We greatly appreciate your willingness to submit a contribution. However, we are not accepting pull requests against this repository, as all development happens on the [main project repository](https://github.com/mautic/mautic).
We kindly request that you submit this pull request against the [respective directory](https://github.com/mautic/mautic/blob/head/plugins/MauticEmailMarketingBundle) of the main repository where we'll review and provide feedback. If this is your first Mautic contribution, be sure to read the [contributing guide](https://github.com/mautic/mautic/blob/4.x/.github/CONTRIBUTING.md) which provides guidelines and instructions for submitting contributions.
Thank you again, and we look forward to receiving your contribution! :smiley:
Best,
The Mautic team

View File

@@ -0,0 +1,68 @@
<?php
namespace MauticPlugin\MauticEmailMarketingBundle\Api;
use Mautic\PluginBundle\Exception\ApiErrorException;
class ConstantContactApi extends EmailMarketingApi
{
private string $version = 'v2';
protected function request($endpoint, $parameters = [], $method = 'GET', $query = [])
{
$url = sprintf('https://api.constantcontact.com/%s/%s?api_key=%s', $this->version, $endpoint, $this->keys['client_id']);
$response = $this->integration->makeRequest($url, $parameters, $method, [
'encode_parameters' => 'json',
'append_auth_token' => true,
'query' => $query,
]);
if (is_array($response) && !empty($response[0]['error_message'])) {
$errors = [];
foreach ($response as $error) {
$errors[] = $error['error_message'];
}
throw new ApiErrorException(implode(' ', $errors));
} else {
return $response;
}
}
/**
* @return mixed|string
*
* @throws ApiErrorException
*/
public function getLists()
{
return $this->request('lists');
}
/**
* @param array $fields
* @param array $config
*
* @return mixed|string
*
* @throws ApiErrorException
*/
public function subscribeLead($email, $listId, $fields = [], $config = [])
{
$parameters = array_merge($fields, [
'lists' => [
['id' => "$listId"],
],
'email_addresses' => [
['email_address' => $email],
],
]);
$query = [
'action_by' => $config['action_by'],
];
return $this->request('contacts', $parameters, 'POST', $query);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace MauticPlugin\MauticEmailMarketingBundle\Api;
use Mautic\PluginBundle\Integration\AbstractIntegration;
use Mautic\PluginBundle\Integration\UnifiedIntegrationInterface;
class EmailMarketingApi
{
protected $keys;
/**
* @param AbstractIntegration $integration
*/
public function __construct(
protected UnifiedIntegrationInterface $integration,
) {
$this->keys = $integration->getKeys();
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace MauticPlugin\MauticEmailMarketingBundle\Api;
use Mautic\PluginBundle\Exception\ApiErrorException;
class IcontactApi extends EmailMarketingApi
{
protected function request($endpoint, $parameters = [], $method = 'GET')
{
$url = sprintf('%s/%s/c/%s/%s', $this->integration->getApiUrl(), $this->keys['accountId'], $this->keys['clientFolderId'], $endpoint);
$response = $this->integration->makeRequest($url, $parameters, $method, [
'encode_parameters' => 'json',
'encoding_headers_set' => true,
]);
if (is_array($response) && !empty($response['errors'])) {
throw new ApiErrorException(implode(' ', $response['errors']));
} else {
return $response;
}
}
/**
* @return mixed|string
*
* @throws ApiErrorException
*/
public function getLists()
{
return $this->request('lists');
}
/**
* @return mixed|string
*
* @throws ApiErrorException
*/
public function getCustomFields()
{
return $this->request('customfields');
}
/**
* @param array $fields
*
* @return mixed|string
*
* @throws ApiErrorException
*/
public function subscribeLead($listId, $fields)
{
$fields['status'] = 'normal';
$contacts = $this->request('contacts', [$fields], 'POST');
if (!empty($contacts['contacts'][0]['contactId'])) {
$contactId = $contacts['contacts'][0]['contactId'];
$fields = [
'status' => 'normal',
'listId' => $listId,
'contactId' => $contactId,
];
return $this->request('subscriptions', [$fields], 'POST');
}
return $contacts;
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace MauticPlugin\MauticEmailMarketingBundle\Api;
use Mautic\PluginBundle\Exception\ApiErrorException;
class MailchimpApi extends EmailMarketingApi
{
private string $version = '3.0';
/**
* @param array $parameters
* @param string $method
*
* @return mixed|string
*
* @throws ApiErrorException
*/
protected function request($endpoint, $parameters = [], $method = 'GET')
{
if (isset($this->keys['password'])) {
// Extract the dc from the key
$parts = explode('-', $this->keys['password']);
if (2 !== count($parts)) {
throw new ApiErrorException('Invalid key');
}
$dc = $parts[1];
$apiUrl = 'https://'.$dc.'.api.mailchimp.com';
$parameters['apikey'] = $this->keys['password'];
} else {
$apiUrl = $this->keys['api_endpoint'];
$parameters['apikey'] = $this->keys['access_token'];
}
$url = sprintf('%s/%s/%s', $apiUrl, $this->version, $endpoint);
$response = $this->integration->makeRequest($url, $parameters, $method, ['encode_parameters' => 'json']);
if (is_array($response) && !empty($response['status']) && 'error' == $response['status']) {
throw new ApiErrorException($response['error']);
} elseif (is_array($response) && !empty($response['errors'])) {
$errors = [];
foreach ($response['errors'] as $error) {
$errors[] = $error['message'];
}
throw new ApiErrorException(implode(' ', $errors));
} else {
return $response;
}
}
public function getLists()
{
return $this->request('lists', ['limit' => 100]);
}
/**
* @return mixed|string
*
* @throws ApiErrorException
*/
public function getCustomFields($listId)
{
return $this->request('lists/'.$listId.'/merge-fields');
}
/**
* @param array $fields
* @param array $config
*
* @return mixed|string
*
* @throws ApiErrorException
*/
public function subscribeLead($email, $listId, $fields = [], $config = [])
{
$emailStruct = new \stdClass();
$emailStruct->email = $email;
$parameters = array_merge($config, [
'id' => $listId,
]);
if (!empty($fields)) {
$parameters = array_merge($parameters, ['merge_fields' => $fields]);
}
$parameters['email_address'] = $email;
return $this->request('lists/'.$listId.'/members', $parameters, 'POST');
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,76 @@
<?php
return [
'name' => 'Email Marketing',
'description' => 'Enables integration with Mautic supported email marketing services.',
'version' => '1.0',
'author' => 'Mautic',
'services' => [
'integrations' => [
'mautic.integration.constantcontact' => [
'class' => MauticPlugin\MauticEmailMarketingBundle\Integration\ConstantContactIntegration::class,
'arguments' => [
'event_dispatcher',
'mautic.helper.cache_storage',
'doctrine.orm.entity_manager',
'request_stack',
'router',
'translator',
'monolog.logger.mautic',
'mautic.helper.encryption',
'mautic.lead.model.lead',
'mautic.lead.model.company',
'mautic.helper.paths',
'mautic.core.model.notification',
'mautic.lead.model.field',
'mautic.plugin.model.integration_entity',
'mautic.lead.model.dnc',
'mautic.lead.field.fields_with_unique_identifier',
],
],
'mautic.integration.icontact' => [
'class' => MauticPlugin\MauticEmailMarketingBundle\Integration\IcontactIntegration::class,
'arguments' => [
'event_dispatcher',
'mautic.helper.cache_storage',
'doctrine.orm.entity_manager',
'request_stack',
'router',
'translator',
'monolog.logger.mautic',
'mautic.helper.encryption',
'mautic.lead.model.lead',
'mautic.lead.model.company',
'mautic.helper.paths',
'mautic.core.model.notification',
'mautic.lead.model.field',
'mautic.plugin.model.integration_entity',
'mautic.lead.model.dnc',
'mautic.lead.field.fields_with_unique_identifier',
],
],
'mautic.integration.mailchimp' => [
'class' => MauticPlugin\MauticEmailMarketingBundle\Integration\MailchimpIntegration::class,
'arguments' => [
'event_dispatcher',
'mautic.helper.cache_storage',
'doctrine.orm.entity_manager',
'request_stack',
'router',
'translator',
'monolog.logger.mautic',
'mautic.helper.encryption',
'mautic.lead.model.lead',
'mautic.lead.model.company',
'mautic.helper.paths',
'mautic.core.model.notification',
'mautic.lead.model.field',
'mautic.plugin.model.integration_entity',
'mautic.lead.model.dnc',
'mautic.lead.field.fields_with_unique_identifier',
],
],
],
],
];

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
use Mautic\CoreBundle\DependencyInjection\MauticCoreExtension;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return function (ContainerConfigurator $configurator): void {
$services = $configurator->services()
->defaults()
->autowire()
->autoconfigure()
->public();
$excludes = [
'Api',
];
$services->load('MauticPlugin\\MauticEmailMarketingBundle\\', '../')
->exclude('../{'.implode(',', array_merge(MauticCoreExtension::DEFAULT_EXCLUDES, $excludes)).'}');
};

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace MauticPlugin\MauticEmailMarketingBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
class MauticEmailMarketingExtension extends Extension
{
/**
* @param mixed[] $configs
*/
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Config'));
$loader->load('services.php');
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace MauticPlugin\MauticEmailMarketingBundle\Form\Type;
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\PluginBundle\Form\Type\FieldsType;
use Mautic\PluginBundle\Helper\IntegrationHelper;
use Mautic\PluginBundle\Model\PluginModel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @extends AbstractType<array<mixed>>
*/
class ConstantContactType extends AbstractType
{
public function __construct(
private IntegrationHelper $integrationHelper,
private PluginModel $pluginModel,
protected RequestStack $requestStack,
protected CoreParametersHelper $coreParametersHelper,
) {
}
/**
* @param FormBuilderInterface<array<mixed>|null> $builder
* @param array<string, mixed> $options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void
{
/** @var \MauticPlugin\MauticEmailMarketingBundle\Integration\ConstantContactIntegration $object */
$object = $this->integrationHelper->getIntegrationObject('ConstantContact');
$integrationName = $object->getName();
$session = $this->requestStack->getSession();
$limit = $session->get(
'mautic.plugin.'.$integrationName.'.lead.limit',
$this->coreParametersHelper->get('default_pagelimit')
);
$page = $session->get('mautic.plugin.'.$integrationName.'.lead.page', 1);
$api = $object->getApiHelper();
try {
$lists = $api->getLists();
$choices = [];
if (!empty($lists)) {
foreach ($lists as $list) {
$choices[$list['id']] = $list['name'];
}
asort($choices);
}
} catch (\Exception $e) {
$choices = [];
$error = $e->getMessage();
$page = 1;
}
$builder->add('list', ChoiceType::class, [
'choices' => array_flip($choices), // Choice type expects labels as keys
'label' => 'mautic.emailmarketing.list',
'required' => false,
'attr' => [
'tooltip' => 'mautic.emailmarketing.list.tooltip',
],
]);
$builder->add('sendWelcome', YesNoButtonGroupType::class, [
'label' => 'mautic.emailmarketing.send_welcome',
'data' => (!isset($options['data']['sendWelcome'])) ? true : $options['data']['sendWelcome'],
]);
if (!empty($error)) {
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($error): void {
$form = $event->getForm();
if ($error) {
$form['list']->addError(new FormError($error));
}
});
}
if (isset($options['form_area']) && 'integration' == $options['form_area']) {
$leadFields = $this->pluginModel->getLeadFields();
$fields = $object->getFormLeadFields();
[$specialInstructions, $alertType] = $object->getFormNotes('leadfield_match');
$builder->add('leadFields', FieldsType::class, [
'label' => 'mautic.integration.leadfield_matches',
'required' => true,
'mautic_fields' => $leadFields,
'integration' => $object->getName(),
'integration_object' => $object,
'limit' => $limit,
'page' => $page,
'data' => $options['data'] ?? [],
'integration_fields' => $fields,
'special_instructions' => $specialInstructions,
'mapped' => true,
'alert_type' => $alertType,
]);
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefined(['form_area']);
}
public function getBlockPrefix(): string
{
return 'emailmarketing_constantcontact';
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace MauticPlugin\MauticEmailMarketingBundle\Form\Type;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\PluginBundle\Form\Type\FieldsType;
use Mautic\PluginBundle\Helper\IntegrationHelper;
use Mautic\PluginBundle\Model\PluginModel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @extends AbstractType<array<mixed>>
*/
class IcontactType extends AbstractType
{
public function __construct(
private IntegrationHelper $integrationHelper,
private PluginModel $pluginModel,
protected RequestStack $requestStack,
protected CoreParametersHelper $coreParametersHelper,
) {
}
/**
* @param FormBuilderInterface<array<mixed>|null> $builder
* @param array<string, mixed> $options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void
{
/** @var \MauticPlugin\MauticEmailMarketingBundle\Integration\IcontactIntegration $object */
$object = $this->integrationHelper->getIntegrationObject('Icontact');
$integrationName = $object->getName();
$session = $this->requestStack->getSession();
$limit = $session->get(
'mautic.plugin.'.$integrationName.'.lead.limit',
$this->coreParametersHelper->get('default_pagelimit')
);
$page = $session->get('mautic.plugin.'.$integrationName.'.lead.page', 1);
$api = $object->getApiHelper();
try {
$lists = $api->getLists();
$choices = [];
if (!empty($lists['lists'])) {
foreach ($lists['lists'] as $list) {
$choices[$list['listId']] = $list['name'];
}
asort($choices);
}
} catch (\Exception $e) {
$choices = [];
$error = $e->getMessage();
$page = 1;
}
$builder->add('list', ChoiceType::class, [
'choices' => array_flip($choices), // Choice type expects labels as keys
'label' => 'mautic.emailmarketing.list',
'required' => false,
'attr' => [
'tooltip' => 'mautic.emailmarketing.list.tooltip',
],
]);
if (!empty($error)) {
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($error): void {
$form = $event->getForm();
if ($error) {
$form['list']->addError(new FormError($error));
}
});
}
if (isset($options['form_area']) && 'integration' == $options['form_area']) {
$leadFields = $this->pluginModel->getLeadFields();
$fields = $object->getFormLeadFields();
[$specialInstructions, $alertType] = $object->getFormNotes('leadfield_match');
$builder->add('leadFields', FieldsType::class, [
'label' => 'mautic.integration.leadfield_matches',
'required' => true,
'mautic_fields' => $leadFields,
'integration' => $object->getName(),
'integration_object' => $object,
'limit' => $limit,
'page' => $page,
'data' => $options['data'] ?? [],
'integration_fields' => $fields,
'special_instructions' => $specialInstructions,
'mapped' => true,
'error_bubbling' => false,
]);
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefined(['form_area']);
}
public function getBlockPrefix(): string
{
return 'emailmarketing_icontact';
}
}

View File

@@ -0,0 +1,173 @@
<?php
namespace MauticPlugin\MauticEmailMarketingBundle\Form\Type;
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\PluginBundle\Form\Type\FieldsType;
use Mautic\PluginBundle\Helper\IntegrationHelper;
use Mautic\PluginBundle\Model\PluginModel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @extends AbstractType<array<mixed>>
*/
class MailchimpType extends AbstractType
{
public function __construct(
private IntegrationHelper $integrationHelper,
private PluginModel $pluginModel,
protected RequestStack $requestStack,
protected CoreParametersHelper $coreParametersHelper,
) {
}
/**
* @param FormBuilderInterface<array<mixed>|null> $builder
* @param array<string, mixed> $options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void
{
/** @var \MauticPlugin\MauticEmailMarketingBundle\Integration\MailchimpIntegration $mailchimp */
$mailchimp = $this->integrationHelper->getIntegrationObject('Mailchimp');
$api = $mailchimp->getApiHelper();
try {
$lists = $api->getLists();
$choices = [];
if (!empty($lists)) {
if ($lists['total_items']) {
foreach ($lists['lists'] as $list) {
$choices[$list['id']] = $list['name'];
}
}
asort($choices);
}
} catch (\Exception $e) {
$choices = [];
$error = $e->getMessage();
}
$builder->add('list', ChoiceType::class, [
'choices' => array_flip($choices), // Choice type expects labels as keys
'label' => 'mautic.emailmarketing.list',
'required' => false,
'attr' => [
'tooltip' => 'mautic.emailmarketing.list.tooltip',
'onchange' => 'Mautic.getIntegrationLeadFields(\'Mailchimp\', this, {"list": this.value});',
],
]);
$builder->add('doubleOptin', YesNoButtonGroupType::class, [
'label' => 'mautic.mailchimp.double_optin',
'data' => (!isset($options['data']['doubleOptin'])) ? true : $options['data']['doubleOptin'],
]);
$builder->add('sendWelcome', YesNoButtonGroupType::class, [
'label' => 'mautic.emailmarketing.send_welcome',
'data' => (!isset($options['data']['sendWelcome'])) ? true : $options['data']['sendWelcome'],
]);
if (!empty($error)) {
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($error): void {
$form = $event->getForm();
if ($error) {
$form['list']->addError(new FormError($error));
}
});
}
if (isset($options['form_area']) && 'integration' == $options['form_area']) {
$leadFields = $this->pluginModel->getLeadFields();
$formModifier = function (FormInterface $form, $data) use ($mailchimp, $leadFields): void {
$integrationName = $mailchimp->getName();
$session = $this->requestStack->getSession();
$limit = $session->get(
'mautic.plugin.'.$integrationName.'.lead.limit',
$this->coreParametersHelper->get('default_pagelimit')
);
$page = $session->get('mautic.plugin.'.$integrationName.'.lead.page', 1);
$settings = [
'silence_exceptions' => false,
'feature_settings' => [
'list_settings' => $data,
],
'ignore_field_cache' => (1 == $page && 'POST' !== $_SERVER['REQUEST_METHOD']) ? true : false,
];
try {
$fields = $mailchimp->getFormLeadFields($settings);
if (!is_array($fields)) {
$fields = [];
}
$error = '';
} catch (\Exception $e) {
$fields = [];
$error = $e->getMessage();
$page = 1;
}
[$specialInstructions] = $mailchimp->getFormNotes('leadfield_match');
$form->add('leadFields', FieldsType::class, [
'label' => 'mautic.integration.leadfield_matches',
'required' => true,
'mautic_fields' => $leadFields,
'integration' => $mailchimp->getName(),
'integration_object' => $mailchimp,
'limit' => $limit,
'page' => $page,
'data' => $data,
'integration_fields' => $fields,
'special_instructions' => $specialInstructions,
'mapped' => true,
'error_bubbling' => false,
]);
if ($error) {
$form->addError(new FormError($error));
}
};
$builder->addEventListener(FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier): void {
$data = $event->getData();
if (isset($data['leadFields']['leadFields'])) {
$data['leadFields'] = $data['leadFields']['leadFields'];
}
$formModifier($event->getForm(), $data);
}
);
$builder->addEventListener(FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($formModifier): void {
$data = $event->getData();
if (isset($data['leadFields']['leadFields'])) {
$data['leadFields'] = $data['leadFields']['leadFields'];
}
$formModifier($event->getForm(), $data);
}
);
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefined(['form_area']);
}
public function getBlockPrefix(): string
{
return 'emailmarketing_mailchimp';
}
}

View File

@@ -0,0 +1,169 @@
<?php
namespace MauticPlugin\MauticEmailMarketingBundle\Integration;
use MauticPlugin\MauticEmailMarketingBundle\Form\Type\ConstantContactType;
class ConstantContactIntegration extends EmailAbstractIntegration
{
public function getName(): string
{
return 'ConstantContact';
}
public function getDisplayName(): string
{
return 'Constant Contact';
}
public function getAuthenticationType(): string
{
return 'oauth2';
}
/**
* Get the URL required to obtain an oauth2 access token.
*/
public function getAccessTokenUrl(): string
{
return 'https://oauth2.constantcontact.com/oauth2/oauth/token';
}
/**
* Get the authentication/login URL for oauth2 access.
*/
public function getAuthenticationUrl(): string
{
return 'https://oauth2.constantcontact.com/oauth2/oauth/siteowner/authorize';
}
/**
* Retrieves and stores tokens returned from oAuthLogin.
*
* @param array $settings
* @param array $parameters
*
* @return bool|string false if no error; otherwise the error string
*/
public function authCallback($settings = [], $parameters = [])
{
// Constanct Contact doesn't like POST
$settings['method'] = 'GET';
return parent::authCallback($settings, $parameters);
}
/**
* @return mixed[]
*/
public function getAvailableLeadFields($settings = []): array
{
if (!$this->isAuthorized()) {
return [];
}
$fields = [
'email',
'prefix_name',
'first_name',
'last_name',
'company_name',
'job_title',
'address_line1',
'address_line2',
'address_city',
'address_state',
'address_country_code',
'address_postal_code',
'cell_phone',
'fax',
'work_phone',
'home_phone',
];
$leadFields = [];
foreach ($fields as $f) {
$leadFields[$f] = [
'label' => $this->translator->trans('mautic.constantcontact.field.'.$f),
'type' => 'string',
'required' => ('email' == $f) ? true : false,
];
}
$c = 1;
while ($c <= 15) {
$leadFields['customfield_'.$c] = [
'label' => $this->translator->trans('mautic.constantcontact.customfield.'.$f),
'type' => 'string',
'required' => false,
];
++$c;
}
return $leadFields;
}
public function pushLead($lead, $config = []): bool
{
$config = $this->mergeConfigToFeatureSettings($config);
$mappedData = $this->populateLeadData($lead, $config);
if (empty($mappedData)) {
return false;
} elseif (empty($mappedData['email'])) {
return false;
} elseif (!isset($config['list_settings'])) {
return false;
}
try {
if ($this->isAuthorized()) {
$email = $mappedData['email'];
unset($mappedData['email']);
$addresses = [];
$customfields = [];
foreach ($mappedData as $k => $v) {
if (str_starts_with($v, 'address_')) {
$addresses[str_replace('address_', '', $k)] = $v;
unset($mappedData[$k]);
} elseif (str_starts_with($v, 'customfield_')) {
$key = str_replace('customfield_', 'CustomField', $k);
$customfields[] = [
'name' => $key,
'value' => $v,
];
unset($mappedData[$k]);
}
}
if (!empty($addresses)) {
$addresses['address_type'] = 'PERSONAL';
$mappedData['addresses'] = $addresses;
}
if (!empty($customfields)) {
$mappedData['custom_fields'] = $customfields;
}
$options = [];
$options['action_by'] = (!empty($config['list_settings']['sendWelcome'])) ? 'ACTION_BY_VISITOR' : 'ACTION_BY_OWNER';
$listId = $config['list_settings']['list'];
$this->getApiHelper()->subscribeLead($email, $listId, $mappedData, $options);
return true;
}
} catch (\Exception $e) {
$this->logIntegrationError($e);
}
return false;
}
public function getFormType(): string
{
return ConstantContactType::class;
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace MauticPlugin\MauticEmailMarketingBundle\Integration;
use Mautic\PluginBundle\Integration\AbstractIntegration;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilder;
abstract class EmailAbstractIntegration extends AbstractIntegration
{
protected $pushContactLink = false;
/**
* @return array
*/
public function getSupportedFeatures()
{
return ['push_lead'];
}
/**
* @param FormBuilder|Form $builder
*/
public function appendToForm(&$builder, $data, $formArea): void
{
if ('features' == $formArea || 'integration' == $formArea) {
if ($this->isAuthorized()) {
$formType = $this->getFormType();
if ($formType) {
if ('integration' == $formArea && isset($data['leadFields']) && empty($data['list_settings']['leadFields'])) {
$data['list_settings']['leadFields'] = $data['leadFields'];
}
$builder->add('list_settings', $formType, [
'label' => false,
'form_area' => $formArea,
'data' => $data['list_settings'] ?? [],
]);
}
}
}
}
/**
* @return string
*/
public function getFormTheme()
{
return '@MauticEmailMarketing/FormTheme/EmailMarketing/layout.html.twig';
}
/**
* Returns form type.
*
* @return string|null
*/
abstract public function getFormType();
/**
* Get the API helper.
*
* @return object
*/
public function getApiHelper()
{
static $helper;
if (empty($helper)) {
$class = '\\MauticPlugin\\MauticEmailMarketingBundle\\Api\\'.$this->getName().'Api';
$helper = new $class($this);
}
return $helper;
}
/**
* Merges a config from integration_list with feature settings.
*
* @param array $config
*
* @return array|mixed
*/
public function mergeConfigToFeatureSettings($config = [])
{
$featureSettings = $this->settings->getFeatureSettings();
if (isset($config['config']['list_settings']['leadFields'])) {
$config['config']['leadFields'] = $this->formatMatchedFields($config['config']['list_settings']['leadFields']);
unset($config['config']['list_settings']['leadFields']);
}
if (empty($config['integration']) || (!empty($config['integration']) && $config['integration'] == $this->getName())) {
$featureSettings = array_merge($featureSettings, $config['config']);
}
return $featureSettings;
}
}

View File

@@ -0,0 +1,228 @@
<?php
namespace MauticPlugin\MauticEmailMarketingBundle\Integration;
use MauticPlugin\MauticEmailMarketingBundle\Form\Type\IcontactType;
class IcontactIntegration extends EmailAbstractIntegration
{
public function getName(): string
{
return 'Icontact';
}
public function getDisplayName(): string
{
return 'iContact';
}
public function getAuthenticationType(): string
{
return 'rest';
}
/**
* Get a list of keys required to make an API call. Examples are key, clientId, clientSecret.
*
* @return array<string, string>
*/
public function getRequiredKeyFields(): array
{
return [
'API-AppId' => 'mautic.icontact.keyfield.appid',
'API-Username' => 'mautic.icontact.keyfield.username',
'API-Password' => 'mautic.icontact.keyfield.password',
];
}
public function getSecretKeys(): array
{
return [
'API-Password',
];
}
public function getApiUrl(): string
{
return 'https://app.icontact.com/icp/a';
}
/**
* Get account ID and client folder ID.
*/
public function authCallback($settings = [], $parameters = [])
{
$url = $this->getApiUrl();
$response = $this->makeRequest($url, $parameters);
// Validation kind of error
if (isset($response['errors'][0])) {
return $response['errors'][0];
}
// Timeout kind of error
if (isset($response['errors']['message'])) {
return $response['errors']['message'];
}
$keys = [];
if (!empty($response['accounts'])) {
$keys['accountId'] = $response['accounts'][0]['accountId'];
$url .= '/'.$keys['accountId'].'/c';
$response = $this->makeRequest($url, $parameters);
if (!empty($response['clientfolders'])) {
$keys['clientFolderId'] = $response['clientfolders'][0]['clientFolderId'];
return $this->extractAuthKeys($keys, 'clientFolderId');
}
}
return false;
}
/**
* @param array $parameters
* @param string $method
* @param array $settings
*
* @return mixed|string
*/
public function makeRequest($url, $parameters = [], $method = 'GET', $settings = [])
{
$settings['headers'] = [
'Except:',
'Accept: application/json',
'Content-Type: application/json',
'Api-Version: 2.2',
'Api-AppId: '.$this->keys['API-AppId'],
'Api-Username: '.$this->keys['API-Username'],
'API-Password: '.$this->keys['API-Password'],
];
return parent::makeRequest($url, $parameters, $method, $settings);
}
public function isAuthorized(): bool
{
$keys = $this->getRequiredKeyFields();
foreach ($keys as $k => $l) {
if (empty($this->keys[$k])) {
return false;
}
}
if (empty($this->keys['accountId']) || empty($this->keys['clientFolderId'])) {
return false;
}
return true;
}
/**
* @return mixed[]
*/
public function getAvailableLeadFields($settings = []): array
{
if (!$this->isAuthorized()) {
return [];
}
static $leadFields = [];
if (empty($leadFields)) {
$fields = [
'email',
'prefix',
'firstName',
'lastName',
'suffix',
'street',
'street2',
'city',
'state',
'postalCode',
'phone',
'fax',
'business',
];
$leadFields = [];
foreach ($fields as $f) {
$leadFields[$f] = [
'label' => $this->translator->trans('mautic.icontact.field.'.$f),
'type' => 'string',
'required' => ('email' == $f) ? true : false,
];
}
$customfields = $this->getApiHelper()->getCustomFields();
if (!empty($customfields['customfields'])) {
foreach ($customfields['customfields'] as $field) {
$leadFields['cf_'.$field['customFieldId']] = [
'label' => $field['publicName'],
'type' => 'string',
'required' => false,
];
}
}
}
return $leadFields;
}
/**
* @param \Mautic\LeadBundle\Entity\Lead $lead
* @param array $config
*/
public function pushLead($lead, $config = []): bool
{
$config = $this->mergeConfigToFeatureSettings($config);
$mappedData = $this->populateLeadData($lead, $config);
if (empty($mappedData)) {
return false;
} elseif (empty($mappedData['email'])) {
return false;
} elseif (!isset($config['list_settings'])) {
return false;
}
try {
if ($this->isAuthorized()) {
$customfields = [];
foreach ($mappedData as $k => &$v) {
if (str_starts_with($k, 'cf_')) {
$customfields[str_replace('cf_', '', $k)] = (string) $v;
unset($mappedData[$k]);
} else {
$v = (string) $v;
}
}
$listId = $config['list_settings']['list'];
if (!empty($customfields)) {
$mappedData += $customfields;
}
$this->getApiHelper()->subscribeLead($listId, $mappedData);
return true;
}
} catch (\Exception $e) {
$this->logIntegrationError($e);
}
return false;
}
public function getFormType(): string
{
return IcontactType::class;
}
}

View File

@@ -0,0 +1,175 @@
<?php
namespace MauticPlugin\MauticEmailMarketingBundle\Integration;
use MauticPlugin\MauticEmailMarketingBundle\Form\Type\MailchimpType;
class MailchimpIntegration extends EmailAbstractIntegration
{
public function getName(): string
{
return 'Mailchimp';
}
public function getDisplayName(): string
{
return 'MailChimp';
}
public function getAuthenticationType(): string
{
return (empty($this->keys['client_id'])) ? 'basic' : 'oauth2';
}
/**
* Get the URL required to obtain an oauth2 access token.
*/
public function getAccessTokenUrl(): string
{
return 'https://login.mailchimp.com/oauth2/token';
}
/**
* Get the authentication/login URL for oauth2 access.
*/
public function getAuthenticationUrl(): string
{
return 'https://login.mailchimp.com/oauth2/authorize';
}
public function getRequiredKeyFields(): array
{
return (empty($this->keys['client_id'])) ?
[
'username' => 'mautic.integration.keyfield.username',
'password' => 'mautic.integration.keyfield.api',
] :
[
'client_id' => 'mautic.integration.keyfield.clientid',
'client_secret' => 'mautic.integration.keyfield.clientsecret',
];
}
/**
* @param array $settings
* @param array $parameters
*
* @return bool|string
*/
public function authCallback($settings = [], $parameters = [])
{
$error = parent::authCallback($settings, $parameters);
if (empty($error)) {
// Now post to the metadata URL
$data = $this->makeRequest('https://login.mailchimp.com/oauth2/metadata');
return $this->extractAuthKeys($data, 'dc');
} else {
return $error;
}
}
/**
* @param array $settings
*
* @return mixed[]
*/
public function getAvailableLeadFields($settings = []): array
{
if (isset($settings['list'])) {
// Ajax update
$listId = $settings['list'];
} elseif (!empty($settings['feature_settings']['list_settings']['list'])) {
// Form load
$listId = $settings['feature_settings']['list_settings']['list'];
} elseif (!empty($settings['list_settings']['list'])) {
// Push action
$listId = $settings['list_settings']['list'];
}
if (!empty($listId)) {
$settings['cache_suffix'] = $cacheSuffix = '.'.$listId;
if ($fields = parent::getAvailableLeadFields($settings)) {
return $fields;
}
$fields = $this->getApiHelper()->getCustomFields($listId);
if (!empty($fields['merge_fields']) && count($fields['merge_fields'])) {
foreach ($fields['merge_fields'] as $field) {
$leadFields[$field['tag']] = [
'label' => $field['name'],
'type' => 'string',
'required' => $field['required'],
];
}
}
$leadFields['EMAIL'] = [
'label' => 'Email',
'type' => 'string',
'required' => true,
];
$this->cache->set('leadFields'.$cacheSuffix, $leadFields);
return $leadFields;
}
return [];
}
/**
* @param array $config
*/
public function pushLead($lead, $config = []): bool
{
$config = $this->mergeConfigToFeatureSettings($config);
$mappedData = $this->populateLeadData($lead, $config);
if (empty($mappedData)) {
return false;
} elseif (empty($mappedData['EMAIL'])) {
return false;
} elseif (!isset($config['list_settings'])) {
return false;
}
try {
if ($this->isAuthorized()) {
$email = $mappedData['EMAIL'];
unset($mappedData['EMAIL']);
$options = [];
$options['status'] = $config['list_settings']['doubleOptin'] ? 'pending' : 'subscribed';
$options['send_welcome'] = $config['list_settings']['sendWelcome'];
$listId = $config['list_settings']['list'];
$this->getApiHelper()->subscribeLead($email, $listId, $mappedData, $options);
return true;
}
} catch (\Exception $e) {
$this->logIntegrationError($e);
}
return false;
}
/**
* @return array<string, mixed>
*/
public function getFormSettings(): array
{
$settings = parent::getFormSettings();
$settings['dynamic_contact_fields'] = true;
return $settings;
}
public function getFormType(): string
{
return MailchimpType::class;
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace MauticPlugin\MauticEmailMarketingBundle;
use Mautic\PluginBundle\Bundle\PluginBundleBase;
class MauticEmailMarketingBundle extends PluginBundleBase
{
}

View File

@@ -0,0 +1,5 @@
# Mautic bundle for Email Marketing plugin
## This plugin is managed centrally in https://github.com/mautic/mautic/blob/head/plugins/MauticEmailMarketingBundle and this is a read-only mirror repository.
**📣 Please make PRs and issues against Mautic Core, not here!**

View File

@@ -0,0 +1,30 @@
{% block emailmarketing_constantcontact_row %}
<div class="row">
<div class="col-md-8">
{{ form_row(form.list) }}
</div>
</div>
{{ form_row(form.sendWelcome) }}
{% endblock %}
{% block emailmarketing_icontact_row %}
<div class="row">
<div class="col-md-8">
{{ form_row(form.list) }}
</div>
</div>
{% endblock %}
{% block emailmarketing_mailchimp_row %}
<div class="alert alert-info">
{{ 'mautic.emailmarketing.list.update'|trans }}
</div>
<div class="row">
<div class="col-md-8">
{{ form_row(form.list) }}
</div>
</div>
{{ form_row(form.doubleOptin) }}
{{ form_row(form.sendWelcome) }}
{% endblock %}

View File

@@ -0,0 +1,52 @@
mautic.constantcontact.customfield1="Custom field 1"
mautic.constantcontact.customfield2="Custom field 2"
mautic.constantcontact.customfield3="Custom field 3"
mautic.constantcontact.customfield4="Custom field 4"
mautic.constantcontact.customfield5="Custom field 5"
mautic.constantcontact.customfield6="Custom field 6"
mautic.constantcontact.customfield7="Custom field 7"
mautic.constantcontact.customfield8="Custom field 8"
mautic.constantcontact.customfield9="Custom field 9"
mautic.constantcontact.customfield10="Custom field 10"
mautic.constantcontact.customfield11="Custom field 11"
mautic.constantcontact.customfield12="Custom field 12"
mautic.constantcontact.customfield13="Custom field 13"
mautic.constantcontact.customfield14="Custom field 14"
mautic.constantcontact.customfield15="Custom field 15"
mautic.constantcontact.field.address_city="City"
mautic.constantcontact.field.address_country_code="Country code (2 characters)"
mautic.constantcontact.field.address_line1="Address line 1"
mautic.constantcontact.field.address_line2="Address line 2"
mautic.constantcontact.field.address_state="State"
mautic.constantcontact.field.address_postal_code="Postal code"
mautic.constantcontact.field.cell_phone="Cell phone"
mautic.constantcontact.field.company_name="Company name"
mautic.constantcontact.field.email="Email"
mautic.constantcontact.field.fax="Fax"
mautic.constantcontact.field.first_name="First name"
mautic.constantcontact.field.home_phone="Home phone"
mautic.constantcontact.field.job_title="Job title"
mautic.constantcontact.field.last_name="Last name"
mautic.constantcontact.field.prefix_name="Prefix name"
mautic.constantcontact.field.work_phone="Work phone"
mautic.icontact.field.business="Business phone"
mautic.icontact.field.city="City"
mautic.icontact.field.email="Email"
mautic.icontact.field.fax="Fax"
mautic.icontact.field.firstName="First name"
mautic.icontact.field.lastName="Last name"
mautic.icontact.field.phone="Phone"
mautic.icontact.field.postalCode="Postal code"
mautic.icontact.field.prefix="Prefix"
mautic.icontact.field.street="Address line 1"
mautic.icontact.field.street2="Address line 2"
mautic.icontact.field.suffix="Suffix"
mautic.icontact.field.state="State"
mautic.icontact.keyfield.appid="App ID"
mautic.icontact.keyfield.username="App username"
mautic.icontact.keyfield.password="App API password"
mautic.emailmarketing.list="List"
mautic.emailmarketing.list.tooltip="Choose the list the lead should be added to."
mautic.emailmarketing.list.update="The Lead Field Mapping tab will appear after selecting a list and will update after changing the selected list."
mautic.emailmarketing.send_welcome="Send welcome email"
mautic.mailchimp.double_optin="Enable double opt in"

View File

@@ -0,0 +1,17 @@
{
"name": "mautic/plugin-emailmarketing",
"description": "Email Marketing Plugin",
"type": "mautic-plugin",
"keywords": [
"mautic",
"plugin",
"integration"
],
"extra": {
"install-directory-name": "MauticEmailMarketingBundle"
},
"minimum-stability": "dev",
"require": {
"mautic/core-lib": "^7.0"
}
}