Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/* Social Media */
|
||||
.symbol-hashtag:before {
|
||||
content: '#';
|
||||
}
|
||||
|
||||
.shuffle-item.integration {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.integration-disabled img {
|
||||
filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#grayscale"); /* Firefox 10+ */
|
||||
filter: gray; /* IE6-9 */
|
||||
-webkit-filter: grayscale(100%); /* Chrome 19+ & Safari 6+ */
|
||||
-webkit-transition: all .6s ease; /* Fade to color for Chrome and Safari */
|
||||
-webkit-backface-visibility: hidden; /* Fix for transition flickering */
|
||||
}
|
||||
|
||||
.integration-disabled img:hover {
|
||||
filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'grayscale\'><feColorMatrix type=\'matrix\' values=\'1 0 0 0 0, 0 1 0 0 0, 0 0 1 0 0, 0 0 0 1 0\'/></filter></svg>#grayscale");
|
||||
-webkit-filter: grayscale(0%);
|
||||
}
|
||||
.field-selector {
|
||||
width: 500px;
|
||||
}
|
||||
.col-centered{
|
||||
margin: 0 auto;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.placeholder
|
||||
{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.placeholder::after
|
||||
{
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: 19px;
|
||||
content: attr(data-placeholder);
|
||||
pointer-events: none;
|
||||
opacity: 0.3;
|
||||
font-size: 9px
|
||||
}
|
||||
|
||||
.integration-fields{
|
||||
font-size: 12px;
|
||||
padding-left: 7px;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,322 @@
|
||||
/* PluginBundle */
|
||||
Mautic.matchedFields = function (index, object, integration) {
|
||||
var compoundMauticFields = ['mauticContactId','mauticContactTimelineLink'];
|
||||
|
||||
if (mQuery('#integration_details_featureSettings_updateDncByDate_0').is(':checked')) {
|
||||
compoundMauticFields.push('mauticContactIsContactableByEmail');
|
||||
}
|
||||
var integrationField = mQuery('#integration_details_featureSettings_'+object+'Fields_i_' + index).attr('data-value');
|
||||
var mauticField = mQuery('#integration_details_featureSettings_'+object+'Fields_m_' + index + ' option:selected').val();
|
||||
|
||||
if(mQuery('.btn-arrow' + index).parent().attr('data-force-direction') != 1) {
|
||||
if (mQuery.inArray(mauticField, compoundMauticFields) >= 0) {
|
||||
mQuery('.btn-arrow' + index).removeClass('active');
|
||||
mQuery('#integration_details_featureSettings_' + object + 'Fields_update_mautic' + index + '_0').attr('checked', 'checked');
|
||||
mQuery('input[name="integration_details[featureSettings][' + object + 'Fields][update_mautic' + index + ']"]').prop('disabled', true).trigger("chosen:updated");
|
||||
mQuery('.btn-arrow' + index).addClass('disabled');
|
||||
}
|
||||
else {
|
||||
mQuery('input[name="integration_details[featureSettings][' + object + 'Fields][update_mautic' + index + ']"]').prop('disabled', false).trigger("chosen:updated");
|
||||
mQuery('.btn-arrow' + index).removeClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
if (object == 'lead') {
|
||||
var updateMauticField = mQuery('input[name="integration_details[featureSettings]['+object+'Fields][update_mautic' + index + ']"]:checked').val();
|
||||
} else {
|
||||
var updateMauticField = mQuery('input[name="integration_details[featureSettings]['+object+'Fields][update_mautic_company' + index + ']"]:checked').val();
|
||||
}
|
||||
Mautic.ajaxActionRequest('plugin:matchFields', {object: object, integration: integration, integrationField : integrationField, mauticField: mauticField, updateMautic : updateMauticField}, function(response) {
|
||||
var theMessage = (response.success) ? '<i class="ri-check-line-circle text-success"></i>' : '';
|
||||
mQuery('#matched-' + index + "-" + object).html(theMessage);
|
||||
});
|
||||
};
|
||||
Mautic.initiateIntegrationAuthorization = function() {
|
||||
mQuery('#integration_details_in_auth').val(1);
|
||||
|
||||
Mautic.postForm(mQuery('form[name="integration_details"]'), 'loadIntegrationAuthWindow');
|
||||
};
|
||||
|
||||
Mautic.loadIntegrationAuthWindow = function(response) {
|
||||
if (response.newContent) {
|
||||
Mautic.processModalContent(response, '#IntegrationEditModal');
|
||||
} else {
|
||||
Mautic.stopPageLoadingBar();
|
||||
Mautic.stopIconSpinPostEvent();
|
||||
mQuery('#integration_details_in_auth').val(0);
|
||||
|
||||
if (response.authUrl) {
|
||||
var generator = window.open(response.authUrl, 'integrationauth', 'height=500,width=500');
|
||||
|
||||
if (!generator || generator.closed || typeof generator.closed == 'undefined') {
|
||||
alert(mauticLang.popupBlockerMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Mautic.refreshIntegrationForm = function() {
|
||||
var opener = window.opener;
|
||||
if(opener) {
|
||||
var form = opener.mQuery('form[name="integration_details"]');
|
||||
if (form.length) {
|
||||
var action = form.attr('action');
|
||||
if (action) {
|
||||
opener.Mautic.startModalLoadingBar('#IntegrationEditModal');
|
||||
opener.Mautic.loadAjaxModal('#IntegrationEditModal', action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.close()
|
||||
};
|
||||
|
||||
Mautic.integrationOnLoad = function(container, response) {
|
||||
if (response && response.name) {
|
||||
var integration = '.integration-' + response.name;
|
||||
if (response.enabled) {
|
||||
mQuery(integration).removeClass('integration-disabled');
|
||||
} else {
|
||||
mQuery(integration).addClass('integration-disabled');
|
||||
}
|
||||
} else {
|
||||
Mautic.filterIntegrations();
|
||||
}
|
||||
mQuery('[data-toggle="tooltip"]').tooltip();
|
||||
};
|
||||
|
||||
Mautic.integrationConfigOnLoad = function(container) {
|
||||
if (mQuery('.fields-container select.integration-field').length) {
|
||||
var selects = mQuery('.fields-container select.integration-field');
|
||||
selects.on('change', function() {
|
||||
var select = mQuery(this),
|
||||
newValue = select.val(),
|
||||
previousValue = select.attr('data-value');
|
||||
select.attr('data-value', newValue);
|
||||
|
||||
var groupSelects = mQuery(this).closest('.fields-container').find('select.integration-field').not(select);
|
||||
|
||||
// Enable old value
|
||||
if (previousValue) {
|
||||
mQuery('option[value="' + previousValue + '"]', groupSelects).each(function() {
|
||||
if (!mQuery(this).closest('select').prop('disabled')) {
|
||||
mQuery(this).prop('disabled', false);
|
||||
mQuery(this).removeAttr('disabled');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (newValue) {
|
||||
mQuery('option[value="' + newValue + '"]', groupSelects).each(function() {
|
||||
if (!mQuery(this).closest('select').prop('disabled')) {
|
||||
mQuery(this).prop('disabled', true);
|
||||
mQuery(this).attr('disabled', 'disabled');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
groupSelects.each(function() {
|
||||
mQuery(this).trigger('chosen:updated');
|
||||
});
|
||||
});
|
||||
|
||||
selects.each(function() {
|
||||
if (!mQuery(this).closest('.field-container').hasClass('hide')) {
|
||||
mQuery(this).trigger('change');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Mautic.filterIntegrations = function(update) {
|
||||
var filter = mQuery('#integrationFilter').val();
|
||||
|
||||
if (update) {
|
||||
mQuery.ajax({
|
||||
url: mauticAjaxUrl,
|
||||
type: "POST",
|
||||
data: "action=plugin:setIntegrationFilter&plugin=" + filter
|
||||
});
|
||||
}
|
||||
|
||||
//activate shuffles
|
||||
if (mQuery('.native-integrations').length) {
|
||||
//give a slight delay in order for images to load so that shuffle starts out with correct dimensions
|
||||
setTimeout(function () {
|
||||
var Shuffle = window.Shuffle,
|
||||
element = document.querySelector('.native-integrations'),
|
||||
shuffleOptions = {
|
||||
itemSelector: '.shuffle-item'
|
||||
};
|
||||
|
||||
// Using global variable to make it available outside of the scope of this function
|
||||
window.nativeIntegrationsShuffleInstance = new Shuffle(element, shuffleOptions);
|
||||
|
||||
window.nativeIntegrationsShuffleInstance.filter(function($el) {
|
||||
if (filter) {
|
||||
return mQuery($el).hasClass('plugin' + filter);
|
||||
} else {
|
||||
// Shuffle.js has a bug. It hides the first item when we reset the filter.
|
||||
// This fixes it.
|
||||
mQuery(shuffleOptions.itemSelector).first().css('transform', '');
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Update shuffle on sidebar minimize/maximize
|
||||
mQuery("html")
|
||||
.on("fa.sidebar.minimize", function() {
|
||||
setTimeout(function() {
|
||||
window.nativeIntegrationsShuffleInstance.update();
|
||||
}, 1000);
|
||||
})
|
||||
.on("fa.sidebar.maximize", function() {
|
||||
setTimeout(function() {
|
||||
window.nativeIntegrationsShuffleInstance.update();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// This delay is needed so that the tab has time to render and the sizes are correctly calculated
|
||||
mQuery('#plugin-nav-tabs a').click(function () {
|
||||
setTimeout(function() {
|
||||
window.nativeIntegrationsShuffleInstance.update();
|
||||
}, 500);
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
Mautic.getIntegrationLeadFields = function (integration, el, settings) {
|
||||
|
||||
if (typeof settings == 'undefined') {
|
||||
settings = {};
|
||||
}
|
||||
settings.integration = integration;
|
||||
settings.object = 'lead';
|
||||
|
||||
Mautic.getIntegrationFields(settings, 1, el);
|
||||
};
|
||||
|
||||
Mautic.getIntegrationCompanyFields = function (integration, el, settings) {
|
||||
if (typeof settings == 'undefined') {
|
||||
settings = {};
|
||||
}
|
||||
settings.integration = integration;
|
||||
settings.object = 'company';
|
||||
|
||||
Mautic.getIntegrationFields(settings, 1, el);
|
||||
};
|
||||
|
||||
Mautic.getIntegrationFields = function(settings, page, el) {
|
||||
var object = settings.object ? settings.object : 'lead';
|
||||
var fieldsTab = ('lead' === object) ? '#fields-tab' : '#'+object+'-fields-container';
|
||||
|
||||
if (el && mQuery(el).is('input')) {
|
||||
Mautic.activateLabelLoadingIndicator(mQuery(el).attr('id'));
|
||||
|
||||
var namePrefix = mQuery(el).attr('name').split('[')[0];
|
||||
if ('integration_details' !== namePrefix) {
|
||||
var nameParts = mQuery(el).attr('name').match(/\[.*?\]+/g);
|
||||
nameParts = nameParts.slice(0, -1);
|
||||
settings.prefix = namePrefix + nameParts.join('') + "[" + object + "Fields]";
|
||||
}
|
||||
}
|
||||
var fieldsContainer = '#'+object+'FieldsContainer';
|
||||
|
||||
var inModal = mQuery(fieldsContainer).closest('.modal');
|
||||
if (inModal) {
|
||||
var modalId = '#'+mQuery(fieldsContainer).closest('.modal').attr('id');
|
||||
Mautic.startModalLoadingBar(modalId);
|
||||
}
|
||||
|
||||
Mautic.ajaxActionRequest('plugin:getIntegrationFields',
|
||||
{
|
||||
page: page,
|
||||
integration: (settings.integration) ? settings.integration : null,
|
||||
settings: settings
|
||||
},
|
||||
function(response) {
|
||||
if (response.success) {
|
||||
mQuery(fieldsContainer).replaceWith(response.html);
|
||||
Mautic.onPageLoad(fieldsContainer);
|
||||
Mautic.integrationConfigOnLoad(fieldsContainer);
|
||||
if (mQuery(fieldsTab).length) {
|
||||
mQuery(fieldsTab).removeClass('hide');
|
||||
}
|
||||
} else {
|
||||
if (mQuery(fieldsTab).length) {
|
||||
mQuery(fieldsTab).addClass('hide');
|
||||
}
|
||||
}
|
||||
|
||||
if (el) {
|
||||
Mautic.removeLabelLoadingIndicator();
|
||||
}
|
||||
|
||||
if (inModal) {
|
||||
Mautic.stopModalLoadingBar(modalId);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Mautic.getIntegrationConfig = function (el, settings) {
|
||||
Mautic.activateLabelLoadingIndicator(mQuery(el).attr('id'));
|
||||
|
||||
if (typeof settings == 'undefined') {
|
||||
settings = {};
|
||||
}
|
||||
|
||||
settings.name = mQuery(el).attr('name');
|
||||
var data = {integration: mQuery(el).val(), settings: settings};
|
||||
mQuery('.integration-campaigns-status').html('');
|
||||
mQuery('.integration-config-container').html('');
|
||||
|
||||
Mautic.ajaxActionRequest('plugin:getIntegrationConfig', data,
|
||||
function (response) {
|
||||
if (response.success) {
|
||||
mQuery('.integration-config-container').html(response.html);
|
||||
Mautic.onPageLoad('.integration-config-container', response);
|
||||
}
|
||||
|
||||
Mautic.integrationConfigOnLoad('.integration-config-container');
|
||||
Mautic.removeLabelLoadingIndicator();
|
||||
},
|
||||
false,
|
||||
false,
|
||||
"GET"
|
||||
);
|
||||
|
||||
|
||||
};
|
||||
|
||||
Mautic.getIntegrationCampaignStatus = function (el, settings) {
|
||||
Mautic.activateLabelLoadingIndicator(mQuery(el).attr('id'));
|
||||
if (typeof settings == 'undefined') {
|
||||
settings = {};
|
||||
}
|
||||
|
||||
// Extract the name and ID prefixes
|
||||
var prefix = mQuery(el).attr('name').split("[")[0];
|
||||
|
||||
settings.name = mQuery('#'+prefix+'_properties_integration').attr('name');
|
||||
var data = {integration:mQuery('#'+prefix+'_properties_integration').val(),campaign: mQuery(el).val(), settings: settings};
|
||||
|
||||
mQuery('.integration-campaigns-status').html('');
|
||||
mQuery('.integration-campaigns-status').removeClass('hide');
|
||||
Mautic.ajaxActionRequest('plugin:getIntegrationCampaignStatus', data,
|
||||
function (response) {
|
||||
|
||||
if (response.success) {
|
||||
mQuery('.integration-campaigns-status').append(response.html);
|
||||
Mautic.onPageLoad('.integration-campaigns-status', response);
|
||||
}
|
||||
|
||||
Mautic.integrationConfigOnLoad('.integration-campaigns-status');
|
||||
Mautic.removeLabelLoadingIndicator();
|
||||
},
|
||||
false,
|
||||
false,
|
||||
"GET"
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Bundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
/**
|
||||
* Base Bundle class which should be extended by addon bundles.
|
||||
*/
|
||||
abstract class PluginBundleBase extends Bundle
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Bundle;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Mautic\IntegrationsBundle\Migration\Engine;
|
||||
use Mautic\PluginBundle\Entity\Plugin;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
class PluginDatabase
|
||||
{
|
||||
private readonly string $mauticDbPrefix;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly Connection $connection,
|
||||
#[Autowire(env: 'MAUTIC_TABLE_PREFIX')]
|
||||
?string $mauticDbPrefix,
|
||||
) {
|
||||
$this->mauticDbPrefix = $mauticDbPrefix ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Install plugin schema based on Doctrine metadata.
|
||||
*
|
||||
* @param array<class-string, ClassMetadata> $metadata
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function installPluginSchema(array $metadata, ?bool $installedSchema = null): void
|
||||
{
|
||||
if (null !== $installedSchema) {
|
||||
// Schema already exists, so no need to proceed
|
||||
return;
|
||||
}
|
||||
|
||||
$schemaTool = new SchemaTool($this->em);
|
||||
$installQueries = $schemaTool->getCreateSchemaSql(array_values($metadata));
|
||||
$connection = $this->connection;
|
||||
|
||||
foreach ($installQueries as $q) {
|
||||
// Check if the query is a DDL statement
|
||||
if (self::isDDLStatement($q)) {
|
||||
// Execute DDL statements outside of a transaction
|
||||
$connection->executeStatement($q);
|
||||
} else {
|
||||
// For non-DDL statements, use transactions
|
||||
try {
|
||||
$connection->beginTransaction();
|
||||
$connection->executeStatement($q);
|
||||
$connection->commit();
|
||||
} catch (\Exception $e) {
|
||||
// Rollback only for non-DDL statements
|
||||
if ($connection->isTransactionActive()) {
|
||||
$connection->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function onPluginUpdate(Plugin $plugin): void
|
||||
{
|
||||
$migrationEngine = new Engine(
|
||||
$this->em,
|
||||
$this->mauticDbPrefix,
|
||||
__DIR__.'/../../../../plugins/'.$plugin->getBundle(),
|
||||
$plugin->getBundle()
|
||||
);
|
||||
|
||||
$migrationEngine->up();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, ClassMetadata> $metadata
|
||||
*/
|
||||
public function dropPluginSchema(array $metadata): void
|
||||
{
|
||||
$db = $this->em->getConnection();
|
||||
$schemaTool = new SchemaTool($this->em);
|
||||
$dropQueries = $schemaTool->getDropSchemaSQL($metadata);
|
||||
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
foreach ($dropQueries as $q) {
|
||||
$db->executeStatement($q);
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
} catch (\Exception $e) {
|
||||
$db->rollback();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private static function isDDLStatement(string $query): bool|int
|
||||
{
|
||||
return preg_match('/^(CREATE|ALTER|DROP|RENAME|TRUNCATE|COMMENT)\s/i', $query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Command;
|
||||
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Mautic\PluginBundle\Integration\UnifiedIntegrationInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'mautic:integration:fetchleads',
|
||||
description: 'Fetch leads from integration.',
|
||||
aliases: [
|
||||
'mautic:integration:synccontacts',
|
||||
]
|
||||
)]
|
||||
class FetchLeadsCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private TranslatorInterface $translator,
|
||||
private IntegrationHelper $integrationHelper,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->addOption(
|
||||
'--integration',
|
||||
'-i',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Fetch leads from integration. Integration must be enabled and authorised.',
|
||||
null
|
||||
)
|
||||
->addOption('--start-date', '-d', InputOption::VALUE_REQUIRED, 'Set start date for updated values.')
|
||||
->addOption(
|
||||
'--end-date',
|
||||
'-t',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Set end date for updated values.'
|
||||
)
|
||||
->addOption(
|
||||
'--fetch-all',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Get all CRM contacts whatever the date is. Should be used at instance initialization only'
|
||||
)
|
||||
->addOption(
|
||||
'--time-interval',
|
||||
'-a',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Send time interval to check updates on Salesforce, it should be a correct php formatted time interval in the past eg:(10 minutes)'
|
||||
)
|
||||
->addOption(
|
||||
'--limit',
|
||||
'-l',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Number of records to process when syncing objects',
|
||||
100
|
||||
)
|
||||
->addOption('--force', '-f', InputOption::VALUE_NONE, 'Force execution even if another process is assumed running.');
|
||||
|
||||
parent::configure();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$integration = $input->getOption('integration');
|
||||
$startDate = $input->getOption('start-date');
|
||||
$endDate = $input->getOption('end-date');
|
||||
$interval = $input->getOption('time-interval');
|
||||
$limit = $input->getOption('limit');
|
||||
$fetchAll = $input->getOption('fetch-all');
|
||||
$leadsExecuted = $contactsExecuted = null;
|
||||
|
||||
// @TODO Since integration is mandatory it should really be turned into an agument, but that would not be B.C.
|
||||
if (!$integration) {
|
||||
throw new \RuntimeException('An integration must be specified');
|
||||
}
|
||||
|
||||
$integrationObject = $this->integrationHelper->getIntegrationObject($integration);
|
||||
if (!$integrationObject instanceof UnifiedIntegrationInterface) {
|
||||
$availableIntegrations = array_filter($this->integrationHelper->getIntegrationObjects(),
|
||||
fn (UnifiedIntegrationInterface $availableIntegration) => $availableIntegration->isConfigured());
|
||||
throw new \RuntimeException(sprintf('The Integration "%s" is not one of the available integrations (%s)', $integration, implode(', ', array_keys($availableIntegrations))));
|
||||
}
|
||||
|
||||
if (!$interval) {
|
||||
$interval = '15 minutes';
|
||||
}
|
||||
$startDate = !$startDate ? date('c', strtotime('-'.$interval)) : date('c', strtotime($startDate));
|
||||
$endDate = !$endDate ? date('c') : date('c', strtotime($endDate));
|
||||
|
||||
if (!$endDate) {
|
||||
$output->writeln(sprintf('<info>Invalid date rage given %s -> %s</info>', $startDate, $endDate));
|
||||
|
||||
return 255;
|
||||
}
|
||||
|
||||
$integrationObject = $this->integrationHelper->getIntegrationObject($integration);
|
||||
|
||||
if (!$integrationObject->isAuthorized()) {
|
||||
$output->writeln(sprintf('<error>ERROR:</error> <info>'.$this->translator->trans('mautic.plugin.command.notauthorized').'</info>', $integration));
|
||||
|
||||
return 255;
|
||||
}
|
||||
|
||||
// Tell audit log to use integration name
|
||||
define('MAUTIC_AUDITLOG_USER', $integration);
|
||||
|
||||
$config = $integrationObject->mergeConfigToFeatureSettings();
|
||||
$supportedFeatures = $integrationObject->getIntegrationSettings()->getSupportedFeatures();
|
||||
|
||||
defined('MAUTIC_CONSOLE_VERBOSITY') or define('MAUTIC_CONSOLE_VERBOSITY', $output->getVerbosity());
|
||||
|
||||
if (!isset($config['objects'])) {
|
||||
$config['objects'] = [];
|
||||
}
|
||||
|
||||
$params['start'] = $startDate;
|
||||
$params['end'] = $endDate;
|
||||
$params['limit'] = $limit;
|
||||
$params['fetchAll'] = $fetchAll;
|
||||
$params['output'] = $output;
|
||||
|
||||
$integrationObject->setCommandParameters($params);
|
||||
|
||||
// set this constant to ensure that all contacts have the same date modified time and date synced time to prevent a pull/push loop
|
||||
define('MAUTIC_DATE_MODIFIED_OVERRIDE', time());
|
||||
|
||||
if (isset($supportedFeatures) && in_array('get_leads', $supportedFeatures)) {
|
||||
if (null !== $integrationObject && method_exists($integrationObject, 'getLeads') && isset($config['objects'])) {
|
||||
$output->writeln('<info>'.$this->translator->trans('mautic.plugin.command.fetch.leads', ['%integration%' => $integration]).'</info>');
|
||||
$output->writeln('<comment>'.$this->translator->trans('mautic.plugin.command.fetch.leads.starting').'</comment>');
|
||||
|
||||
// Handle case when integration object are named "Contacts" and "Leads"
|
||||
$leadObjectName = 'Lead';
|
||||
if (in_array('Leads', $config['objects'])) {
|
||||
$leadObjectName = 'Leads';
|
||||
}
|
||||
$contactObjectName = 'Contact';
|
||||
if (in_array(strtolower('Contacts'), array_map(fn ($i): string => strtolower($i), $config['objects']), true)) {
|
||||
$contactObjectName = 'Contacts';
|
||||
}
|
||||
|
||||
$updated = $created = $processed = 0;
|
||||
if (in_array($leadObjectName, $config['objects'])) {
|
||||
$leadList = [];
|
||||
$results = $integrationObject->getLeads($params, null, $leadsExecuted, $leadList, $leadObjectName);
|
||||
if (is_array($results)) {
|
||||
[$justUpdated, $justCreated] = $results;
|
||||
$updated += (int) $justUpdated;
|
||||
$created += (int) $justCreated;
|
||||
} else {
|
||||
$processed += (int) $results;
|
||||
}
|
||||
}
|
||||
if (in_array(strtolower($contactObjectName), array_map(fn ($i): string => strtolower($i), $config['objects']), true)) {
|
||||
$output->writeln('');
|
||||
$output->writeln('<comment>'.$this->translator->trans('mautic.plugin.command.fetch.contacts.starting').'</comment>');
|
||||
$contactList = [];
|
||||
$results = $integrationObject->getLeads($params, null, $contactsExecuted, $contactList, $contactObjectName);
|
||||
if (is_array($results)) {
|
||||
[$justUpdated, $justCreated] = $results;
|
||||
$updated += (int) $justUpdated;
|
||||
$created += (int) $justCreated;
|
||||
} else {
|
||||
$processed += (int) $results;
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln('');
|
||||
|
||||
if ($processed) {
|
||||
$output->writeln(
|
||||
'<comment>'.$this->translator->trans('mautic.plugin.command.fetch.leads.events_executed', ['%events%' => $processed])
|
||||
.'</comment>'."\n"
|
||||
);
|
||||
} else {
|
||||
$output->writeln(
|
||||
'<comment>'.$this->translator->trans(
|
||||
'mautic.plugin.command.fetch.leads.events_executed_breakout',
|
||||
['%updated%' => $updated, '%created%' => $created]
|
||||
)
|
||||
.'</comment>'."\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $integrationObject && method_exists($integrationObject, 'getCompanies') && isset($config['objects'])
|
||||
&& in_array(
|
||||
'company',
|
||||
$config['objects']
|
||||
)
|
||||
) {
|
||||
$updated = $created = $processed = 0;
|
||||
$output->writeln('<info>'.$this->translator->trans('mautic.plugin.command.fetch.companies', ['%integration%' => $integration]).'</info>');
|
||||
$output->writeln('<comment>'.$this->translator->trans('mautic.plugin.command.fetch.companies.starting').'</comment>');
|
||||
|
||||
$results = $integrationObject->getCompanies($params);
|
||||
if (is_array($results)) {
|
||||
[$justUpdated, $justCreated] = $results;
|
||||
$updated += (int) $justUpdated;
|
||||
$created += (int) $justCreated;
|
||||
} else {
|
||||
$processed += (int) $results;
|
||||
}
|
||||
$output->writeln('');
|
||||
if ($processed) {
|
||||
$output->writeln(
|
||||
'<comment>'.$this->translator->trans('mautic.plugin.command.fetch.companies.events_executed', ['%events%' => $processed])
|
||||
.'</comment>'."\n"
|
||||
);
|
||||
} else {
|
||||
$output->writeln(
|
||||
'<comment>'.$this->translator->trans(
|
||||
'mautic.plugin.command.fetch.companies.events_executed_breakout',
|
||||
['%updated%' => $updated, '%created%' => $created]
|
||||
)
|
||||
.'</comment>'."\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($supportedFeatures) && in_array('push_leads', $supportedFeatures) && method_exists($integrationObject, 'pushLeads')) {
|
||||
$output->writeln('<info>'.$this->translator->trans('mautic.plugin.command.pushing.leads', ['%integration%' => $integration]).'</info>');
|
||||
$result = $integrationObject->pushLeads($params);
|
||||
$ignored = 0;
|
||||
|
||||
if (4 === count($result)) {
|
||||
[$updated, $created, $errored, $ignored] = $result;
|
||||
} elseif (3 === count($result)) {
|
||||
[$updated, $created, $errored] = $result;
|
||||
} else {
|
||||
$errored = '?';
|
||||
[$updated, $created] = $result;
|
||||
}
|
||||
$output->writeln(
|
||||
'<comment>'.$this->translator->trans(
|
||||
'mautic.plugin.command.fetch.pushing.leads.events_executed',
|
||||
[
|
||||
'%updated%' => $updated,
|
||||
'%created%' => $created,
|
||||
'%errored%' => $errored,
|
||||
'%ignored%' => $ignored,
|
||||
]
|
||||
)
|
||||
.'</comment>'."\n"
|
||||
);
|
||||
|
||||
if (in_array('push_companies', $supportedFeatures) && method_exists($integrationObject, 'pushCompanies')) {
|
||||
$output->writeln('<info>'.$this->translator->trans('mautic.plugin.command.pushing.companies', ['%integration%' => $integration]).'</info>');
|
||||
$result = $integrationObject->pushCompanies($params);
|
||||
$ignored = 0;
|
||||
|
||||
if (4 === count($result)) {
|
||||
[$updated, $created, $errored, $ignored] = $result;
|
||||
} elseif (3 === count($result)) {
|
||||
[$updated, $created, $errored] = $result;
|
||||
} else {
|
||||
$errored = '?';
|
||||
[$updated, $created] = $result;
|
||||
}
|
||||
$output->writeln(
|
||||
'<comment>'.$this->translator->trans(
|
||||
'mautic.plugin.command.fetch.pushing.companies.events_executed',
|
||||
[
|
||||
'%updated%' => $updated,
|
||||
'%created%' => $created,
|
||||
'%errored%' => $errored,
|
||||
'%ignored%' => $ignored,
|
||||
]
|
||||
)
|
||||
.'</comment>'."\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Command;
|
||||
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'mautic:integration:pushleadactivity',
|
||||
description: 'Push lead activity to integration.',
|
||||
aliases: [
|
||||
'mautic:integration:pushactivity',
|
||||
]
|
||||
)]
|
||||
class PushLeadActivityCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private TranslatorInterface $translator,
|
||||
private IntegrationHelper $integrationHelper,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->addOption(
|
||||
'--integration',
|
||||
'-i',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Integration name. Integration must be enabled and authorised.',
|
||||
null
|
||||
)
|
||||
->addOption('--start-date', '-d', InputOption::VALUE_REQUIRED, 'Set start date for updated values.')
|
||||
->addOption(
|
||||
'--end-date',
|
||||
'-t',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Set end date for updated values.'
|
||||
)
|
||||
->addOption(
|
||||
'--time-interval',
|
||||
'-a',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Send time interval to check updates on Salesforce, it should be a correct php formatted time interval in the past eg:(-10 minutes)'
|
||||
)
|
||||
->addOption('--force', '-f', InputOption::VALUE_NONE, 'Force execution even if another process is assumed running.');
|
||||
|
||||
parent::configure();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$integration = $input->getOption('integration');
|
||||
$startDate = $input->getOption('start-date');
|
||||
$endDate = $input->getOption('end-date');
|
||||
$interval = $input->getOption('time-interval');
|
||||
|
||||
if (!$interval) {
|
||||
$interval = '15 minutes';
|
||||
}
|
||||
if (!$startDate) {
|
||||
$startDate = date('c', strtotime('-'.$interval));
|
||||
}
|
||||
|
||||
if (!$endDate) {
|
||||
$endDate = date('c');
|
||||
}
|
||||
|
||||
if ($integration) {
|
||||
$integrationObject = $this->integrationHelper->getIntegrationObject($integration);
|
||||
|
||||
if (null !== $integrationObject && method_exists($integrationObject, 'pushLeadActivity')) {
|
||||
$output->writeln('<info>'.$this->translator->trans('mautic.plugin.command.push.leads.activity', ['%integration%' => $integration]).'</info>');
|
||||
|
||||
$params['start'] = $startDate;
|
||||
$params['end'] = $endDate;
|
||||
|
||||
$processed = intval($integrationObject->pushLeadActivity($params));
|
||||
|
||||
$output->writeln('<comment>'.$this->translator->trans('mautic.plugin.command.push.leads.events_executed', ['%events%' => $processed]).'</comment>'."\n");
|
||||
}
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Command;
|
||||
|
||||
use Mautic\PluginBundle\Facade\ReloadFacade;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'mautic:plugins:reload',
|
||||
description: 'Installs, updates, enable and/or disable plugins.',
|
||||
aliases: [
|
||||
'mautic:plugins:install',
|
||||
'mautic:plugins:update',
|
||||
]
|
||||
)]
|
||||
class ReloadCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private ReloadFacade $reloadFacade,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeLn($this->reloadFacade->reloadPlugins());
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'routes' => [
|
||||
'main' => [
|
||||
'mautic_integration_auth_callback_secure' => [
|
||||
'path' => '/plugins/integrations/authcallback/{integration}',
|
||||
'controller' => 'Mautic\PluginBundle\Controller\AuthController::authCallbackAction',
|
||||
],
|
||||
'mautic_integration_auth_postauth_secure' => [
|
||||
'path' => '/plugins/integrations/authstatus/{integration}',
|
||||
'controller' => 'Mautic\PluginBundle\Controller\AuthController::authStatusAction',
|
||||
],
|
||||
'mautic_plugin_index' => [
|
||||
'path' => '/plugins',
|
||||
'controller' => 'Mautic\PluginBundle\Controller\PluginController::indexAction',
|
||||
],
|
||||
'mautic_plugin_config' => [
|
||||
'path' => '/plugins/config/{name}/{page}',
|
||||
'controller' => 'Mautic\PluginBundle\Controller\PluginController::configAction',
|
||||
],
|
||||
'mautic_plugin_info' => [
|
||||
'path' => '/plugins/info/{name}',
|
||||
'controller' => 'Mautic\PluginBundle\Controller\PluginController::infoAction',
|
||||
],
|
||||
'mautic_plugin_reload' => [
|
||||
'path' => '/plugins/reload',
|
||||
'controller' => 'Mautic\PluginBundle\Controller\PluginController::reloadAction',
|
||||
],
|
||||
],
|
||||
'public' => [
|
||||
'mautic_integration_auth_user' => [
|
||||
'path' => '/plugins/integrations/authuser/{integration}',
|
||||
'controller' => 'Mautic\PluginBundle\Controller\AuthController::authUserAction',
|
||||
],
|
||||
'mautic_integration_auth_callback' => [
|
||||
'path' => '/plugins/integrations/authcallback/{integration}',
|
||||
'controller' => 'Mautic\PluginBundle\Controller\AuthController::authCallbackAction',
|
||||
],
|
||||
'mautic_integration_auth_postauth' => [
|
||||
'path' => '/plugins/integrations/authstatus/{integration}',
|
||||
'controller' => 'Mautic\PluginBundle\Controller\AuthController::authStatusAction',
|
||||
],
|
||||
],
|
||||
],
|
||||
'menu' => [
|
||||
'admin' => [
|
||||
'priority' => 50,
|
||||
'items' => [
|
||||
'mautic.plugin.plugins' => [
|
||||
'id' => 'mautic_plugin_root',
|
||||
'access' => 'plugin:plugins:manage',
|
||||
'route' => 'mautic_plugin_index',
|
||||
'parent' => 'mautic.core.integrations',
|
||||
'iconClass' => 'ri-plug-line',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'services' => [
|
||||
'other' => [
|
||||
'mautic.helper.integration' => [
|
||||
'class' => Mautic\PluginBundle\Helper\IntegrationHelper::class,
|
||||
'arguments' => [
|
||||
'service_container',
|
||||
'doctrine.orm.entity_manager',
|
||||
'mautic.helper.paths',
|
||||
'mautic.helper.bundle',
|
||||
'mautic.helper.core_parameters',
|
||||
'twig',
|
||||
'mautic.plugin.model.plugin',
|
||||
],
|
||||
],
|
||||
'mautic.plugin.helper.reload' => [
|
||||
'class' => Mautic\PluginBundle\Helper\ReloadHelper::class,
|
||||
'arguments' => [
|
||||
'event_dispatcher',
|
||||
],
|
||||
],
|
||||
],
|
||||
'facades' => [
|
||||
'mautic.plugin.facade.reload' => [
|
||||
'class' => Mautic\PluginBundle\Facade\ReloadFacade::class,
|
||||
'arguments' => [
|
||||
'mautic.plugin.model.plugin',
|
||||
'mautic.plugin.helper.reload',
|
||||
'translator',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Mautic\CoreBundle\DependencyInjection\MauticCoreExtension;
|
||||
use Mautic\PluginBundle\EventListener\CampaignSubscriber;
|
||||
use Mautic\PluginBundle\EventListener\FormSubscriber;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
|
||||
|
||||
return function (ContainerConfigurator $configurator): void {
|
||||
$services = $configurator->services()
|
||||
->defaults()
|
||||
->autowire()
|
||||
->autoconfigure()
|
||||
->public();
|
||||
|
||||
$excludes = [
|
||||
'Helper/oAuthHelper.php',
|
||||
'Integration/IntegrationObject.php',
|
||||
];
|
||||
|
||||
$services->load('Mautic\\PluginBundle\\', '../')
|
||||
->exclude('../{'.implode(',', array_merge(MauticCoreExtension::DEFAULT_EXCLUDES, $excludes)).'}');
|
||||
|
||||
$services->load('Mautic\\PluginBundle\\Entity\\', '../Entity/*Repository.php');
|
||||
|
||||
$services->alias('mautic.plugin.model.plugin', Mautic\PluginBundle\Model\PluginModel::class);
|
||||
$services->alias('mautic.plugin.model.integration_entity', Mautic\PluginBundle\Model\IntegrationEntityModel::class);
|
||||
|
||||
$services->set(FormSubscriber::class)
|
||||
->call('setIntegrationHelper', [service('mautic.helper.integration')]);
|
||||
$services->set(CampaignSubscriber::class)
|
||||
->call('setIntegrationHelper', [service('mautic.helper.integration')]);
|
||||
};
|
||||
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Controller\AjaxController as CommonAjaxController;
|
||||
use Mautic\PluginBundle\Form\Type\CompanyFieldsType;
|
||||
use Mautic\PluginBundle\Form\Type\FieldsType;
|
||||
use Mautic\PluginBundle\Form\Type\IntegrationCampaignsType;
|
||||
use Mautic\PluginBundle\Form\Type\IntegrationConfigType;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Mautic\PluginBundle\Model\PluginModel;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class AjaxController extends CommonAjaxController
|
||||
{
|
||||
public function setIntegrationFilterAction(Request $request): \Symfony\Component\HttpFoundation\JsonResponse
|
||||
{
|
||||
$session = $request->getSession();
|
||||
$pluginFilter = (int) $request->get('plugin');
|
||||
$session->set('mautic.integrations.filter', $pluginFilter);
|
||||
|
||||
return $this->sendJsonResponse(['success' => 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML for list of fields.
|
||||
*/
|
||||
public function getIntegrationFieldsAction(Request $request, IntegrationHelper $helper): \Symfony\Component\HttpFoundation\JsonResponse
|
||||
{
|
||||
$integration = $request->query->get('integration');
|
||||
$settings = $request->query->all()['settings'] ?? [];
|
||||
$page = $request->query->get('page');
|
||||
|
||||
$dataArray = ['success' => 0];
|
||||
|
||||
if (!empty($integration) && !empty($settings)) {
|
||||
/** @var \Mautic\PluginBundle\Integration\AbstractIntegration $integrationObject */
|
||||
$integrationObject = $helper->getIntegrationObject($integration);
|
||||
|
||||
if ($integrationObject) {
|
||||
if (!$object = $request->attributes->get('object')) {
|
||||
$object = $settings['object'] ?? 'lead';
|
||||
}
|
||||
|
||||
$isLead = ('lead' === $object);
|
||||
$integrationFields = ($isLead)
|
||||
? $integrationObject->getFormLeadFields($settings)
|
||||
: $integrationObject->getFormCompanyFields(
|
||||
$settings
|
||||
);
|
||||
|
||||
if (!empty($integrationFields)) {
|
||||
$session = $request->getSession();
|
||||
$session->set('mautic.plugin.'.$integration.'.'.$object.'.page', $page);
|
||||
|
||||
/** @var PluginModel $pluginModel */
|
||||
$pluginModel = $this->getModel('plugin');
|
||||
|
||||
// Get a list of custom form fields
|
||||
$mauticFields = ($isLead) ? $pluginModel->getLeadFields() : $pluginModel->getCompanyFields();
|
||||
$featureSettings = $integrationObject->getIntegrationSettings()->getFeatureSettings();
|
||||
$enableDataPriority = $integrationObject->getDataPriority();
|
||||
$formType = $isLead ? 'integration_fields' : 'integration_company_fields';
|
||||
$form = $this->createForm(
|
||||
$isLead ? FieldsType::class : CompanyFieldsType::class,
|
||||
$featureSettings[$object.'Fields'] ?? [],
|
||||
[
|
||||
'mautic_fields' => $mauticFields,
|
||||
'data' => $featureSettings,
|
||||
'integration_fields' => $integrationFields,
|
||||
'csrf_protection' => false,
|
||||
'integration_object' => $integrationObject,
|
||||
'enable_data_priority' => $enableDataPriority,
|
||||
'integration' => $integration,
|
||||
'page' => $page,
|
||||
'limit' => $this->coreParametersHelper->get('default_pagelimit'),
|
||||
]
|
||||
);
|
||||
|
||||
$html = $this->render('@MauticCore/Helper/blank_form.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'formTheme' => '@MauticPlugin/FormTheme/Integration/layout.html.twig',
|
||||
'function' => 'row',
|
||||
]
|
||||
)->getContent();
|
||||
|
||||
if (!isset($settings['prefix'])) {
|
||||
$prefix = 'integration_details[featureSettings]['.$object.'Fields]';
|
||||
} else {
|
||||
$prefix = $settings['prefix'];
|
||||
}
|
||||
|
||||
$idPrefix = str_replace(['][', '[', ']'], '_', $prefix);
|
||||
if (str_ends_with($idPrefix, '_')) {
|
||||
$idPrefix = substr($idPrefix, 0, -1);
|
||||
}
|
||||
$html = preg_replace('/'.$form->getName().'\[(.*?)\]/', $prefix.'[$1]', $html);
|
||||
$html = str_replace($form->getName(), $idPrefix, $html);
|
||||
$dataArray['success'] = 1;
|
||||
$dataArray['html'] = $html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->sendJsonResponse($dataArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML for integration properties.
|
||||
*/
|
||||
public function getIntegrationConfigAction(Request $request, IntegrationHelper $integrationHelper): \Symfony\Component\HttpFoundation\JsonResponse
|
||||
{
|
||||
$integration = $request->query->get('integration');
|
||||
$settings = $request->query->all()['settings'] ?? [];
|
||||
$dataArray = ['success' => 0];
|
||||
|
||||
if (!empty($integration) && !empty($settings)) {
|
||||
/** @var \Mautic\PluginBundle\Integration\AbstractIntegration $object */
|
||||
$object = $integrationHelper->getIntegrationObject($integration);
|
||||
|
||||
if ($object) {
|
||||
$data = $statusData = [];
|
||||
$objectSettings = $object->getIntegrationSettings();
|
||||
$defaults = $objectSettings->getFeatureSettings();
|
||||
if (method_exists($object, 'getCampaigns')) {
|
||||
$campaigns = $object->getCampaigns();
|
||||
if (isset($campaigns['records']) && !empty($campaigns['records'])) {
|
||||
foreach ($campaigns['records'] as $campaign) {
|
||||
$data[$campaign['Id']] = $campaign['Name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$form = $this->createForm(IntegrationConfigType::class, $defaults, [
|
||||
'integration' => $object,
|
||||
'csrf_protection' => false,
|
||||
'campaigns' => $data,
|
||||
]);
|
||||
|
||||
$html = $this->render('@MauticCore/Helper/blank_form.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'function' => 'widget',
|
||||
'formTheme' => '@MauticPlugin/FormTheme/Integration/layout.html.twig',
|
||||
'variables' => [
|
||||
'integration' => $object,
|
||||
],
|
||||
])->getContent();
|
||||
|
||||
$prefix = str_replace('[integration]', '[config]', $settings['name']);
|
||||
$idPrefix = str_replace(['][', '[', ']'], '_', $prefix);
|
||||
if (str_ends_with($idPrefix, '_')) {
|
||||
$idPrefix = substr($idPrefix, 0, -1);
|
||||
}
|
||||
|
||||
$html = preg_replace('/integration_config\[(.*?)\]/', $prefix.'[$1]', $html);
|
||||
$html = str_replace('integration_config', $idPrefix, $html);
|
||||
|
||||
$dataArray['success'] = 1;
|
||||
$dataArray['html'] = $html;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->sendJsonResponse($dataArray);
|
||||
}
|
||||
|
||||
public function getIntegrationCampaignStatusAction(Request $request, IntegrationHelper $integrationHelper): \Symfony\Component\HttpFoundation\JsonResponse
|
||||
{
|
||||
$integration = $request->query->get('integration');
|
||||
$campaign = $request->query->get('campaign');
|
||||
$settings = $request->query->all()['settings'] ?? [];
|
||||
$dataArray = ['success' => 0];
|
||||
$statusData = [];
|
||||
if (!empty($integration) && !empty($campaign)) {
|
||||
/** @var \Mautic\PluginBundle\Integration\AbstractIntegration $object */
|
||||
$object = $integrationHelper->getIntegrationObject($integration);
|
||||
|
||||
if ($object) {
|
||||
if (method_exists($object, 'getCampaignMemberStatus')) {
|
||||
$campaignMemberStatus = $object->getCampaignMemberStatus($campaign);
|
||||
if (isset($campaignMemberStatus['records']) && !empty($campaignMemberStatus['records'])) {
|
||||
foreach ($campaignMemberStatus['records'] as $status) {
|
||||
$statusData[$status['Label']] = $status['Label'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$form = $this->createForm(IntegrationCampaignsType::class, $statusData, [
|
||||
'csrf_protection' => false,
|
||||
'campaignContactStatus' => $statusData,
|
||||
]);
|
||||
|
||||
$html = $this->render('@MauticCore/Helper/blank_form.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'formTheme' => '@MauticPlugin/FormTheme/Integration/layout.html.twig',
|
||||
'function' => 'widget',
|
||||
'variables' => [
|
||||
'integration' => $object,
|
||||
],
|
||||
])->getContent();
|
||||
|
||||
$prefix = str_replace('[integration]', '[campaign_member_status][campaign_member_status]', $settings['name']);
|
||||
|
||||
$idPrefix = str_replace(['][', '[', ']'], '_', $prefix);
|
||||
|
||||
if (str_ends_with($idPrefix, '_')) {
|
||||
$idPrefix = substr($idPrefix, 0, -1);
|
||||
}
|
||||
|
||||
$html = preg_replace('/integration_campaign_status_campaign_member_status\[(.*?)\]/', $prefix.'[$1]', $html);
|
||||
$html = str_replace('integration_campaign_status_campaign_member_status', $idPrefix, $html);
|
||||
$html = str_replace('integration_campaign_status[campaign_member_status]', $prefix, $html);
|
||||
|
||||
$dataArray['success'] = 1;
|
||||
$dataArray['html'] = $html;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->sendJsonResponse($dataArray);
|
||||
}
|
||||
|
||||
public function matchFieldsAction(Request $request, IntegrationHelper $integrationHelper): \Symfony\Component\HttpFoundation\JsonResponse
|
||||
{
|
||||
$integration = $request->request->get('integration');
|
||||
$integration_field = $request->request->get('integrationField');
|
||||
$mautic_field = $request->request->get('mauticField');
|
||||
$update_mautic = $request->request->get('updateMautic');
|
||||
$object = $request->request->get('object');
|
||||
|
||||
$integration_object = $integrationHelper->getIntegrationObject($integration);
|
||||
$entity = $integration_object->getIntegrationSettings();
|
||||
$featureSettings = $entity->getFeatureSettings();
|
||||
$doNotMatchField = ('-1' === $mautic_field || '' === $mautic_field);
|
||||
if ('lead' == $object) {
|
||||
$fields = 'leadFields';
|
||||
$updateFields = 'update_mautic';
|
||||
} else {
|
||||
$fields = 'companyFields';
|
||||
$updateFields = 'update_mautic_company';
|
||||
}
|
||||
$newFeatureSettings = [];
|
||||
if ($doNotMatchField) {
|
||||
if (isset($featureSettings[$updateFields]) && array_key_exists($integration_field, $featureSettings[$updateFields])) {
|
||||
unset($featureSettings[$updateFields][$integration_field]);
|
||||
}
|
||||
if (isset($featureSettings[$fields]) && array_key_exists($integration_field, $featureSettings[$fields])) {
|
||||
unset($featureSettings[$fields][$integration_field]);
|
||||
}
|
||||
$dataArray = ['success' => 0];
|
||||
} else {
|
||||
$newFeatureSettings[$integration_field] = $update_mautic;
|
||||
if (isset($featureSettings[$updateFields])) {
|
||||
$featureSettings[$updateFields] = array_merge($featureSettings[$updateFields], $newFeatureSettings);
|
||||
} else {
|
||||
$featureSettings[$updateFields] = $newFeatureSettings;
|
||||
}
|
||||
$newFeatureSettings[$integration_field] = $mautic_field;
|
||||
if (isset($featureSettings[$fields])) {
|
||||
$featureSettings[$fields] = array_merge($featureSettings[$fields], $newFeatureSettings);
|
||||
} else {
|
||||
$featureSettings[$fields] = $newFeatureSettings;
|
||||
}
|
||||
|
||||
$dataArray = ['success' => 1];
|
||||
}
|
||||
$entity->setFeatureSettings($featureSettings);
|
||||
|
||||
$pluginModel = $this->getModel('plugin');
|
||||
\assert($pluginModel instanceof PluginModel);
|
||||
$pluginModel->saveFeatureSettings($entity);
|
||||
|
||||
return $this->sendJsonResponse($dataArray);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Controller\FormController;
|
||||
use Mautic\PluginBundle\Event\PluginIntegrationAuthRedirectEvent;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Mautic\PluginBundle\PluginEvents;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class AuthController extends FormController
|
||||
{
|
||||
/**
|
||||
* @param string $integration
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function authCallbackAction(Request $request, IntegrationHelper $integrationHelper, $integration): JsonResponse|RedirectResponse
|
||||
{
|
||||
$isAjax = $request->isXmlHttpRequest();
|
||||
$session = $request->getSession();
|
||||
|
||||
$integrationObject = $integrationHelper->getIntegrationObject($integration);
|
||||
|
||||
// check to see if the service exists
|
||||
if (!$integrationObject) {
|
||||
$session->set('mautic.integration.postauth.message', ['mautic.integration.notfound', ['%name%' => $integration], 'error']);
|
||||
if ($isAjax) {
|
||||
return new JsonResponse(['url' => $this->generateUrl('mautic_integration_auth_postauth', ['integration' => $integration])]);
|
||||
} else {
|
||||
return new RedirectResponse($this->generateUrl('mautic_integration_auth_postauth', ['integration' => $integration]));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$error = $integrationObject->authCallback();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$session->set('mautic.integration.postauth.message', [$e->getMessage(), [], 'error']);
|
||||
$redirectUrl = $this->generateUrl('mautic_integration_auth_postauth', ['integration' => $integration]);
|
||||
if ($isAjax) {
|
||||
return new JsonResponse(['url' => $redirectUrl]);
|
||||
} else {
|
||||
return new RedirectResponse($redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// check for error
|
||||
if ($error) {
|
||||
$type = 'error';
|
||||
$message = 'mautic.integration.error.oauthfail';
|
||||
$params = ['%error%' => $error];
|
||||
} else {
|
||||
$type = 'notice';
|
||||
$message = 'mautic.integration.notice.oauthsuccess';
|
||||
$params = [];
|
||||
}
|
||||
|
||||
$session->set('mautic.integration.postauth.message', [$message, $params, $type]);
|
||||
|
||||
$identifier[$integration] = null;
|
||||
$socialCache = [];
|
||||
$userData = $integrationObject->getUserData($identifier, $socialCache);
|
||||
|
||||
$session->set('mautic.integration.'.$integration.'.userdata', $userData);
|
||||
|
||||
return new RedirectResponse($this->generateUrl('mautic_integration_auth_postauth', ['integration' => $integration]));
|
||||
}
|
||||
|
||||
public function authStatusAction(Request $request, $integration): \Symfony\Component\HttpFoundation\Response
|
||||
{
|
||||
$postAuthTemplate = '@MauticPlugin/Auth/postauth.html.twig';
|
||||
|
||||
$session = $request->getSession();
|
||||
$postMessage = $session->get('mautic.integration.postauth.message');
|
||||
$userData = [];
|
||||
|
||||
if (isset($integration)) {
|
||||
$userData = $session->get('mautic.integration.'.$integration.'.userdata');
|
||||
}
|
||||
|
||||
$message = $type = '';
|
||||
$alert = 'success';
|
||||
if (!empty($postMessage)) {
|
||||
$message = $this->translator->trans($postMessage[0], $postMessage[1], 'flashes');
|
||||
$session->remove('mautic.integration.postauth.message');
|
||||
$type = $postMessage[2];
|
||||
if ('error' == $type) {
|
||||
$alert = 'danger';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render($postAuthTemplate, ['message' => $message, 'alert' => $alert, 'data' => $userData]);
|
||||
}
|
||||
|
||||
public function authUserAction(IntegrationHelper $integrationHelper, $integration): RedirectResponse
|
||||
{
|
||||
$integrationObject = $integrationHelper->getIntegrationObject($integration);
|
||||
|
||||
$settings['method'] = 'GET';
|
||||
$settings['integration'] = $integrationObject->getName();
|
||||
|
||||
/** @var \Mautic\PluginBundle\Integration\AbstractIntegration $integrationObject */
|
||||
$event = $this->dispatcher->dispatch(
|
||||
new PluginIntegrationAuthRedirectEvent(
|
||||
$integrationObject,
|
||||
$integrationObject->getAuthLoginUrl()
|
||||
),
|
||||
PluginEvents::PLUGIN_ON_INTEGRATION_AUTH_REDIRECT
|
||||
);
|
||||
$oauthUrl = $event->getAuthUrl();
|
||||
|
||||
return new RedirectResponse($oauthUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Controller;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\CoreBundle\Controller\FormController;
|
||||
use Mautic\CoreBundle\Helper\InputHelper;
|
||||
use Mautic\PluginBundle\Event\PluginIntegrationAuthRedirectEvent;
|
||||
use Mautic\PluginBundle\Event\PluginIntegrationEvent;
|
||||
use Mautic\PluginBundle\Facade\ReloadFacade;
|
||||
use Mautic\PluginBundle\Form\Type\DetailsType;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Mautic\PluginBundle\Integration\AbstractIntegration;
|
||||
use Mautic\PluginBundle\Model\PluginModel;
|
||||
use Mautic\PluginBundle\PluginEvents;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class PluginController extends FormController
|
||||
{
|
||||
/**
|
||||
* @return JsonResponse|Response
|
||||
*/
|
||||
public function indexAction(Request $request, IntegrationHelper $integrationHelper)
|
||||
{
|
||||
if (!$this->security->isGranted('plugin:plugins:manage')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
/** @var PluginModel $pluginModel */
|
||||
$pluginModel = $this->getModel('plugin');
|
||||
|
||||
// List of plugins for filter and to show as a single integration
|
||||
$plugins = $pluginModel->getEntities(
|
||||
[
|
||||
'filter' => [
|
||||
'force' => [
|
||||
[
|
||||
'column' => 'p.isMissing',
|
||||
'expr' => 'eq',
|
||||
'value' => 0,
|
||||
],
|
||||
],
|
||||
],
|
||||
'hydration_mode' => 'hydrate_array',
|
||||
]
|
||||
);
|
||||
|
||||
$session = $request->getSession();
|
||||
$pluginFilter = $request->get('plugin', $session->get('mautic.integrations.filter', ''));
|
||||
|
||||
$session->set('mautic.integrations.filter', $pluginFilter);
|
||||
|
||||
$integrationObjects = $integrationHelper->getIntegrationObjects(null, null, true);
|
||||
$integrations = $foundPlugins = [];
|
||||
|
||||
foreach ($integrationObjects as $name => $object) {
|
||||
$settings = $object->getIntegrationSettings();
|
||||
$plugin = $settings->getPlugin();
|
||||
$pluginId = $plugin ? $plugin->getId() : $name;
|
||||
if (isset($plugins[$pluginId]) || $pluginId === $name) {
|
||||
$integrations[$name] = [
|
||||
'name' => $object->getName(),
|
||||
'display' => $object->getDisplayName(),
|
||||
'icon' => $integrationHelper->getIconPath($object),
|
||||
'enabled' => $settings->isPublished(),
|
||||
'plugin' => $pluginId,
|
||||
'isBundle' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$foundPlugins[$pluginId] = true;
|
||||
}
|
||||
|
||||
$nonIntegrationPlugins = array_diff_key($plugins, $foundPlugins);
|
||||
foreach ($nonIntegrationPlugins as $plugin) {
|
||||
$integrations[$plugin['name']] = [
|
||||
'name' => $plugin['bundle'],
|
||||
'display' => $plugin['name'],
|
||||
'icon' => $integrationHelper->getIconPath($plugin),
|
||||
'enabled' => true,
|
||||
'plugin' => $plugin['id'],
|
||||
'description' => $plugin['description'],
|
||||
'isBundle' => true,
|
||||
];
|
||||
}
|
||||
|
||||
// sort by name
|
||||
uksort(
|
||||
$integrations,
|
||||
fn ($a, $b): int => strnatcasecmp($a, $b)
|
||||
);
|
||||
|
||||
$tmpl = $request->isXmlHttpRequest() ? $request->get('tmpl', 'index') : 'index';
|
||||
|
||||
if (!empty($pluginFilter)) {
|
||||
foreach ($plugins as $plugin) {
|
||||
if ($plugin['id'] == $pluginFilter) {
|
||||
$pluginName = $plugin['name'];
|
||||
$pluginId = $plugin['id'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->delegateView(
|
||||
[
|
||||
'viewParameters' => [
|
||||
'items' => $integrations,
|
||||
'tmpl' => $tmpl,
|
||||
'pluginFilter' => ($pluginFilter) ? ['id' => $pluginId, 'name' => $pluginName] : false,
|
||||
'plugins' => $plugins,
|
||||
],
|
||||
'contentTemplate' => '@MauticPlugin/Integration/grid.html.twig',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => '#mautic_plugin_index',
|
||||
'mauticContent' => 'integration',
|
||||
'route' => $this->generateUrl('mautic_plugin_index'),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return JsonResponse|Response
|
||||
*/
|
||||
public function configAction(Request $request, EntityManagerInterface $em, IntegrationHelper $integrationHelper, LoggerInterface $mauticLogger, $name, $activeTab = 'details-container', $page = 1)
|
||||
{
|
||||
if (!$this->security->isGranted('plugin:plugins:manage')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
if (!empty($request->get('activeTab'))) {
|
||||
$activeTab = $request->get('activeTab');
|
||||
}
|
||||
|
||||
$session = $request->getSession();
|
||||
|
||||
$integrationDetailsPost = $request->request->all()['integration_details'] ?? [];
|
||||
$authorize = empty($integrationDetailsPost['in_auth']) ? false : true;
|
||||
|
||||
/** @var AbstractIntegration $integrationObject */
|
||||
$integrationObject = $integrationHelper->getIntegrationObject($name);
|
||||
|
||||
// Verify that the requested integration exists
|
||||
if (empty($integrationObject)) {
|
||||
throw $this->createNotFoundException($this->translator->trans('mautic.core.url.error.404'));
|
||||
}
|
||||
|
||||
$object = ('leadFieldsContainer' === $activeTab) ? 'lead' : 'company';
|
||||
$limit = $this->coreParametersHelper->get('default_pagelimit');
|
||||
$start = (1 === $page) ? 0 : (($page - 1) * $limit);
|
||||
if ($start < 0) {
|
||||
$start = 0;
|
||||
}
|
||||
$session->set('mautic.plugin.'.$name.'.'.$object.'.start', $start);
|
||||
$session->set('mautic.plugin.'.$name.'.'.$object.'.page', $page);
|
||||
|
||||
/** @var PluginModel $pluginModel */
|
||||
$pluginModel = $this->getModel('plugin');
|
||||
$leadFields = $pluginModel->getLeadFields();
|
||||
$companyFields = $pluginModel->getCompanyFields();
|
||||
/** @var AbstractIntegration $integrationObject */
|
||||
$entity = $integrationObject->getIntegrationSettings();
|
||||
$existingPublishedState = $entity->getIsPublished();
|
||||
$form = $this->createForm(
|
||||
DetailsType::class,
|
||||
$entity,
|
||||
[
|
||||
'integration' => $entity->getName(),
|
||||
'lead_fields' => $leadFields,
|
||||
'company_fields' => $companyFields,
|
||||
'integration_object' => $integrationObject,
|
||||
'action' => $this->generateUrl('mautic_plugin_config', ['name' => $name]),
|
||||
]
|
||||
);
|
||||
|
||||
if ('POST' == $request->getMethod()) {
|
||||
$valid = false;
|
||||
if (!$cancelled = $this->isFormCancelled($form)) {
|
||||
$currentKeys = $integrationObject->getDecryptedApiKeys($entity);
|
||||
$currentFeatureSettings = $entity->getFeatureSettings();
|
||||
$valid = $this->isFormValid($form);
|
||||
|
||||
if ($authorize || $valid) {
|
||||
$integration = $entity->getName();
|
||||
|
||||
if (isset($form['apiKeys'])) {
|
||||
$keys = $form['apiKeys']->getData();
|
||||
|
||||
// Prevent merged keys
|
||||
$secretKeys = $integrationObject->getSecretKeys();
|
||||
foreach ($secretKeys as $secretKey) {
|
||||
if (empty($keys[$secretKey]) && !empty($currentKeys[$secretKey])) {
|
||||
$keys[$secretKey] = $currentKeys[$secretKey];
|
||||
}
|
||||
}
|
||||
$keys = $this->removeAuthData($keys, $currentKeys, $integrationObject);
|
||||
$integrationObject->encryptAndSetApiKeys($keys, $entity);
|
||||
|
||||
$integrationObject->encryptAndSetApiKeys($keys, $entity);
|
||||
}
|
||||
|
||||
if (!$authorize) {
|
||||
$features = $entity->getSupportedFeatures();
|
||||
if (in_array('public_profile', $features) || in_array('push_lead', $features)) {
|
||||
// Ungroup the fields
|
||||
$mauticLeadFields = [];
|
||||
foreach ($leadFields as $groupFields) {
|
||||
$mauticLeadFields = array_merge($mauticLeadFields, $groupFields);
|
||||
}
|
||||
$mauticCompanyFields = [];
|
||||
foreach ($companyFields as $groupFields) {
|
||||
$mauticCompanyFields = array_merge($mauticCompanyFields, $groupFields);
|
||||
}
|
||||
|
||||
if ($missing = $integrationObject->cleanUpFields($entity, $mauticLeadFields, $mauticCompanyFields)) {
|
||||
if ($entity->getIsPublished()) {
|
||||
// Only fail validation if the integration is enabled
|
||||
if (!empty($missing['leadFields'])) {
|
||||
$valid = false;
|
||||
|
||||
$form->get('featureSettings')->get('leadFields')->addError(
|
||||
new FormError(
|
||||
$this->translator->trans('mautic.plugin.field.required_mapping_missing', [], 'validators')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($missing['companyFields'])) {
|
||||
$valid = false;
|
||||
|
||||
$form->get('featureSettings')->get('companyFields')->addError(
|
||||
new FormError(
|
||||
$this->translator->trans('mautic.plugin.field.required_mapping_missing', [], 'validators')
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// make sure they aren't overwritten because of API connection issues
|
||||
$entity->setFeatureSettings($currentFeatureSettings);
|
||||
}
|
||||
|
||||
if ($valid || $authorize) {
|
||||
$dispatcher = $this->dispatcher;
|
||||
$mauticLogger->info('Dispatching integration config save event.');
|
||||
if ($dispatcher->hasListeners(PluginEvents::PLUGIN_ON_INTEGRATION_CONFIG_SAVE)) {
|
||||
$mauticLogger->info('Event dispatcher has integration config save listeners.');
|
||||
if (!$valid && !$existingPublishedState) {
|
||||
$integrationObject->getIntegrationSettings()->setIsPublished(false);
|
||||
}
|
||||
$event = new PluginIntegrationEvent($integrationObject);
|
||||
|
||||
$dispatcher->dispatch($event, PluginEvents::PLUGIN_ON_INTEGRATION_CONFIG_SAVE);
|
||||
|
||||
$entity = $event->getEntity();
|
||||
}
|
||||
|
||||
$em->persist($entity);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
if ($authorize) {
|
||||
// redirect to the oauth URL
|
||||
/** @var AbstractIntegration $integrationObject */
|
||||
$event = $this->dispatcher->dispatch(
|
||||
new PluginIntegrationAuthRedirectEvent(
|
||||
$integrationObject,
|
||||
$integrationObject->getAuthLoginUrl()
|
||||
),
|
||||
PluginEvents::PLUGIN_ON_INTEGRATION_AUTH_REDIRECT
|
||||
);
|
||||
$oauthUrl = $event->getAuthUrl();
|
||||
|
||||
return new JsonResponse(
|
||||
[
|
||||
'integration' => $integration,
|
||||
'authUrl' => $oauthUrl,
|
||||
'authorize' => 1,
|
||||
'popupBlockerMessage' => $this->translator->trans('mautic.core.popupblocked'),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (($cancelled || ($valid && !$this->isFormApplied($form))) && !$authorize) {
|
||||
// Close the modal and return back to the list view
|
||||
return new JsonResponse(
|
||||
[
|
||||
'closeModal' => 1,
|
||||
'enabled' => $entity->getIsPublished(),
|
||||
'name' => $integrationObject->getName(),
|
||||
'mauticContent' => 'integrationConfig',
|
||||
'sidebar' => $this->renderView('@MauticCore/LeftPanel/index.html.twig'),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$template = $integrationObject->getFormTemplate();
|
||||
$objectTheme = $integrationObject->getFormTheme();
|
||||
$themes = [
|
||||
'@MauticPlugin/FormTheme/Integration/layout.html.twig',
|
||||
];
|
||||
if (is_array($objectTheme)) {
|
||||
$themes = array_merge($themes, $objectTheme);
|
||||
} elseif (is_string($objectTheme)) {
|
||||
$themes[] = $objectTheme;
|
||||
}
|
||||
$themes = array_unique($themes);
|
||||
|
||||
$formSettings = $integrationObject->getFormSettings();
|
||||
$callbackUrl = !empty($formSettings['requires_callback']) ? $integrationObject->getAuthCallbackUrl() : '';
|
||||
|
||||
$formNotes = [];
|
||||
$noteSections = ['authorization', 'features', 'feature_settings', 'custom'];
|
||||
foreach ($noteSections as $section) {
|
||||
if ('custom' === $section) {
|
||||
$formNotes[$section] = $integrationObject->getFormNotes($section);
|
||||
} else {
|
||||
[$specialInstructions, $alertType] = $integrationObject->getFormNotes($section);
|
||||
|
||||
if (!empty($specialInstructions)) {
|
||||
$formNotes[$section] = [
|
||||
'note' => $specialInstructions,
|
||||
'type' => $alertType,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->delegateView(
|
||||
[
|
||||
'viewParameters' => [
|
||||
'form' => $form->createView(),
|
||||
'description' => $integrationObject->getDescription(),
|
||||
'formSettings' => $formSettings,
|
||||
'formNotes' => $formNotes,
|
||||
'callbackUrl' => $callbackUrl,
|
||||
'activeTab' => $activeTab,
|
||||
'formThemes' => $themes,
|
||||
],
|
||||
'contentTemplate' => $template,
|
||||
'passthroughVars' => [
|
||||
'activeLink' => '#mautic_plugin_index',
|
||||
'mauticContent' => 'integrationConfig',
|
||||
'route' => false,
|
||||
'sidebar' => $this->renderView('@MauticCore/LeftPanel/index.html.twig'),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|JsonResponse|RedirectResponse|Response
|
||||
*/
|
||||
public function infoAction(IntegrationHelper $integrationHelper, $name)
|
||||
{
|
||||
if (!$this->security->isGranted('plugin:plugins:manage')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
/** @var PluginModel $pluginModel */
|
||||
$pluginModel = $this->getModel('plugin');
|
||||
|
||||
$bundle = $pluginModel->getRepository()->findOneBy(
|
||||
[
|
||||
'bundle' => InputHelper::clean($name),
|
||||
]
|
||||
);
|
||||
|
||||
if (!$bundle) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$bundle->splitDescriptions();
|
||||
|
||||
return $this->delegateView(
|
||||
[
|
||||
'viewParameters' => [
|
||||
'bundle' => $bundle,
|
||||
'icon' => $integrationHelper->getIconPath($bundle),
|
||||
],
|
||||
'contentTemplate' => '@MauticPlugin/Integration/info.html.twig',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => '#mautic_plugin_index',
|
||||
'mauticContent' => 'integration',
|
||||
'route' => false,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the addon bundles directly and loads bundles which are not registered to the database.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function reloadAction(Request $request, ReloadFacade $reloadFacade)
|
||||
{
|
||||
if (!$this->security->isGranted('plugin:plugins:manage')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$this->addFlashMessage(
|
||||
$reloadFacade->reloadPlugins()
|
||||
);
|
||||
|
||||
$viewParameters = [
|
||||
'page' => $request->getSession()->get('mautic.plugin.page'),
|
||||
];
|
||||
|
||||
// Refresh the index contents
|
||||
return $this->postActionRedirect(
|
||||
[
|
||||
'returnUrl' => $this->generateUrl('mautic_plugin_index', $viewParameters),
|
||||
'viewParameters' => $viewParameters,
|
||||
'contentTemplate' => 'Mautic\PluginBundle\Controller\PluginController::indexAction',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => '#mautic_plugin_index',
|
||||
'mauticContent' => 'plugin',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array <string,mixed> $keys
|
||||
* @param array <string,mixed> $currentKeys
|
||||
*
|
||||
* @return array <string,mixed>
|
||||
*
|
||||
* @phpstan-ignore-next-line Ignore as AbstractIntegration is deprecated
|
||||
*/
|
||||
private function removeAuthData(array $keys, array $currentKeys, AbstractIntegration $integrationObject): array
|
||||
{
|
||||
$resetTokens = false;
|
||||
$secretKeys = array_unique(array_merge($integrationObject->getSecretKeys(), [$integrationObject->getClientIdKey()]));
|
||||
|
||||
foreach ($secretKeys as $secretKey) {
|
||||
if (($keys[$secretKey] ?? null) !== ($currentKeys[$secretKey] ?? null)) {
|
||||
$resetTokens = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$resetTokens) {
|
||||
return $keys;
|
||||
}
|
||||
|
||||
$keysToRemove = array_unique(array_merge($integrationObject->getRefreshTokenKeys(), [$integrationObject->getAuthTokenKey()]));
|
||||
|
||||
return array_diff_key($keys, array_flip($keysToRemove));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
|
||||
|
||||
class MauticPluginExtension 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');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
|
||||
use Mautic\CoreBundle\Entity\CacheInvalidateInterface;
|
||||
use Mautic\CoreBundle\Entity\CommonEntity;
|
||||
|
||||
class Integration extends CommonEntity implements CacheInvalidateInterface
|
||||
{
|
||||
public const CACHE_NAMESPACE = 'IntegrationSettings';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var Plugin|null
|
||||
*/
|
||||
private $plugin;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isPublished = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $supportedFeatures = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $apiKeys = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $featureSettings = [];
|
||||
|
||||
public static function loadMetadata(ORM\ClassMetadata $metadata): void
|
||||
{
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('plugin_integration_settings')
|
||||
->setCustomRepositoryClass(IntegrationRepository::class);
|
||||
|
||||
$builder->createField('id', 'integer')
|
||||
->makePrimaryKey()
|
||||
->generatedValue()
|
||||
->build();
|
||||
|
||||
$builder->createManyToOne('plugin', 'Plugin')
|
||||
->inversedBy('integrations')
|
||||
->addJoinColumn('plugin_id', 'id', true, false, 'CASCADE')
|
||||
->build();
|
||||
|
||||
$builder->addField('name', 'string');
|
||||
|
||||
$builder->createField('isPublished', 'boolean')
|
||||
->columnName('is_published')
|
||||
->build();
|
||||
|
||||
$builder->createField('supportedFeatures', 'array')
|
||||
->columnName('supported_features')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('apiKeys', 'array')
|
||||
->columnName('api_keys')
|
||||
->build();
|
||||
|
||||
$builder->createField('featureSettings', 'array')
|
||||
->columnName('feature_settings')
|
||||
->nullable()
|
||||
->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Plugin|null
|
||||
*/
|
||||
public function getPlugin()
|
||||
{
|
||||
return $this->plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $plugin
|
||||
*
|
||||
* @return Integration
|
||||
*/
|
||||
public function setPlugin($plugin)
|
||||
{
|
||||
$this->plugin = $plugin;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $name
|
||||
*
|
||||
* @return Integration
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->isChanged('name', $name);
|
||||
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getIsPublished()
|
||||
{
|
||||
return $this->isPublished;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $isPublished
|
||||
*
|
||||
* @return Integration
|
||||
*/
|
||||
public function setIsPublished($isPublished)
|
||||
{
|
||||
$this->isChanged('isPublished', $isPublished);
|
||||
|
||||
$this->isPublished = $isPublished;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isPublished(): bool
|
||||
{
|
||||
return $this->isPublished;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSupportedFeatures()
|
||||
{
|
||||
return $this->supportedFeatures;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $supportedFeatures
|
||||
*
|
||||
* @return Integration
|
||||
*/
|
||||
public function setSupportedFeatures($supportedFeatures)
|
||||
{
|
||||
$this->isChanged('supportedFeatures', $supportedFeatures);
|
||||
|
||||
$this->supportedFeatures = $supportedFeatures;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getApiKeys()
|
||||
{
|
||||
return $this->apiKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $apiKeys
|
||||
*
|
||||
* @return Integration
|
||||
*/
|
||||
public function setApiKeys($apiKeys)
|
||||
{
|
||||
$this->apiKeys = $apiKeys;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFeatureSettings()
|
||||
{
|
||||
return $this->featureSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $featureSettings
|
||||
*
|
||||
* @return Integration
|
||||
*/
|
||||
public function setFeatureSettings($featureSettings)
|
||||
{
|
||||
$this->isChanged('featureSettings', $featureSettings);
|
||||
|
||||
$this->featureSettings = $featureSettings;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCacheNamespacesToDelete(): array
|
||||
{
|
||||
return [self::CACHE_NAMESPACE];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
|
||||
use Mautic\CoreBundle\Entity\CommonEntity;
|
||||
|
||||
class IntegrationEntity extends CommonEntity
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $integration;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $integrationEntity;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $integrationEntityId;
|
||||
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $dateAdded;
|
||||
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $lastSyncDate;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $internalEntity;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $internalEntityId;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $internal;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->internal = new ArrayCollection();
|
||||
}
|
||||
|
||||
public static function loadMetadata(ORM\ClassMetadata $metadata): void
|
||||
{
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('integration_entity')
|
||||
->setCustomRepositoryClass(IntegrationEntityRepository::class)
|
||||
->addIndex(['integration', 'integration_entity', 'integration_entity_id'], 'integration_external_entity')
|
||||
->addIndex(['integration', 'internal_entity', 'internal_entity_id'], 'integration_internal_entity')
|
||||
->addIndex(['integration', 'internal_entity', 'integration_entity'], 'integration_entity_match')
|
||||
->addIndex(['integration', 'last_sync_date'], 'integration_last_sync_date')
|
||||
->addIndex(['internal_entity_id', 'integration_entity_id', 'internal_entity', 'integration_entity'], 'internal_integration_entity');
|
||||
|
||||
$builder->addId();
|
||||
|
||||
$builder->addDateAdded();
|
||||
|
||||
$builder->addNullableField('integration', 'string');
|
||||
|
||||
$builder->createField('integrationEntity', 'string')
|
||||
->columnName('integration_entity')
|
||||
->nullable()
|
||||
->build();
|
||||
$builder->createField('integrationEntityId', 'string')
|
||||
->columnName('integration_entity_id')
|
||||
->nullable()
|
||||
->build();
|
||||
$builder->createField('internalEntity', 'string')
|
||||
->columnName('internal_entity')
|
||||
->nullable()
|
||||
->build();
|
||||
$builder->createField('internalEntityId', 'integer')
|
||||
->columnName('internal_entity_id')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('lastSyncDate', 'datetime')
|
||||
->columnName('last_sync_date')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->addNullableField('internal', 'array');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIntegration()
|
||||
{
|
||||
return $this->integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $integration
|
||||
*
|
||||
* @return IntegrationEntity
|
||||
*/
|
||||
public function setIntegration($integration)
|
||||
{
|
||||
$this->integration = $integration;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIntegrationEntity()
|
||||
{
|
||||
return $this->integrationEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $integrationEntity
|
||||
*
|
||||
* @return IntegrationEntity
|
||||
*/
|
||||
public function setIntegrationEntity($integrationEntity)
|
||||
{
|
||||
$this->integrationEntity = $integrationEntity;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIntegrationEntityId()
|
||||
{
|
||||
return $this->integrationEntityId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $integrationEntityId
|
||||
*
|
||||
* @return IntegrationEntity
|
||||
*/
|
||||
public function setIntegrationEntityId($integrationEntityId)
|
||||
{
|
||||
$this->integrationEntityId = $integrationEntityId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getDateAdded()
|
||||
{
|
||||
return $this->dateAdded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $dateAdded
|
||||
*
|
||||
* @return IntegrationEntity
|
||||
*/
|
||||
public function setDateAdded($dateAdded)
|
||||
{
|
||||
$this->dateAdded = $dateAdded;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getLastSyncDate()
|
||||
{
|
||||
return $this->lastSyncDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $lastSyncDate
|
||||
*
|
||||
* @return IntegrationEntity
|
||||
*/
|
||||
public function setLastSyncDate($lastSyncDate)
|
||||
{
|
||||
$this->lastSyncDate = $lastSyncDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getInternalEntity()
|
||||
{
|
||||
return $this->internalEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $internalEntity
|
||||
*
|
||||
* @return IntegrationEntity
|
||||
*/
|
||||
public function setInternalEntity($internalEntity)
|
||||
{
|
||||
$this->internalEntity = $internalEntity;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getInternalEntityId()
|
||||
{
|
||||
return $this->internalEntityId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $internalEntityId
|
||||
*
|
||||
* @return IntegrationEntity
|
||||
*/
|
||||
public function setInternalEntityId($internalEntityId)
|
||||
{
|
||||
$this->internalEntityId = $internalEntityId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getInternal()
|
||||
{
|
||||
return $this->internal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $internal
|
||||
*
|
||||
* @return IntegrationEntity
|
||||
*/
|
||||
public function setInternal($internal)
|
||||
{
|
||||
$this->internal = $internal;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,539 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Entity;
|
||||
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Query\Expression\CompositeExpression;
|
||||
use Mautic\CoreBundle\Entity\CommonRepository;
|
||||
|
||||
/**
|
||||
* @extends CommonRepository<IntegrationEntity>
|
||||
*/
|
||||
class IntegrationEntityRepository extends CommonRepository
|
||||
{
|
||||
/**
|
||||
* @param array<int>|int|null $internalEntityIds
|
||||
* @param mixed $startDate
|
||||
* @param mixed $endDate
|
||||
* @param bool $push
|
||||
* @param int $start
|
||||
* @param int $limit
|
||||
* @param int|string|array<int|string>|null $integrationEntityIds
|
||||
*/
|
||||
public function getIntegrationsEntityId(
|
||||
$integration,
|
||||
$integrationEntity,
|
||||
$internalEntity,
|
||||
$internalEntityIds = null,
|
||||
$startDate = null,
|
||||
$endDate = null,
|
||||
$push = false,
|
||||
$start = 0,
|
||||
$limit = 0,
|
||||
$integrationEntityIds = null,
|
||||
): array {
|
||||
$q = $this->_em->getConnection()->createQueryBuilder()
|
||||
->select('DISTINCT(i.integration_entity_id), i.id, i.internal_entity_id, i.integration_entity, i.internal_entity')
|
||||
->from(MAUTIC_TABLE_PREFIX.'integration_entity', 'i');
|
||||
|
||||
$q->where('i.integration = :integration')
|
||||
->andWhere('i.internal_entity = :internalEntity')
|
||||
->setParameter('integration', $integration)
|
||||
->setParameter('internalEntity', $internalEntity);
|
||||
|
||||
if ($integrationEntity) {
|
||||
$q->andWhere('i.integration_entity = :integrationEntity')
|
||||
->setParameter('integrationEntity', $integrationEntity);
|
||||
}
|
||||
|
||||
if ('lead' === $internalEntity) {
|
||||
$joinCondition = $q->expr()->and(
|
||||
$q->expr()->eq('l.id', 'i.internal_entity_id')
|
||||
);
|
||||
|
||||
if ($push) {
|
||||
$joinCondition->with(
|
||||
$q->expr()->gte('l.last_active', ':startDate')
|
||||
);
|
||||
$q->setParameter('startDate', $startDate);
|
||||
}
|
||||
|
||||
$q->join('i', MAUTIC_TABLE_PREFIX.'leads', 'l', $joinCondition);
|
||||
}
|
||||
|
||||
if ($internalEntityIds) {
|
||||
if (is_array($internalEntityIds)) {
|
||||
$q->andWhere('i.internal_entity_id in (:internalEntityIds)')
|
||||
->setParameter('internalEntityIds', $internalEntityIds, ArrayParameterType::STRING);
|
||||
} else {
|
||||
$q->andWhere('i.internal_entity_id = :internalEntityId')
|
||||
->setParameter('internalEntityId', $internalEntityIds);
|
||||
}
|
||||
}
|
||||
|
||||
if ($startDate and !$push) {
|
||||
$q->andWhere('i.last_sync_date >= :startDate')
|
||||
->setParameter('startDate', $startDate);
|
||||
}
|
||||
|
||||
if ($endDate and !$push) {
|
||||
$q->andWhere('i.last_sync_date <= :endDate')
|
||||
->setParameter('endDate', $endDate);
|
||||
}
|
||||
|
||||
if ($integrationEntityIds) {
|
||||
if (is_array($integrationEntityIds)) {
|
||||
$q->andWhere('i.integration_entity_id in (:integrationEntityIds)')
|
||||
->setParameter('integrationEntityIds', $integrationEntityIds, ArrayParameterType::STRING);
|
||||
} else {
|
||||
$q->andWhere('i.integration_entity_id = :integrationEntityId')
|
||||
->setParameter('integrationEntityId', $integrationEntityIds);
|
||||
}
|
||||
}
|
||||
|
||||
if ($start) {
|
||||
$q->setFirstResult((int) $start);
|
||||
}
|
||||
|
||||
if ($limit) {
|
||||
$q->setMaxResults((int) $limit);
|
||||
}
|
||||
|
||||
return $q->executeQuery()->fetchAllAssociative();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getIntegrationEntity($integration, $integrationEntity, $internalEntity, $internalEntityId, $leadFields = null)
|
||||
{
|
||||
$q = $this->_em->getConnection()->createQueryBuilder()
|
||||
->from(MAUTIC_TABLE_PREFIX.'integration_entity', 'i')
|
||||
->join('i', MAUTIC_TABLE_PREFIX.'leads', 'l', 'l.id = i.internal_entity_id');
|
||||
$q->select('i.integration_entity_id, i.integration_entity, i.id, i.internal_entity_id');
|
||||
if ($leadFields) {
|
||||
$q->addSelect($leadFields);
|
||||
}
|
||||
|
||||
$q->where(
|
||||
$q->expr()->and(
|
||||
$q->expr()->eq('i.integration', ':integration'),
|
||||
$q->expr()->eq('i.internal_entity', ':internalEntity'),
|
||||
$q->expr()->eq('i.integration_entity', ':integrationEntity'),
|
||||
$q->expr()->eq('i.internal_entity_id', (int) $internalEntityId)
|
||||
)
|
||||
)
|
||||
->setParameter('integration', $integration)
|
||||
->setParameter('internalEntity', $internalEntity)
|
||||
->setParameter('integrationEntity', $integrationEntity)
|
||||
->setMaxResults(1);
|
||||
|
||||
$results = $q->executeQuery()->fetchAllAssociative();
|
||||
|
||||
return ($results) ? $results[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IntegrationEntity[]
|
||||
*/
|
||||
public function getIntegrationEntities($integration, $integrationEntity, $internalEntity, $internalEntityIds)
|
||||
{
|
||||
$q = $this->createQueryBuilder('i', 'i.internalEntityId');
|
||||
|
||||
$q->where(
|
||||
$q->expr()->andX(
|
||||
$q->expr()->eq('i.integration', ':integration'),
|
||||
$q->expr()->eq('i.internalEntity', ':internalEntity'),
|
||||
$q->expr()->eq('i.integrationEntity', ':integrationEntity'),
|
||||
$q->expr()->in('i.internalEntityId', ':internalEntityIds')
|
||||
)
|
||||
)
|
||||
->setParameter('integration', $integration)
|
||||
->setParameter('internalEntity', $internalEntity)
|
||||
->setParameter('integrationEntity', $integrationEntity)
|
||||
->setParameter('internalEntityIds', $internalEntityIds);
|
||||
|
||||
return $q->getQuery()->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @param array|string $integrationEntity
|
||||
* @param array $excludeIntegrationIds
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function findLeadsToUpdate(
|
||||
$integration,
|
||||
$internalEntity,
|
||||
$leadFields,
|
||||
$limit = 25,
|
||||
$fromDate = null,
|
||||
$toDate = null,
|
||||
$integrationEntity = ['Contact', 'Lead'],
|
||||
$excludeIntegrationIds = [],
|
||||
): array {
|
||||
if ('company' == $internalEntity) {
|
||||
$joinTable = 'companies';
|
||||
} else {
|
||||
$joinTable = 'leads';
|
||||
}
|
||||
$q = $this->_em->getConnection()->createQueryBuilder()
|
||||
->from(MAUTIC_TABLE_PREFIX.'integration_entity', 'i')
|
||||
->join('i', MAUTIC_TABLE_PREFIX.$joinTable, 'l', 'l.id = i.internal_entity_id');
|
||||
|
||||
if (false === $limit) {
|
||||
$q->select('count(i.integration_entity_id) as total');
|
||||
|
||||
if ($integrationEntity) {
|
||||
$q->addSelect('i.integration_entity');
|
||||
}
|
||||
} else {
|
||||
$q->select('i.integration_entity_id, i.integration_entity, i.id, i.internal_entity_id,'.$leadFields);
|
||||
}
|
||||
|
||||
$q->where('i.integration = :integration');
|
||||
|
||||
if ($integrationEntity) {
|
||||
if (!is_array($integrationEntity)) {
|
||||
$integrationEntity = [$integrationEntity];
|
||||
}
|
||||
$sub = null;
|
||||
foreach ($integrationEntity as $key => $entity) {
|
||||
if (null === $sub) {
|
||||
$sub = CompositeExpression::or($q->expr()->eq('i.integration_entity', ':entity'.$key));
|
||||
$q->setParameter('entity'.$key, $entity);
|
||||
continue;
|
||||
}
|
||||
|
||||
$sub->with($q->expr()->eq('i.integration_entity', ':entity'.$key));
|
||||
$q->setParameter('entity'.$key, $entity);
|
||||
}
|
||||
$q->andWhere($sub);
|
||||
}
|
||||
|
||||
$q->andWhere('i.internal_entity = :internalEntity')
|
||||
->setParameter('integration', $integration)
|
||||
->setParameter('internalEntity', $internalEntity);
|
||||
|
||||
if (!empty($excludeIntegrationIds)) {
|
||||
$q->andWhere(
|
||||
$q->expr()->notIn(
|
||||
'i.integration_entity_id',
|
||||
array_map(
|
||||
fn ($x): string => "'".$x."'",
|
||||
$excludeIntegrationIds
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$q->andWhere(
|
||||
$q->expr()->and(
|
||||
$q->expr()->isNotNull('i.integration_entity_id'),
|
||||
$q->expr()->or(
|
||||
$q->expr()->and(
|
||||
$q->expr()->isNotNull('i.last_sync_date'),
|
||||
$q->expr()->gt('l.date_modified', 'i.last_sync_date')
|
||||
),
|
||||
$q->expr()->and(
|
||||
$q->expr()->isNull('i.last_sync_date'),
|
||||
$q->expr()->isNotNull('l.date_modified'),
|
||||
$q->expr()->gt('l.date_modified', 'l.date_added')
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if ('lead' == $internalEntity) {
|
||||
$q->andWhere(
|
||||
$q->expr()->and($q->expr()->isNotNull('l.email')));
|
||||
} else {
|
||||
$q->andWhere(
|
||||
$q->expr()->and($q->expr()->isNotNull('l.companyname')));
|
||||
}
|
||||
|
||||
if ($fromDate) {
|
||||
if ($toDate) {
|
||||
$q->andWhere(
|
||||
$q->expr()->comparison('l.date_modified', 'BETWEEN', ':dateFrom and :dateTo')
|
||||
)
|
||||
->setParameter('dateFrom', $fromDate)
|
||||
->setParameter('dateTo', $toDate);
|
||||
} else {
|
||||
$q->andWhere(
|
||||
$q->expr()->gte('l.date_modified', ':dateFrom')
|
||||
)
|
||||
->setParameter('dateFrom', $fromDate);
|
||||
}
|
||||
} elseif ($toDate) {
|
||||
$q->andWhere(
|
||||
$q->expr()->lte('l.date_modified', ':dateTo')
|
||||
)
|
||||
->setParameter('dateTo', $toDate);
|
||||
}
|
||||
|
||||
// Group by email to prevent duplicates from affecting this
|
||||
|
||||
if (false === $limit and $integrationEntity) {
|
||||
$q->groupBy('i.integration_entity')->having('total');
|
||||
}
|
||||
if ($limit) {
|
||||
$q->setMaxResults($limit);
|
||||
}
|
||||
|
||||
$results = $q->executeQuery()->fetchAllAssociative();
|
||||
|
||||
$leads = [];
|
||||
|
||||
if ($integrationEntity) {
|
||||
foreach ($integrationEntity as $entity) {
|
||||
$leads[$entity] = (false === $limit) ? 0 : [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($results as $result) {
|
||||
if ($integrationEntity) {
|
||||
if (false === $limit) {
|
||||
$leads[$result['integration_entity']] = (int) $result['total'];
|
||||
} else {
|
||||
$leads[$result['integration_entity']][$result['internal_entity_id']] = $result;
|
||||
}
|
||||
} else {
|
||||
$leads[$result['internal_entity_id']] = $result['internal_entity_id'];
|
||||
}
|
||||
}
|
||||
|
||||
return $leads;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
*
|
||||
* @return array|int
|
||||
*/
|
||||
public function findLeadsToCreate($integration, $leadFields, $limit = 25, $fromDate = null, $toDate = null, $internalEntity = 'lead')
|
||||
{
|
||||
if ('company' == $internalEntity) {
|
||||
$joinTable = 'companies';
|
||||
} else {
|
||||
$joinTable = 'leads';
|
||||
}
|
||||
$q = $this->_em->getConnection()->createQueryBuilder()
|
||||
->from(MAUTIC_TABLE_PREFIX.$joinTable, 'l');
|
||||
|
||||
if (false === $limit) {
|
||||
$q->select('count(*) as total');
|
||||
} else {
|
||||
$q->select('l.id as internal_entity_id,'.$leadFields);
|
||||
}
|
||||
if ('company' == $internalEntity) {
|
||||
$q->where('not exists (select null from '.MAUTIC_TABLE_PREFIX
|
||||
.'integration_entity i where i.integration = :integration and i.internal_entity LIKE "'.$internalEntity.'%" and i.internal_entity_id = l.id)')
|
||||
->setParameter('integration', $integration);
|
||||
} else {
|
||||
$q->where('l.date_identified is not null')
|
||||
->andWhere(
|
||||
'not exists (select null from '.MAUTIC_TABLE_PREFIX
|
||||
.'integration_entity i where i.integration = :integration and i.internal_entity LIKE "'.$internalEntity.'%" and i.internal_entity_id = l.id)'
|
||||
)
|
||||
->setParameter('integration', $integration);
|
||||
}
|
||||
|
||||
if ('company' == $internalEntity) {
|
||||
$q->andWhere('l.companyname is not null');
|
||||
} else {
|
||||
$q->andWhere('l.email is not null');
|
||||
}
|
||||
if ($limit) {
|
||||
$q->setMaxResults($limit);
|
||||
}
|
||||
|
||||
if ($fromDate) {
|
||||
if ($toDate) {
|
||||
$q->andWhere(
|
||||
$q->expr()->or(
|
||||
$q->expr()->and(
|
||||
$q->expr()->isNotNull('l.date_modified'),
|
||||
$q->expr()->comparison('l.date_modified', 'BETWEEN', ':dateFrom and :dateTo')
|
||||
),
|
||||
$q->expr()->and(
|
||||
$q->expr()->isNull('l.date_modified'),
|
||||
$q->expr()->comparison('l.date_added', 'BETWEEN', ':dateFrom and :dateTo')
|
||||
)
|
||||
)
|
||||
)
|
||||
->setParameter('dateFrom', $fromDate)
|
||||
->setParameter('dateTo', $toDate);
|
||||
} else {
|
||||
$q->andWhere(
|
||||
$q->expr()->or(
|
||||
$q->expr()->and(
|
||||
$q->expr()->isNotNull('l.date_modified'),
|
||||
$q->expr()->gte('l.date_modified', ':dateFrom')
|
||||
),
|
||||
$q->expr()->and(
|
||||
$q->expr()->isNull('l.date_modified'),
|
||||
$q->expr()->gte('l.date_added', ':dateFrom')
|
||||
)
|
||||
)
|
||||
)
|
||||
->setParameter('dateFrom', $fromDate);
|
||||
}
|
||||
} elseif ($toDate) {
|
||||
$q->andWhere(
|
||||
$q->expr()->or(
|
||||
$q->expr()->and(
|
||||
$q->expr()->isNotNull('l.date_modified'),
|
||||
$q->expr()->lte('l.date_modified', ':dateTo')
|
||||
),
|
||||
$q->expr()->and(
|
||||
$q->expr()->isNull('l.date_modified'),
|
||||
$q->expr()->lte('l.date_added', ':dateTo')
|
||||
)
|
||||
)
|
||||
)
|
||||
->setParameter('dateTo', $toDate);
|
||||
}
|
||||
|
||||
$results = $q->executeQuery()->fetchAllAssociative();
|
||||
|
||||
if (false === $limit) {
|
||||
return (int) $results[0]['total'];
|
||||
}
|
||||
|
||||
$leads = [];
|
||||
foreach ($results as $result) {
|
||||
$leads[$result['internal_entity_id']] = $result;
|
||||
}
|
||||
|
||||
return $leads;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getIntegrationEntityCount($leadId, $integration = null, $integrationEntity = null, $internalEntity = null)
|
||||
{
|
||||
return $this->getIntegrationEntityByLead($leadId, $integration, $integrationEntity, $internalEntity, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|bool $limit
|
||||
*
|
||||
* @return array|int
|
||||
*/
|
||||
public function getIntegrationEntityByLead($leadId, $integration = null, $integrationEntity = null, $internalEntity = null, $limit = 100)
|
||||
{
|
||||
$q = $this->_em->getConnection()->createQueryBuilder()
|
||||
->from(MAUTIC_TABLE_PREFIX.'integration_entity', 'i');
|
||||
|
||||
if (false === $limit) {
|
||||
$q->select('count(*) as total');
|
||||
} else {
|
||||
$q->select('i.integration, i.integration_entity, i.integration_entity_id, i.date_added, i.last_sync_date, i.internal');
|
||||
}
|
||||
|
||||
$q->where('i.internal not like \'%error%\' and i.integration_entity_id is not null');
|
||||
$q->orderBy('i.last_sync_date', 'DESC');
|
||||
|
||||
if (empty($integration)) {
|
||||
// get list of published integrations
|
||||
$pq = $this->_em->getConnection()->createQueryBuilder()
|
||||
->select('p.name')
|
||||
->from(MAUTIC_TABLE_PREFIX.'plugin_integration_settings', 'p')
|
||||
->where('p.is_published = 1');
|
||||
$rows = $pq->executeQuery()->fetchAllAssociative();
|
||||
$plugins = array_map(static fn ($i): string => "'{$i['name']}'", $rows);
|
||||
if (count($plugins) > 0) {
|
||||
$q->andWhere($q->expr()->in('i.integration', $plugins));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
$q->andWhere($q->expr()->eq('i.integration', ':integration'));
|
||||
$q->setParameter('integration', $integration);
|
||||
}
|
||||
|
||||
$q->andWhere(
|
||||
$q->expr()->and(
|
||||
"i.internal_entity='lead'",
|
||||
$q->expr()->eq('i.internal_entity_id', ':internalEntityId')
|
||||
)
|
||||
);
|
||||
|
||||
$q->setParameter('internalEntityId', $leadId);
|
||||
|
||||
if (!empty($internalEntity)) {
|
||||
$q->andWhere($q->expr()->eq('i.internalEntity', ':internalEntity'));
|
||||
$q->setParameter('internalEntity', $internalEntity);
|
||||
}
|
||||
|
||||
if (!empty($integrationEntity)) {
|
||||
$q->andWhere($q->expr()->eq('i.integrationEntity', ':integrationEntity'));
|
||||
$q->setParameter('integrationEntity', $integrationEntity);
|
||||
}
|
||||
|
||||
$results = $q->executeQuery()->fetchAllAssociative();
|
||||
|
||||
if (false === $limit && count($results) > 0) {
|
||||
return (int) $results[0]['total'];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function markAsDeleted(array $integrationIds, $integration, $internalEntityType): void
|
||||
{
|
||||
$q = $this->_em->getConnection()->createQueryBuilder();
|
||||
$q->update(MAUTIC_TABLE_PREFIX.'integration_entity')
|
||||
->set('internal_entity', ':entity')
|
||||
->where(
|
||||
$q->expr()->and(
|
||||
$q->expr()->eq('integration', ':integration'),
|
||||
$q->expr()->in('integration_entity_id', array_map([$q->expr(), 'literal'], $integrationIds))
|
||||
)
|
||||
)
|
||||
->setParameter('integration', $integration)
|
||||
->setParameter('entity', $internalEntityType.'-deleted')
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
public function findLeadsToDelete($internalEntity, $leadId): void
|
||||
{
|
||||
$q = $this->_em->getConnection()->createQueryBuilder()
|
||||
->delete(MAUTIC_TABLE_PREFIX.'integration_entity')
|
||||
->from(MAUTIC_TABLE_PREFIX.'integration_entity');
|
||||
|
||||
$q->where('internal_entity_id = :leadId')
|
||||
->andWhere($q->expr()->like('internal_entity', ':internalEntity'))
|
||||
->setParameter('leadId', $leadId)
|
||||
->setParameter('internalEntity', $internalEntity)
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
public function updateErrorLeads($internalEntity, $leadId): void
|
||||
{
|
||||
$q = $this->_em->getConnection()->createQueryBuilder()
|
||||
->update(MAUTIC_TABLE_PREFIX.'integration_entity')
|
||||
->set('internal_entity', ':lead')->setParameter('lead', 'lead');
|
||||
|
||||
$q->where('internal_entity_id = :leadId')
|
||||
->andWhere($q->expr()->isNotNull('integration_entity_id'))
|
||||
->andWhere($q->expr()->eq('internal_entity', ':internalEntity'))
|
||||
->setParameter('leadId', $leadId)
|
||||
->setParameter('internalEntity', $internalEntity)
|
||||
->executeStatement();
|
||||
|
||||
$z = $this->_em->getConnection()->createQueryBuilder()
|
||||
->delete(MAUTIC_TABLE_PREFIX.'integration_entity')
|
||||
->from(MAUTIC_TABLE_PREFIX.'integration_entity');
|
||||
|
||||
$z->where('internal_entity_id = :leadId')
|
||||
->andWhere($q->expr()->isNull('integration_entity_id'))
|
||||
->andWhere($q->expr()->like('internal_entity', ':internalEntity'))
|
||||
->setParameter('leadId', $leadId)
|
||||
->setParameter('internalEntity', $internalEntity)
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Query;
|
||||
use Mautic\CoreBundle\Cache\ResultCacheHelper;
|
||||
use Mautic\CoreBundle\Cache\ResultCacheOptions;
|
||||
use Mautic\CoreBundle\Entity\CommonRepository;
|
||||
|
||||
/**
|
||||
* @extends CommonRepository<Integration>
|
||||
*/
|
||||
class IntegrationRepository extends CommonRepository
|
||||
{
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getIntegrations(): array
|
||||
{
|
||||
$query = $this->createQueryBuilder('i')
|
||||
->join('i.plugin', 'p')
|
||||
->getQuery();
|
||||
$this->enableCache($query);
|
||||
|
||||
$services = $query->getResult();
|
||||
|
||||
$results = [];
|
||||
foreach ($services as $s) {
|
||||
$results[$s->getName()] = $s;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get core (no plugin) integrations.
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getCoreIntegrations(): array
|
||||
{
|
||||
$query = $this->createQueryBuilder('i')
|
||||
->getQuery();
|
||||
$this->enableCache($query);
|
||||
|
||||
$services = $query->getResult();
|
||||
|
||||
$results = [];
|
||||
foreach ($services as $s) {
|
||||
$results[$s->getName()] = $s;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function findOneByName(string $name): ?Integration
|
||||
{
|
||||
$query = $this->createQueryBuilder('i')
|
||||
->where('i.name = :name')
|
||||
->setParameter('name', $name)
|
||||
->setMaxResults(1)
|
||||
->getQuery();
|
||||
$this->enableCache($query);
|
||||
|
||||
return $query->getOneOrNullResult();
|
||||
}
|
||||
|
||||
private function enableCache(Query $query): void
|
||||
{
|
||||
ResultCacheHelper::enableOrmQueryCache($query, new ResultCacheOptions(Integration::CACHE_NAMESPACE));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
|
||||
use Mautic\CoreBundle\Entity\CacheInvalidateInterface;
|
||||
use Mautic\CoreBundle\Entity\CommonEntity;
|
||||
|
||||
class Plugin extends CommonEntity implements CacheInvalidateInterface
|
||||
{
|
||||
public const DESCRIPTION_DELIMITER_REGEX = "/\R---\R/";
|
||||
public const CACHE_NAMESPACE = 'Plugin';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $primaryDescription;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $secondaryDescription;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isMissing = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $bundle;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $version;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $author;
|
||||
|
||||
/**
|
||||
* @var ArrayCollection<int, Integration>
|
||||
*/
|
||||
private $integrations;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->integrations = new ArrayCollection();
|
||||
}
|
||||
|
||||
public static function loadMetadata(ORM\ClassMetadata $metadata): void
|
||||
{
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('plugins')
|
||||
->setCustomRepositoryClass(PluginRepository::class)
|
||||
->addUniqueConstraint(['bundle'], 'unique_bundle');
|
||||
|
||||
$builder->addIdColumns();
|
||||
|
||||
$builder->createField('isMissing', 'boolean')
|
||||
->columnName('is_missing')
|
||||
->build();
|
||||
|
||||
$builder->createField('bundle', 'string')
|
||||
->length(50)
|
||||
->build();
|
||||
|
||||
$builder->createField('version', 'string')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('author', 'string')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createOneToMany('integrations', 'Integration')
|
||||
->setIndexBy('id')
|
||||
->mappedBy('plugin')
|
||||
->fetchExtraLazy()
|
||||
->build();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->id = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return Plugin
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $bundle
|
||||
*/
|
||||
public function setBundle($bundle): void
|
||||
{
|
||||
$this->bundle = $bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBundle()
|
||||
{
|
||||
return $this->bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getIntegrations()
|
||||
{
|
||||
return $this->integrations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $description
|
||||
*/
|
||||
public function setDescription($description): void
|
||||
{
|
||||
$this->description = $description;
|
||||
$this->splitDescriptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPrimaryDescription()
|
||||
{
|
||||
return $this->primaryDescription ?: $this->description;
|
||||
}
|
||||
|
||||
public function hasSecondaryDescription(): bool
|
||||
{
|
||||
return $this->description && preg_match(self::DESCRIPTION_DELIMITER_REGEX, $this->description) >= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getSecondaryDescription()
|
||||
{
|
||||
return $this->secondaryDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $version
|
||||
*/
|
||||
public function setVersion($version): void
|
||||
{
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getIsMissing()
|
||||
{
|
||||
return $this->isMissing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $isMissing
|
||||
*/
|
||||
public function setIsMissing($isMissing): void
|
||||
{
|
||||
$this->isMissing = $isMissing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAuthor()
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $author
|
||||
*/
|
||||
public function setAuthor($author): void
|
||||
{
|
||||
$this->author = $author;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits description into primary and secondary.
|
||||
*/
|
||||
public function splitDescriptions(): void
|
||||
{
|
||||
if ($this->hasSecondaryDescription()) {
|
||||
$parts = preg_split(self::DESCRIPTION_DELIMITER_REGEX, $this->description);
|
||||
$this->primaryDescription = trim($parts[0]);
|
||||
$this->secondaryDescription = trim($parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
public function getCacheNamespacesToDelete(): array
|
||||
{
|
||||
return [
|
||||
self::CACHE_NAMESPACE,
|
||||
Integration::CACHE_NAMESPACE,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Entity;
|
||||
|
||||
use Mautic\CoreBundle\Entity\CommonRepository;
|
||||
|
||||
/**
|
||||
* @extends CommonRepository<Plugin>
|
||||
*/
|
||||
class PluginRepository extends CommonRepository
|
||||
{
|
||||
/**
|
||||
* Find an addon record by bundle name.
|
||||
*
|
||||
* @param string $bundle
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Doctrine\ORM\NonUniqueResultException
|
||||
*/
|
||||
public function findByBundle($bundle)
|
||||
{
|
||||
$q = $this->createQueryBuilder($this->getTableAlias());
|
||||
$q->where($q->expr()->eq('p.bundle', ':bundle'))
|
||||
->setParameter('bundle', $bundle);
|
||||
|
||||
return $q->getQuery()->getOneOrNullResult();
|
||||
}
|
||||
|
||||
public function getEntities(array $args = [])
|
||||
{
|
||||
$q = $this->_em->createQueryBuilder();
|
||||
$q->select($this->getTableAlias())
|
||||
->from(Plugin::class, $this->getTableAlias(), (!empty($args['index'])) ? $this->getTableAlias().'.'.$args['index'] : $this->getTableAlias().'.id');
|
||||
|
||||
$args['qb'] = $q;
|
||||
$args['ignore_paginator'] = true;
|
||||
|
||||
return parent::getEntities($args);
|
||||
}
|
||||
|
||||
protected function getDefaultOrder(): array
|
||||
{
|
||||
return [
|
||||
['p.name', 'ASC'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getTableAlias(): string
|
||||
{
|
||||
return 'p';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Integration\AbstractIntegration;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class AbstractPluginIntegrationEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @var AbstractIntegration
|
||||
*/
|
||||
protected $integration;
|
||||
|
||||
/**
|
||||
* Get the integration's name.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getIntegrationName()
|
||||
{
|
||||
return $this->integration->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the integration object.
|
||||
*
|
||||
* @return AbstractIntegration
|
||||
*/
|
||||
public function getIntegration()
|
||||
{
|
||||
return $this->integration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Event;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Mautic\PluginBundle\Entity\Plugin;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class PluginInstallEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @param array<class-string, ClassMetadata>|null $metadata
|
||||
*/
|
||||
public function __construct(
|
||||
private Plugin $plugin,
|
||||
private ?array $metadata,
|
||||
private ?bool $installedSchema,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getPlugin(): Plugin
|
||||
{
|
||||
return $this->plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<class-string, ClassMetadata>|null
|
||||
*/
|
||||
public function getMetadata(): ?array
|
||||
{
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
public function getInstalledSchema(): ?bool
|
||||
{
|
||||
return $this->installedSchema;
|
||||
}
|
||||
|
||||
public function checkContext(string $pluginName): bool
|
||||
{
|
||||
return $pluginName === $this->plugin->getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Integration\UnifiedIntegrationInterface;
|
||||
|
||||
class PluginIntegrationAuthCallbackUrlEvent extends AbstractPluginIntegrationEvent
|
||||
{
|
||||
/**
|
||||
* @param string $callbackUrl
|
||||
*/
|
||||
public function __construct(
|
||||
UnifiedIntegrationInterface $integration,
|
||||
private $callbackUrl,
|
||||
) {
|
||||
$this->integration = $integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCallbackUrl()
|
||||
{
|
||||
return $this->callbackUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $callbackUrl
|
||||
*/
|
||||
public function setCallbackUrl($callbackUrl): void
|
||||
{
|
||||
$this->callbackUrl = $callbackUrl;
|
||||
|
||||
$this->stopPropagation();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Integration\UnifiedIntegrationInterface;
|
||||
|
||||
class PluginIntegrationAuthRedirectEvent extends AbstractPluginIntegrationEvent
|
||||
{
|
||||
/**
|
||||
* @param string $authUrl
|
||||
*/
|
||||
public function __construct(
|
||||
UnifiedIntegrationInterface $integration,
|
||||
private $authUrl,
|
||||
) {
|
||||
$this->integration = $integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthUrl()
|
||||
{
|
||||
return $this->authUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $authUrl
|
||||
*/
|
||||
public function setAuthUrl($authUrl): void
|
||||
{
|
||||
$this->authUrl = $authUrl;
|
||||
|
||||
$this->stopPropagation();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Mautic\PluginBundle\Integration\UnifiedIntegrationInterface;
|
||||
|
||||
class PluginIntegrationEvent extends AbstractPluginIntegrationEvent
|
||||
{
|
||||
public function __construct(UnifiedIntegrationInterface $integration)
|
||||
{
|
||||
$this->integration = $integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Integration
|
||||
*/
|
||||
public function getEntity()
|
||||
{
|
||||
return $this->integration->getIntegrationSettings();
|
||||
}
|
||||
|
||||
public function setEntity(Integration $integration): void
|
||||
{
|
||||
$this->integration->setIntegrationSettings($integration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Integration\UnifiedIntegrationInterface;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class PluginIntegrationFormBuildEvent extends AbstractPluginIntegrationEvent
|
||||
{
|
||||
public function __construct(
|
||||
UnifiedIntegrationInterface $integration,
|
||||
private FormBuilderInterface $builder,
|
||||
private array $options,
|
||||
) {
|
||||
$this->integration = $integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FormBuilderInterface
|
||||
*/
|
||||
public function getFormBuilder()
|
||||
{
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Integration\UnifiedIntegrationInterface;
|
||||
|
||||
class PluginIntegrationFormDisplayEvent extends AbstractPluginIntegrationEvent
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $settings
|
||||
*/
|
||||
public function __construct(
|
||||
UnifiedIntegrationInterface $integration,
|
||||
private array $settings,
|
||||
) {
|
||||
$this->integration = $integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSettings()
|
||||
{
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
public function setSettings(array $settings): void
|
||||
{
|
||||
$this->settings = $settings;
|
||||
|
||||
$this->stopPropagation();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Integration\UnifiedIntegrationInterface;
|
||||
|
||||
class PluginIntegrationKeyEvent extends AbstractPluginIntegrationEvent
|
||||
{
|
||||
public function __construct(
|
||||
UnifiedIntegrationInterface $integration,
|
||||
private ?array $keys = null,
|
||||
) {
|
||||
$this->integration = $integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the keys array.
|
||||
*/
|
||||
public function getKeys()
|
||||
{
|
||||
return $this->keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new keys array.
|
||||
*/
|
||||
public function setKeys(array $keys): void
|
||||
{
|
||||
$this->keys = $keys;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Event;
|
||||
|
||||
use Mautic\PluginBundle\Integration\UnifiedIntegrationInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class PluginIntegrationRequestEvent extends AbstractPluginIntegrationEvent
|
||||
{
|
||||
private ?ResponseInterface $response = null;
|
||||
|
||||
/**
|
||||
* @param mixed[] $parameters
|
||||
* @param string $method
|
||||
* @param mixed[] $settings
|
||||
* @param string $authType
|
||||
*/
|
||||
public function __construct(
|
||||
UnifiedIntegrationInterface $integration,
|
||||
private $url,
|
||||
private $parameters,
|
||||
private $headers,
|
||||
private $method,
|
||||
private $settings,
|
||||
private $authType,
|
||||
) {
|
||||
$this->integration = $integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function setParameters(array $parameters): void
|
||||
{
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSettings()
|
||||
{
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthType()
|
||||
{
|
||||
return $this->authType;
|
||||
}
|
||||
|
||||
public function setResponse(ResponseInterface $response): void
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function getResponse(): ResponseInterface
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getHeaders()
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function setHeaders(array $headers): void
|
||||
{
|
||||
$this->headers = $headers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Event;
|
||||
|
||||
class PluginIsPublishedEvent extends \Symfony\Contracts\EventDispatcher\Event
|
||||
{
|
||||
private string $message = '';
|
||||
private bool $canPublish = true;
|
||||
|
||||
public function __construct(private int $value, private string $integrationName)
|
||||
{
|
||||
}
|
||||
|
||||
public function getValue(): int
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getMessage(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function setMessage(string $message): void
|
||||
{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
public function isCanPublish(): bool
|
||||
{
|
||||
return $this->canPublish;
|
||||
}
|
||||
|
||||
public function setCanPublish(bool $canPublish): void
|
||||
{
|
||||
$this->canPublish = $canPublish;
|
||||
}
|
||||
|
||||
public function getIntegrationName(): string
|
||||
{
|
||||
return $this->integrationName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Event;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Mautic\PluginBundle\Entity\Plugin;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class PluginUpdateEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @param array<class-string, ClassMetadata> $metadata
|
||||
*/
|
||||
public function __construct(
|
||||
private Plugin $plugin,
|
||||
private string $oldVersion,
|
||||
private array $metadata,
|
||||
private ?Schema $installedSchema,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getPlugin(): Plugin
|
||||
{
|
||||
return $this->plugin;
|
||||
}
|
||||
|
||||
public function getOldVersion(): string
|
||||
{
|
||||
return $this->oldVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<class-string, ClassMetadata>
|
||||
*/
|
||||
public function getMetadata(): array
|
||||
{
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
public function getInstalledSchema(): ?Schema
|
||||
{
|
||||
return $this->installedSchema;
|
||||
}
|
||||
|
||||
public function checkContext(string $pluginName): bool
|
||||
{
|
||||
return $pluginName === $this->plugin->getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\EventListener;
|
||||
|
||||
use Mautic\CampaignBundle\CampaignEvents;
|
||||
use Mautic\CampaignBundle\Event\CampaignBuilderEvent;
|
||||
use Mautic\CampaignBundle\Event\CampaignExecutionEvent;
|
||||
use Mautic\PluginBundle\Form\Type\IntegrationsListType;
|
||||
use Mautic\PluginBundle\PluginEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class CampaignSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
use PushToIntegrationTrait;
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
CampaignEvents::CAMPAIGN_ON_BUILD => ['onCampaignBuild', 0],
|
||||
PluginEvents::ON_CAMPAIGN_TRIGGER_ACTION => ['onCampaignTriggerAction', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function onCampaignBuild(CampaignBuilderEvent $event): void
|
||||
{
|
||||
$action = [
|
||||
'label' => 'mautic.plugin.actions.push_lead',
|
||||
'description' => 'mautic.plugin.actions.tooltip',
|
||||
'formType' => IntegrationsListType::class,
|
||||
'formTheme' => '@MauticPlugin/FormTheme/Integration/layout.html.twig',
|
||||
'eventName' => PluginEvents::ON_CAMPAIGN_TRIGGER_ACTION,
|
||||
];
|
||||
|
||||
$event->addAction('plugin.leadpush', $action);
|
||||
}
|
||||
|
||||
public function onCampaignTriggerAction(CampaignExecutionEvent $event): void
|
||||
{
|
||||
$config = $event->getConfig();
|
||||
$config['campaignEvent'] = $event->getEvent();
|
||||
$config['leadEventLog'] = $event->getLogEntry();
|
||||
$lead = $event->getLead();
|
||||
$errors = [];
|
||||
$success = $this->pushToIntegration($config, $lead, $errors);
|
||||
|
||||
if (count($errors)) {
|
||||
$log = $event->getLogEntry();
|
||||
$log->appendToMetadata(
|
||||
[
|
||||
'failed' => 1,
|
||||
'reason' => implode('<br />', $errors),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$event->setResult($success);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\EventListener;
|
||||
|
||||
use Mautic\FormBundle\Event\FormBuilderEvent;
|
||||
use Mautic\FormBundle\Event\SubmissionEvent;
|
||||
use Mautic\FormBundle\FormEvents;
|
||||
use Mautic\PluginBundle\Form\Type\IntegrationsListType;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class FormSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
use PushToIntegrationTrait;
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
FormEvents::FORM_ON_BUILD => ['onFormBuild', 0],
|
||||
FormEvents::ON_EXECUTE_SUBMIT_ACTION => ['onFormSubmitActionTriggered', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function onFormBuild(FormBuilderEvent $event): void
|
||||
{
|
||||
$event->addSubmitAction('plugin.leadpush', [
|
||||
'group' => 'mautic.plugin.actions',
|
||||
'description' => 'mautic.plugin.actions.tooltip',
|
||||
'label' => 'mautic.plugin.actions.push_lead',
|
||||
'formType' => IntegrationsListType::class,
|
||||
'formTheme' => '@MauticPlugin/FormTheme/Integration/layout.html.twig',
|
||||
'eventName' => FormEvents::ON_EXECUTE_SUBMIT_ACTION,
|
||||
'template' => '@MauticPlugin/Action/integration.html.twig',
|
||||
]);
|
||||
}
|
||||
|
||||
public function onFormSubmitActionTriggered(SubmissionEvent $event): void
|
||||
{
|
||||
if (false === $event->checkContext('plugin.leadpush')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pushToIntegration($event->getActionConfig(), $event->getSubmission()->getLead());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\EventListener;
|
||||
|
||||
use Mautic\PluginBundle\Event\PluginIntegrationRequestEvent;
|
||||
use Mautic\PluginBundle\Helper\oAuthHelper;
|
||||
use Mautic\PluginBundle\PluginEvents;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* This class can provide useful debugging information for API requests and responses.
|
||||
* The information is displayed when a command is executed from the console and the -vv flag is passed to it.
|
||||
*/
|
||||
class IntegrationSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
PluginEvents::PLUGIN_ON_INTEGRATION_RESPONSE => ['onResponse', 0],
|
||||
PluginEvents::PLUGIN_ON_INTEGRATION_REQUEST => ['onRequest', 0],
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* Request event
|
||||
*/
|
||||
public function onRequest(PluginIntegrationRequestEvent $event): void
|
||||
{
|
||||
$name = strtoupper($event->getIntegrationName());
|
||||
$headers = var_export($event->getHeaders(), true);
|
||||
$params = var_export($event->getParameters(), true);
|
||||
$settings = var_export($event->getSettings(), true);
|
||||
|
||||
if (defined('IN_MAUTIC_CONSOLE') && defined('MAUTIC_CONSOLE_VERBOSITY')
|
||||
&& MAUTIC_CONSOLE_VERBOSITY >= ConsoleOutput::VERBOSITY_VERY_VERBOSE) {
|
||||
$output = new ConsoleOutput();
|
||||
$output->writeln('<fg=magenta>REQUEST:</>');
|
||||
$output->writeln('<fg=white>'.$event->getMethod().' '.$event->getUrl().'</>');
|
||||
$output->writeln('<fg=cyan>'.$headers.'</>');
|
||||
$output->writeln('');
|
||||
$output->writeln('<fg=cyan>'.$params.'</>');
|
||||
$output->writeln('');
|
||||
$output->writeln('<fg=cyan>'.$settings.'</>');
|
||||
} else {
|
||||
$this->logger->debug("$name REQUEST URL: ".$event->getMethod().' '.$event->getUrl());
|
||||
if ('' !== $headers) {
|
||||
$hashedHeaders = oAuthHelper::sanitizeHeaderData($event->getHeaders());
|
||||
$headers = var_export($hashedHeaders, true);
|
||||
$this->logger->debug("$name REQUEST HEADERS: \n".$headers.PHP_EOL);
|
||||
}
|
||||
if ('' !== $params) {
|
||||
$this->logger->debug("$name REQUEST PARAMS: \n".$params.PHP_EOL);
|
||||
}
|
||||
if ('' !== $settings) {
|
||||
$this->logger->debug("$name REQUEST SETTINGS: \n".$settings.PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Response event
|
||||
*/
|
||||
public function onResponse(PluginIntegrationRequestEvent $event): void
|
||||
{
|
||||
$response = $event->getResponse();
|
||||
$headers = var_export($response->getHeaders(), true);
|
||||
$name = strtoupper($event->getIntegrationName());
|
||||
$isJson = isset($response->getHeaders()['Content-Type']) && preg_grep('/application\/json/', $response->getHeaders()['Content-Type']);
|
||||
$json = $isJson ? str_replace(' ', ' ', json_encode(json_decode($response->getBody()), JSON_PRETTY_PRINT)) : '';
|
||||
$xml = '';
|
||||
$isXml = isset($response->getHeaders()['Content-Type']) && preg_grep('/text\/xml/', $response->getHeaders()['Content-Type']);
|
||||
if ($isXml) {
|
||||
$doc = new \DOMDocument('1.0');
|
||||
$doc->preserveWhiteSpace = false;
|
||||
$doc->formatOutput = true;
|
||||
$doc->loadXML($response->getBody());
|
||||
$xml = $doc->saveXML();
|
||||
}
|
||||
|
||||
if (defined('IN_MAUTIC_CONSOLE') && defined('MAUTIC_CONSOLE_VERBOSITY')
|
||||
&& MAUTIC_CONSOLE_VERBOSITY >= ConsoleOutput::VERBOSITY_VERY_VERBOSE) {
|
||||
$output = new ConsoleOutput();
|
||||
$output->writeln(sprintf('<fg=magenta>RESPONSE: %d</>', $response->getStatusCode()));
|
||||
$output->writeln('<fg=cyan>'.$headers.'</>');
|
||||
$output->writeln('');
|
||||
|
||||
if ($isJson) {
|
||||
$output->writeln('<fg=cyan>'.$json.'</>');
|
||||
} elseif ($isXml) {
|
||||
$output->writeln('<fg=cyan>'.$xml.'</>');
|
||||
} else {
|
||||
$output->writeln('<fg=cyan>'.$response->getBody().'</>');
|
||||
}
|
||||
} else {
|
||||
$this->logger->debug("$name RESPONSE CODE: {$response->getStatusCode()}");
|
||||
if ('' !== $headers) {
|
||||
$this->logger->debug("$name RESPONSE HEADERS: \n".$headers.PHP_EOL);
|
||||
}
|
||||
if ('' !== $json || '' !== $xml || '' !== $response->getBody()) {
|
||||
$body = "$name RESPONSE BODY: ";
|
||||
if ($isJson) {
|
||||
$body .= $json;
|
||||
} elseif ($isXml) {
|
||||
$body .= $xml;
|
||||
} else {
|
||||
$body = $response->getBody();
|
||||
}
|
||||
|
||||
$this->logger->debug($body);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\EventListener;
|
||||
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Event\CompanyEvent;
|
||||
use Mautic\LeadBundle\Event\LeadEvent;
|
||||
use Mautic\LeadBundle\LeadEvents;
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Mautic\PluginBundle\Entity\IntegrationRepository;
|
||||
use Mautic\PluginBundle\Model\PluginModel;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class LeadSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
private const FEATURE_PUSH_LEAD = 'push_lead';
|
||||
|
||||
public function __construct(
|
||||
private PluginModel $pluginModel,
|
||||
private IntegrationRepository $integrationRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
LeadEvents::LEAD_PRE_DELETE => ['onLeadDelete', 0],
|
||||
LeadEvents::LEAD_POST_SAVE => ['onLeadSave', 0],
|
||||
LeadEvents::COMPANY_PRE_DELETE => ['onCompanyDelete', 0],
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete lead event
|
||||
*/
|
||||
public function onLeadDelete(LeadEvent $event): bool
|
||||
{
|
||||
/** @var Lead $lead */
|
||||
$lead = $event->getLead();
|
||||
$integrationEntityRepo = $this->pluginModel->getIntegrationEntityRepository();
|
||||
$integrationEntityRepo->findLeadsToDelete('lead%', $lead->getId());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete company event
|
||||
*/
|
||||
public function onCompanyDelete(CompanyEvent $event): bool
|
||||
{
|
||||
/** @var \Mautic\LeadBundle\Entity\Company $company */
|
||||
$company = $event->getCompany();
|
||||
$integrationEntityRepo = $this->pluginModel->getIntegrationEntityRepository();
|
||||
$integrationEntityRepo->findLeadsToDelete('company%', $company->getId());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Change lead event
|
||||
*/
|
||||
public function onLeadSave(LeadEvent $event): void
|
||||
{
|
||||
/** @var Lead $lead */
|
||||
$lead = $event->getLead();
|
||||
$integrationEntityRepo = $this->pluginModel->getIntegrationEntityRepository();
|
||||
if ($this->isAnyIntegrationEnabled()) {
|
||||
$integrationEntityRepo->updateErrorLeads('lead-error', $lead->getId());
|
||||
}
|
||||
}
|
||||
|
||||
private function isAnyIntegrationEnabled(): bool
|
||||
{
|
||||
$integrations = $this->integrationRepository->getIntegrations();
|
||||
foreach ($integrations as $integration) {
|
||||
/** @var Integration $integration */
|
||||
$supportedFeatures = $integration->getSupportedFeatures();
|
||||
|
||||
if ($integration->getIsPublished() && !empty($integration->getApiKeys()) && in_array(self::FEATURE_PUSH_LEAD, $supportedFeatures)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\EventListener;
|
||||
|
||||
use Mautic\PluginBundle\Bundle\PluginDatabase;
|
||||
use Mautic\PluginBundle\Event\PluginInstallEvent;
|
||||
use Mautic\PluginBundle\Event\PluginUpdateEvent;
|
||||
use Mautic\PluginBundle\PluginEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class PluginSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(private readonly PluginDatabase $pluginDatabase)
|
||||
{
|
||||
}
|
||||
|
||||
public function onInstall(PluginInstallEvent $event): void
|
||||
{
|
||||
$metadata = $event->getMetadata();
|
||||
|
||||
if (null === $metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pluginDatabase->installPluginSchema(
|
||||
$metadata,
|
||||
$event->getInstalledSchema()
|
||||
);
|
||||
}
|
||||
|
||||
public function onUpdate(PluginUpdateEvent $event): void
|
||||
{
|
||||
$this->pluginDatabase->onPluginUpdate($event->getPlugin());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
PluginEvents::ON_PLUGIN_INSTALL => ['onInstall', 0],
|
||||
PluginEvents::ON_PLUGIN_UPDATE => ['onUpdate', 0],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\EventListener;
|
||||
|
||||
use Mautic\PluginBundle\Form\Type\IntegrationsListType;
|
||||
use Mautic\PluginBundle\Helper\EventHelper;
|
||||
use Mautic\PointBundle\Event\TriggerBuilderEvent;
|
||||
use Mautic\PointBundle\PointEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class PointSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
PointEvents::TRIGGER_ON_BUILD => ['onTriggerBuild', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function onTriggerBuild(TriggerBuilderEvent $event): void
|
||||
{
|
||||
$action = [
|
||||
'group' => 'mautic.plugin.point.action',
|
||||
'label' => 'mautic.plugin.actions.push_lead',
|
||||
'formType' => IntegrationsListType::class,
|
||||
// 'formTheme' => 'MauticPluginBundle:FormTheme:Integration',
|
||||
'callback' => [EventHelper::class, 'pushLead'],
|
||||
];
|
||||
|
||||
$event->addEvent('plugin.leadpush', $action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\EventListener;
|
||||
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Mautic\PluginBundle\Integration\AbstractIntegration;
|
||||
|
||||
/**
|
||||
* Static methods must be used due to the Point triggers not being converted to Events yet
|
||||
* Once that happens, this can be converted to a standard method classes.
|
||||
*
|
||||
* Trait PushToIntegrationTrait
|
||||
*/
|
||||
trait PushToIntegrationTrait
|
||||
{
|
||||
/**
|
||||
* @var IntegrationHelper
|
||||
*/
|
||||
protected static $integrationHelper;
|
||||
|
||||
/**
|
||||
* Used by methodCalls to event subscribers.
|
||||
*/
|
||||
public function setIntegrationHelper(IntegrationHelper $integrationHelper): void
|
||||
{
|
||||
static::setStaticIntegrationHelper($integrationHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by callback methods such as point triggers.
|
||||
*/
|
||||
public static function setStaticIntegrationHelper(IntegrationHelper $integrationHelper): void
|
||||
{
|
||||
static::$integrationHelper = $integrationHelper;
|
||||
}
|
||||
|
||||
protected function pushToIntegration(array $config, Lead $lead, array &$errors = []): bool
|
||||
{
|
||||
return static::pushIt($config, $lead, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used because the the Point trigger actions have not be converted to Events yet and thus must leverage a callback.
|
||||
*/
|
||||
protected static function pushIt($config, $lead, &$errors): bool
|
||||
{
|
||||
$integration = (!empty($config['integration'])) ? $config['integration'] : null;
|
||||
$integrationCampaign = (!empty($config['config']['campaigns'])) ? $config['config']['campaigns'] : null;
|
||||
$integrationMemberStatus = (!empty($config['campaign_member_status']['campaign_member_status']))
|
||||
? $config['campaign_member_status']['campaign_member_status'] : null;
|
||||
$services = static::$integrationHelper->getIntegrationObjects($integration);
|
||||
$success = true;
|
||||
|
||||
foreach ($services as $s) {
|
||||
/** @var AbstractIntegration $s */
|
||||
$settings = $s->getIntegrationSettings();
|
||||
if (!$settings->isPublished()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$personIds = null;
|
||||
if (method_exists($s, 'pushLead')) {
|
||||
if (!$personIds = $s->resetLastIntegrationError()->pushLead($lead, $config)) {
|
||||
$success = false;
|
||||
if ($error = $s->getLastIntegrationError()) {
|
||||
$errors[] = $error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($success && $integrationCampaign && method_exists($s, 'pushLeadToCampaign')) {
|
||||
if (!$s->resetLastIntegrationError()->pushLeadToCampaign($lead, $integrationCampaign, $integrationMemberStatus)) {
|
||||
$success = false;
|
||||
if ($error = $s->getLastIntegrationError()) {
|
||||
$errors[] = $error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Exception;
|
||||
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
|
||||
class ApiErrorException extends \Exception
|
||||
{
|
||||
private $contactId;
|
||||
|
||||
private ?Lead $contact = null;
|
||||
|
||||
private string $shortMessage = '';
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
*/
|
||||
public function __construct($message = 'API error', $code = 0, ?\Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getContactId()
|
||||
{
|
||||
return $this->contactId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $contactId
|
||||
*
|
||||
* @return ApiErrorException
|
||||
*/
|
||||
public function setContactId($contactId)
|
||||
{
|
||||
$this->contactId = $contactId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Lead
|
||||
*/
|
||||
public function getContact()
|
||||
{
|
||||
return $this->contact;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ApiErrorException
|
||||
*/
|
||||
public function setContact(Lead $contact)
|
||||
{
|
||||
$this->contact = $contact;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getShortMessage(): string
|
||||
{
|
||||
return $this->shortMessage;
|
||||
}
|
||||
|
||||
public function setShortMessage(string $shortMessage): ApiErrorException
|
||||
{
|
||||
$this->shortMessage = $shortMessage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Facade;
|
||||
|
||||
use Mautic\PluginBundle\Helper\ReloadHelper;
|
||||
use Mautic\PluginBundle\Model\PluginModel;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ReloadFacade
|
||||
{
|
||||
public function __construct(
|
||||
private PluginModel $pluginModel,
|
||||
private ReloadHelper $reloadHelper,
|
||||
private TranslatorInterface $translator,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method finds all plugins that needs to be enabled, disabled, installed and updated
|
||||
* and do all those actions.
|
||||
*
|
||||
* Returns humanly understandable message about its doings.
|
||||
*/
|
||||
public function reloadPlugins(): string
|
||||
{
|
||||
$plugins = $this->pluginModel->getAllPluginsConfig();
|
||||
$pluginMetadata = $this->pluginModel->getPluginsMetadata();
|
||||
$installedPlugins = $this->pluginModel->getInstalledPlugins();
|
||||
$installedPluginTables = $this->pluginModel->getInstalledPluginTables($pluginMetadata);
|
||||
$installedPluginsSchemas = $this->pluginModel->createPluginSchemas($installedPluginTables);
|
||||
$disabledPlugins = $this->reloadHelper->disableMissingPlugins($plugins, $installedPlugins);
|
||||
$enabledPlugins = $this->reloadHelper->enableFoundPlugins($plugins, $installedPlugins);
|
||||
$updatedPlugins = $this->reloadHelper->updatePlugins($plugins, $installedPlugins, $pluginMetadata, $installedPluginsSchemas);
|
||||
$installedPlugins = $this->reloadHelper->installPlugins($plugins, $installedPlugins, $pluginMetadata, $installedPluginsSchemas);
|
||||
$persist = array_values((array) ($disabledPlugins + $enabledPlugins + $updatedPlugins + $installedPlugins));
|
||||
|
||||
$this->pluginModel->saveEntities($persist);
|
||||
|
||||
// Alert the user to the number of additions
|
||||
return $this->translator->trans(
|
||||
'mautic.plugin.notice.reloaded',
|
||||
[
|
||||
'%added%' => count($installedPlugins),
|
||||
'%disabled%' => count($disabledPlugins),
|
||||
'%updated%' => count($updatedPlugins),
|
||||
],
|
||||
'flashes'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Form\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
class CanPublish extends Constraint
|
||||
{
|
||||
public string $message = 'mautic.lead_list.not_allowed_plugin_publish';
|
||||
|
||||
public string $integrationName;
|
||||
|
||||
public function getDefaultOption(): string
|
||||
{
|
||||
return 'integrationName';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Form\Constraint;
|
||||
|
||||
use Mautic\PluginBundle\Event\PluginIsPublishedEvent;
|
||||
use Mautic\PluginBundle\PluginEvents;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
class CanPublishValidator extends ConstraintValidator
|
||||
{
|
||||
public function __construct(private EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
}
|
||||
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
if (1 !== $value) {
|
||||
return;
|
||||
}
|
||||
if (!$constraint instanceof CanPublish) {
|
||||
throw new \Symfony\Component\Validator\Exception\UnexpectedTypeException($constraint, CanPublish::class);
|
||||
}
|
||||
$event = new PluginIsPublishedEvent($value, $constraint->integrationName);
|
||||
$event = $this->eventDispatcher->dispatch($event, PluginEvents::PLUGIN_IS_PUBLISHED_STATE_CHANGING);
|
||||
|
||||
if (!$event->isCanPublish()) {
|
||||
$this->context->buildViolation($event->getMessage())
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class CompanyFieldsType extends AbstractType
|
||||
{
|
||||
use FieldsTypeTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->buildFormFields($builder, $options, $options['integration_fields'], $options['mautic_fields'], 'company', $options['limit'], $options['start']);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$this->configureFieldOptions($resolver, 'company');
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'integration_company_fields';
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
$this->buildFieldView($view, $options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Form\Type;
|
||||
|
||||
use Mautic\CoreBundle\Form\Type\FormButtonsType;
|
||||
use Mautic\CoreBundle\Form\Type\StandAloneButtonType;
|
||||
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Mautic\PluginBundle\Form\Constraint\CanPublish;
|
||||
use Mautic\PluginBundle\Integration\AbstractIntegration;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<Integration>
|
||||
*/
|
||||
class DetailsType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('isPublished', YesNoButtonGroupType::class, [
|
||||
'constraints' => [
|
||||
new CanPublish($options['integration'] ?? ''),
|
||||
],
|
||||
]);
|
||||
|
||||
/** @var AbstractIntegration $integrationObject */
|
||||
$integrationObject = $options['integration_object'];
|
||||
/** @var Integration $integration */
|
||||
$integration = $options['data'];
|
||||
$formSettings = $integrationObject->getFormDisplaySettings();
|
||||
$decryptedKeys = $integrationObject->decryptApiKeys($integration->getApiKeys());
|
||||
$keys = $integrationObject->getRequiredKeyFields();
|
||||
|
||||
if (!empty($formSettings['hide_keys'])) {
|
||||
foreach ($formSettings['hide_keys'] as $key) {
|
||||
unset($keys[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$builder->add(
|
||||
'apiKeys',
|
||||
KeysType::class,
|
||||
[
|
||||
'label' => false,
|
||||
'integration_keys' => $keys,
|
||||
'data' => $decryptedKeys,
|
||||
'integration_object' => $integrationObject,
|
||||
]
|
||||
);
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($keys, $decryptedKeys, $options): void {
|
||||
$data = $event->getData();
|
||||
$form = $event->getForm();
|
||||
|
||||
$form->add(
|
||||
'apiKeys',
|
||||
KeysType::class,
|
||||
[
|
||||
'label' => false,
|
||||
'integration_keys' => $keys,
|
||||
'data' => $decryptedKeys,
|
||||
'integration_object' => $options['integration_object'],
|
||||
'is_published' => (int) $data['isPublished'],
|
||||
]
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (!empty($formSettings['requires_authorization'])) {
|
||||
$label = ($integrationObject->isAuthorized()) ? 'reauthorize' : 'authorize';
|
||||
|
||||
$builder->add(
|
||||
'authButton',
|
||||
StandAloneButtonType::class,
|
||||
[
|
||||
'attr' => [
|
||||
'class' => 'btn btn-success btn-lg',
|
||||
'onclick' => 'Mautic.initiateIntegrationAuthorization()',
|
||||
'icon' => 'ri-key-2-line',
|
||||
],
|
||||
'label' => 'mautic.integration.form.'.$label,
|
||||
'disabled' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$features = $integrationObject->getSupportedFeatures();
|
||||
$tooltips = $integrationObject->getSupportedFeatureTooltips();
|
||||
if (!empty($features)) {
|
||||
// Check to see if the integration is a new entry and thus not configured
|
||||
$configured = null !== $integration->getId();
|
||||
$enabledFeatures = $integration->getSupportedFeatures();
|
||||
$data = ($configured) ? $enabledFeatures : $features;
|
||||
|
||||
$choices = [];
|
||||
foreach ($features as $f) {
|
||||
$choices['mautic.integration.form.feature.'.$f] = $f;
|
||||
}
|
||||
|
||||
$builder->add(
|
||||
'supportedFeatures',
|
||||
ChoiceType::class,
|
||||
[
|
||||
'choices' => $choices,
|
||||
'expanded' => true,
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'multiple' => true,
|
||||
'label' => 'mautic.integration.form.features',
|
||||
'required' => false,
|
||||
'data' => $data,
|
||||
'choice_attr' => function ($val) use ($tooltips): array {
|
||||
if (array_key_exists($val, $tooltips)) {
|
||||
return [
|
||||
'data-toggle' => 'tooltip',
|
||||
'title' => $tooltips[$val],
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$builder->add(
|
||||
'featureSettings',
|
||||
FeatureSettingsType::class,
|
||||
[
|
||||
'label' => 'mautic.integration.form.feature.settings',
|
||||
'required' => true,
|
||||
'data' => $integration->getFeatureSettings(),
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'integration' => $options['integration'],
|
||||
'integration_object' => $integrationObject,
|
||||
'lead_fields' => $options['lead_fields'],
|
||||
'company_fields' => $options['company_fields'],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add('name', HiddenType::class, ['data' => $options['integration']]);
|
||||
|
||||
$builder->add('in_auth', HiddenType::class, ['mapped' => false]);
|
||||
|
||||
$builder->add('buttons', FormButtonsType::class);
|
||||
|
||||
if (!empty($options['action'])) {
|
||||
$builder->setAction($options['action']);
|
||||
}
|
||||
|
||||
$integrationObject->modifyForm($builder, $options);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults(
|
||||
[
|
||||
'data_class' => Integration::class,
|
||||
]
|
||||
);
|
||||
|
||||
$resolver->setRequired(['integration', 'integration_object', 'lead_fields', 'company_fields']);
|
||||
$resolver->setAllowedTypes('integration_object', [AbstractIntegration::class]);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'integration_details';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Form\Type;
|
||||
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class FeatureSettingsType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
protected RequestStack $requestStack,
|
||||
protected CoreParametersHelper $coreParametersHelper,
|
||||
protected LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface<array<mixed>|null> $builder
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$integrationObject = $options['integration_object'];
|
||||
|
||||
// add custom feature settings
|
||||
$integrationObject->appendToForm($builder, $options['data'], 'features');
|
||||
$leadFields = $options['lead_fields'];
|
||||
$companyFields = $options['company_fields'];
|
||||
|
||||
$formModifier = function (FormInterface $form, $data, $method = 'get') use ($integrationObject, $leadFields, $companyFields): void {
|
||||
$integrationName = $integrationObject->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);
|
||||
$companyPage = $session->get('mautic.plugin.'.$integrationName.'.company.page', 1);
|
||||
$settings = [
|
||||
'silence_exceptions' => false,
|
||||
'feature_settings' => $data,
|
||||
'ignore_field_cache' => (1 == $page && 'POST' !== strtoupper($method)) ? true : false,
|
||||
];
|
||||
|
||||
try {
|
||||
if (empty($fields)) {
|
||||
$fields = $integrationObject->getFormLeadFields($settings);
|
||||
$fields = $fields[0] ?? $fields;
|
||||
}
|
||||
|
||||
if (isset($settings['feature_settings']['objects']) and in_array('company', $settings['feature_settings']['objects'])) {
|
||||
if (empty($integrationCompanyFields)) {
|
||||
$integrationCompanyFields = $integrationObject->getFormCompanyFields($settings);
|
||||
}
|
||||
if (isset($integrationCompanyFields['company'])) {
|
||||
$integrationCompanyFields = $integrationCompanyFields['company'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_array($fields)) {
|
||||
$fields = [];
|
||||
}
|
||||
$error = '';
|
||||
} catch (\Exception $e) {
|
||||
$error = $e->getMessage();
|
||||
$this->logger->error($e);
|
||||
|
||||
// Prevent pagination from confusing things by using the cache
|
||||
$page = 1;
|
||||
$fields = $integrationCompanyFields = [];
|
||||
}
|
||||
|
||||
$enableDataPriority = $integrationObject->getDataPriority();
|
||||
|
||||
$form->add(
|
||||
'leadFields',
|
||||
FieldsType::class,
|
||||
[
|
||||
'label' => 'mautic.integration.leadfield_matches',
|
||||
'required' => true,
|
||||
'mautic_fields' => $leadFields,
|
||||
'data' => $data,
|
||||
'integration_fields' => $fields,
|
||||
'enable_data_priority' => $enableDataPriority,
|
||||
'integration' => $integrationObject->getName(),
|
||||
'integration_object' => $integrationObject,
|
||||
'limit' => $limit,
|
||||
'page' => $page,
|
||||
'mapped' => false,
|
||||
'error_bubbling' => false,
|
||||
]
|
||||
);
|
||||
|
||||
if (!empty($integrationCompanyFields)) {
|
||||
$form->add(
|
||||
'companyFields',
|
||||
CompanyFieldsType::class,
|
||||
[
|
||||
'label' => 'mautic.integration.companyfield_matches',
|
||||
'required' => true,
|
||||
'mautic_fields' => $companyFields,
|
||||
'data' => $data,
|
||||
'integration_fields' => $integrationCompanyFields,
|
||||
'enable_data_priority' => $enableDataPriority,
|
||||
'integration' => $integrationObject->getName(),
|
||||
'integration_object' => $integrationObject,
|
||||
'limit' => $limit,
|
||||
'page' => $companyPage,
|
||||
'mapped' => false,
|
||||
'error_bubbling' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
if ('get' == $method && $error) {
|
||||
$form->addError(new FormError($error));
|
||||
}
|
||||
};
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SET_DATA,
|
||||
function (FormEvent $event) use ($formModifier): void {
|
||||
$data = $event->getData();
|
||||
$formModifier($event->getForm(), $data);
|
||||
}
|
||||
);
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($formModifier): void {
|
||||
$data = $event->getData();
|
||||
$formModifier($event->getForm(), $data, 'post');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setRequired(['integration', 'integration_object', 'lead_fields', 'company_fields']);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'integration_featuresettings';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class FieldsType extends AbstractType
|
||||
{
|
||||
use FieldsTypeTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->buildFormFields($builder, $options, $options['integration_fields'], $options['mautic_fields'], '', $options['limit'], $options['start']);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$this->configureFieldOptions($resolver, 'lead');
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'integration_fields';
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
$this->buildFieldView($view, $options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Form\Type;
|
||||
|
||||
use Mautic\CoreBundle\Form\Type\ButtonGroupType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
trait FieldsTypeTrait
|
||||
{
|
||||
/**
|
||||
* @param string $fieldObject
|
||||
*/
|
||||
protected function buildFormFields(
|
||||
FormBuilderInterface $builder,
|
||||
array $options,
|
||||
array $integrationFields,
|
||||
array $mauticFields,
|
||||
$fieldObject,
|
||||
$limit,
|
||||
$start,
|
||||
) {
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SET_DATA,
|
||||
function (FormEvent $event) use ($options, $integrationFields, $mauticFields, $fieldObject, $limit, $start): void {
|
||||
$form = $event->getForm();
|
||||
$index = 0;
|
||||
$choices = [];
|
||||
$requiredFields = [];
|
||||
$optionalFields = [];
|
||||
$group = [];
|
||||
$fieldData = $event->getData();
|
||||
|
||||
foreach ($mauticFields as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$mauticFields[$key] = array_flip($value);
|
||||
}
|
||||
}
|
||||
|
||||
// First loop to build options
|
||||
foreach ($integrationFields as $field => $details) {
|
||||
$groupName = '0default';
|
||||
if (is_array($details)) {
|
||||
if (isset($details['group'])) {
|
||||
if (!isset($choices[$details['group']])) {
|
||||
$choices[$details['group']] = [];
|
||||
}
|
||||
$label = $details['optionLabel'] ?? $details['label'];
|
||||
$group[$field] = $groupName = $details['group'];
|
||||
$choices[$field] = $label;
|
||||
} else {
|
||||
$choices[$field] = $details['label'];
|
||||
}
|
||||
} else {
|
||||
$choices[$field] = $details;
|
||||
}
|
||||
|
||||
if (!isset($requiredFields[$groupName])) {
|
||||
$requiredFields[$groupName] = [];
|
||||
$optionalFields[$groupName] = [];
|
||||
}
|
||||
|
||||
if (is_array($details) && (!empty($details['required']) || 'Email' == $choices[$field])) {
|
||||
$requiredFields[$groupName][$field] = $details;
|
||||
} else {
|
||||
$optionalFields[$groupName][$field] = $details;
|
||||
}
|
||||
}
|
||||
|
||||
// Order the fields by label
|
||||
ksort($requiredFields, SORT_NATURAL);
|
||||
ksort($optionalFields, SORT_NATURAL);
|
||||
|
||||
$sortFieldsFunction = function ($a, $b): int {
|
||||
if (is_array($a)) {
|
||||
$aLabel = $a['optionLabel'] ?? $a['label'];
|
||||
} else {
|
||||
$aLabel = $a;
|
||||
}
|
||||
|
||||
if (is_array($b)) {
|
||||
$bLabel = $b['optionLabel'] ?? $b['label'];
|
||||
} else {
|
||||
$bLabel = $b;
|
||||
}
|
||||
|
||||
return strnatcasecmp($aLabel, $bLabel);
|
||||
};
|
||||
|
||||
$fields = [];
|
||||
foreach ($requiredFields as $groupedFields) {
|
||||
uasort($groupedFields, $sortFieldsFunction);
|
||||
|
||||
$fields = array_merge($fields, $groupedFields);
|
||||
}
|
||||
foreach ($optionalFields as $groupedFields) {
|
||||
uasort($groupedFields, $sortFieldsFunction);
|
||||
|
||||
$fields = array_merge($fields, $groupedFields);
|
||||
}
|
||||
|
||||
// Ensure that fields aren't hidden
|
||||
if ($start > count($fields) || 0 == $options['page']) {
|
||||
$start = 0;
|
||||
}
|
||||
|
||||
$paginatedFields = array_slice($fields, $start, $limit);
|
||||
$fieldsName = 'leadFields';
|
||||
if ($fieldObject) {
|
||||
$fieldsName = $fieldObject.'Fields';
|
||||
}
|
||||
if (isset($fieldData[$fieldsName])) {
|
||||
$fieldData[$fieldsName] = $options['integration_object']->formatMatchedFields($fieldData[$fieldsName]);
|
||||
}
|
||||
|
||||
foreach ($paginatedFields as $field => $details) {
|
||||
$matched = isset($fieldData[$fieldsName][$field]);
|
||||
$required = (int) (!empty($integrationFields[$field]['required']) || 'Email' == $choices[$field]);
|
||||
++$index;
|
||||
$form->add(
|
||||
'label_'.$index,
|
||||
TextType::class,
|
||||
[
|
||||
'label' => false,
|
||||
'data' => $choices[$field],
|
||||
'attr' => [
|
||||
'class' => 'form-control integration-fields',
|
||||
'data-required' => $required,
|
||||
'data-label' => $choices[$field],
|
||||
'placeholder' => $group[$field] ?? '',
|
||||
'readonly' => true,
|
||||
],
|
||||
'by_reference' => true,
|
||||
'mapped' => false,
|
||||
]
|
||||
);
|
||||
if (isset($options['enable_data_priority']) and $options['enable_data_priority']) {
|
||||
$updateName = 'update_mautic';
|
||||
|
||||
if ($fieldObject) {
|
||||
$updateName .= '_'.$fieldObject;
|
||||
}
|
||||
|
||||
$forceDirection = false;
|
||||
$disabled = (isset($fieldData[$fieldsName][$field])) ? $options['integration_object']->isCompoundMauticField($fieldData[$fieldsName][$field]) : false;
|
||||
$data = isset($fieldData[$updateName][$field]) ? (int) $fieldData[$updateName][$field] : 1;
|
||||
|
||||
// Force to use just one way for certainly fields
|
||||
if (isset($fields[$field]['update_mautic'])) {
|
||||
$data = (bool) $fields[$field]['update_mautic'];
|
||||
$disabled = true;
|
||||
$forceDirection = true;
|
||||
}
|
||||
|
||||
$form->add(
|
||||
$updateName.$index,
|
||||
ButtonGroupType::class,
|
||||
[
|
||||
'choices' => [
|
||||
'<btn class="btn-nospin ri-arrow-left-circle-line"></btn>' => 0,
|
||||
'<btn class="btn-nospin ri-arrow-right-circle-line"></btn>' => 1,
|
||||
],
|
||||
'label' => false,
|
||||
'data' => $data,
|
||||
'placeholder' => false,
|
||||
'attr' => [
|
||||
'data-toggle' => 'tooltip',
|
||||
'title' => 'mautic.plugin.direction.data.update',
|
||||
'disabled' => $disabled,
|
||||
'forceDirection'=> $forceDirection,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (!$fieldObject) {
|
||||
$mauticFields['mautic.lead.report.contact_id'] = 'mauticContactId';
|
||||
$mauticFields['mautic.plugin.integration.contact.timeline.link'] = 'mauticContactTimelineLink';
|
||||
$mauticFields['mautic.plugin.integration.contact.donotcontact.email'] = 'mauticContactIsContactableByEmail';
|
||||
}
|
||||
|
||||
$form->add(
|
||||
'm_'.$index,
|
||||
ChoiceType::class,
|
||||
[
|
||||
'choices' => $mauticFields,
|
||||
'label' => false,
|
||||
'data' => $matched && isset($fieldData[$fieldsName][$field]) ? $fieldData[$fieldsName][$field] : '',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'class' => 'field-selector',
|
||||
'data-placeholder' => ' ',
|
||||
'data-required' => $required,
|
||||
'data-value' => $matched && isset($fieldData[$fieldsName][$field]) ? $fieldData[$fieldsName][$field] : '',
|
||||
'data-choices' => $mauticFields,
|
||||
],
|
||||
]
|
||||
);
|
||||
$form->add(
|
||||
'i_'.$index,
|
||||
HiddenType::class,
|
||||
[
|
||||
'data' => $field,
|
||||
'attr' => [
|
||||
'data-required' => $required,
|
||||
'data-value' => $field,
|
||||
],
|
||||
]
|
||||
);
|
||||
$form->add(
|
||||
$field,
|
||||
HiddenType::class,
|
||||
[
|
||||
'data' => $index,
|
||||
'attr' => [
|
||||
'data-required' => $required,
|
||||
'data-value' => $index,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function configureFieldOptions(OptionsResolver $resolver, $object)
|
||||
{
|
||||
$resolver->setRequired(['integration_fields', 'mautic_fields', 'integration', 'integration_object', 'page']);
|
||||
$resolver->setDefined([('lead' === $object) ? 'update_mautic' : 'update_mautic_company']);
|
||||
$resolver->setDefaults(
|
||||
[
|
||||
'special_instructions' => function (Options $options) {
|
||||
[$specialInstructions, $alertType] = $options['integration_object']->getFormNotes('leadfield_match');
|
||||
|
||||
return $specialInstructions;
|
||||
},
|
||||
'alert_type' => function (Options $options) {
|
||||
[$specialInstructions, $alertType] = $options['integration_object']->getFormNotes('leadfield_match');
|
||||
|
||||
return $alertType;
|
||||
},
|
||||
'allow_extra_fields' => true,
|
||||
'enable_data_priority' => false,
|
||||
'totalFields' => fn (Options $options): int => count($options['integration_fields']),
|
||||
'fixedPageNum' => fn (Options $options): float => ceil($options['totalFields'] / $options['limit']),
|
||||
'limit' => 10,
|
||||
'start' => fn (Options $options): int => (1 === (int) $options['page']) ? 0 : ((int) $options['page'] - 1) * (int) $options['limit'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildFieldView(FormView $view, array $options)
|
||||
{
|
||||
$view->vars['specialInstructions'] = $options['special_instructions'];
|
||||
$view->vars['alertType'] = $options['alert_type'];
|
||||
$view->vars['integration'] = $options['integration'];
|
||||
$view->vars['totalFields'] = $options['totalFields'];
|
||||
$view->vars['page'] = $options['page'];
|
||||
$view->vars['fixedPageNum'] = $options['fixedPageNum'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class IntegrationCampaignsType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add(
|
||||
'campaign_member_status',
|
||||
ChoiceType::class,
|
||||
[
|
||||
'choices' => array_flip($options['campaignContactStatus']),
|
||||
'attr' => [
|
||||
'class' => 'form-control', ],
|
||||
'label' => 'mautic.plugin.integration.campaigns.member.status',
|
||||
'required' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults(
|
||||
['campaignContactStatus' => []]);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'integration_campaign_status';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>|mixed>
|
||||
*/
|
||||
class IntegrationConfigType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
if (null != $options['integration']) {
|
||||
$options['integration']->appendToForm($builder, $options['data'], 'integration');
|
||||
}
|
||||
|
||||
if (!empty($options['campaigns'])) {
|
||||
$builder->add(
|
||||
'campaigns',
|
||||
ChoiceType::class,
|
||||
[
|
||||
'choices' => array_flip($options['campaigns']),
|
||||
'attr' => [
|
||||
'class' => 'form-control', 'onchange' => 'Mautic.getIntegrationCampaignStatus(this);', ],
|
||||
'label' => 'mautic.plugin.integration.campaigns',
|
||||
'placeholder' => 'mautic.plugin.config.campaign.member.chooseone',
|
||||
'required' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setRequired(['integration']);
|
||||
$resolver->setDefaults([
|
||||
'label' => false,
|
||||
'campaigns' => [],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Form\Type;
|
||||
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<mixed>
|
||||
*/
|
||||
class IntegrationsListType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private IntegrationHelper $integrationHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$integrationObjects = $this->integrationHelper->getIntegrationObjects(null, $options['supported_features'], true);
|
||||
$integrations = ['' => ''];
|
||||
|
||||
foreach ($integrationObjects as $object) {
|
||||
$settings = $object->getIntegrationSettings();
|
||||
|
||||
if ($settings->isPublished()) {
|
||||
$pluginName = $settings->getPlugin()->getName();
|
||||
if (!isset($integrations[$pluginName])) {
|
||||
$integrations[$pluginName] = [];
|
||||
}
|
||||
$integrations[$pluginName][$object->getDisplayName()] = $object->getName();
|
||||
}
|
||||
}
|
||||
|
||||
$builder->add(
|
||||
'integration',
|
||||
ChoiceType::class,
|
||||
[
|
||||
'choices' => $integrations,
|
||||
'expanded' => false,
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'multiple' => false,
|
||||
'label' => 'mautic.integration.integration',
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'tooltip' => 'mautic.integration.integration.tooltip',
|
||||
'onchange' => 'Mautic.getIntegrationConfig(this);',
|
||||
],
|
||||
'required' => true,
|
||||
'constraints' => [
|
||||
new NotBlank(
|
||||
['message' => 'mautic.core.value.required']
|
||||
),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$formModifier = function (FormEvent $event) use ($integrationObjects): void {
|
||||
$data = $event->getData();
|
||||
$form = $event->getForm();
|
||||
$statusChoices = [];
|
||||
$campaignChoices = [];
|
||||
|
||||
if (!empty($data['integration'])) {
|
||||
$integrationObject = $this->integrationHelper->getIntegrationObject($data['integration']);
|
||||
if (method_exists($integrationObject, 'getCampaigns')) {
|
||||
$campaigns = $integrationObject->getCampaigns();
|
||||
|
||||
if (isset($campaigns['records']) && !empty($campaigns['records'])) {
|
||||
foreach ($campaigns['records'] as $campaign) {
|
||||
$campaignChoices[$campaign['Id']] = $campaign['Name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (method_exists($integrationObject, 'getCampaignMemberStatus') && isset($data['config']['campaigns'])) {
|
||||
$campaignStatus = $integrationObject->getCampaignMemberStatus($data['config']['campaigns']);
|
||||
|
||||
if (isset($campaignStatus['records']) && !empty($campaignStatus['records'])) {
|
||||
foreach ($campaignStatus['records'] as $campaignS) {
|
||||
$statusChoices[$campaignS['Label']] = $campaignS['Label'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$form->add(
|
||||
'config',
|
||||
IntegrationConfigType::class,
|
||||
[
|
||||
'label' => false,
|
||||
'attr' => [
|
||||
'class' => 'integration-config-container',
|
||||
],
|
||||
'integration' => isset($data['integration'], $integrationObjects[$data['integration']]) ? $integrationObjects[$data['integration']] : null,
|
||||
'campaigns' => $campaignChoices,
|
||||
'data' => $data['config'] ?? [],
|
||||
]
|
||||
);
|
||||
|
||||
$hideClass = (isset($data['campaign_member_status']) && !empty($data['campaign_member_status']['campaign_member_status'])) ? '' : ' hide';
|
||||
$form->add(
|
||||
'campaign_member_status',
|
||||
IntegrationCampaignsType::class,
|
||||
[
|
||||
'label' => false,
|
||||
'attr' => [
|
||||
'class' => 'integration-campaigns-status'.$hideClass,
|
||||
],
|
||||
'campaignContactStatus' => $statusChoices,
|
||||
'data' => $data['campaign_member_status'] ?? [],
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, $formModifier);
|
||||
|
||||
$builder->addEventListener(FormEvents::PRE_SUBMIT, $formModifier);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefined(['supported_features']);
|
||||
$resolver->setDefaults(
|
||||
[
|
||||
'supported_features' => 'push_lead',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'integration_list';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Callback;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class KeysType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$object = $options['integration_object'];
|
||||
$secretKeys = $object->getSecretKeys();
|
||||
$requiredKeys = $object->getRequiredKeyFields();
|
||||
|
||||
foreach ($options['integration_keys'] as $key => $label) {
|
||||
$isSecret = in_array($key, $secretKeys);
|
||||
$required = (isset($requiredKeys[$key]));
|
||||
|
||||
// Password fields are going to be blank even if a value exists so only require if a password is not already saved
|
||||
if ($isSecret && !empty($options['data'][$key])) {
|
||||
$required = false;
|
||||
}
|
||||
|
||||
$constraints = ($required)
|
||||
? [
|
||||
new Callback(
|
||||
function ($validateMe, ExecutionContextInterface $context) use ($options): void {
|
||||
if (empty($validateMe) && !empty($options['is_published'])) {
|
||||
$context->buildViolation('mautic.core.value.required')->addViolation();
|
||||
}
|
||||
}
|
||||
),
|
||||
] : [];
|
||||
|
||||
$type = ($isSecret) ? PasswordType::class : TextType::class;
|
||||
|
||||
$builder->add(
|
||||
$key,
|
||||
$type,
|
||||
[
|
||||
'label' => $label,
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'placeholder' => (PasswordType::class === $type) ? '**************' : '',
|
||||
'autocomplete' => 'off',
|
||||
],
|
||||
'required' => $required,
|
||||
'constraints' => $constraints,
|
||||
'error_bubbling' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
$object->appendToForm($builder, $options['data'], 'keys');
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setRequired(['integration_object', 'integration_keys']);
|
||||
$resolver->setDefined(['secret_keys']);
|
||||
$resolver->setDefaults(['secret_keys' => [], 'is_published' => true]);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'integration_keys';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Helper;
|
||||
|
||||
class Cleaner
|
||||
{
|
||||
public const FIELD_TYPE_STRING = 'string';
|
||||
|
||||
public const FIELD_TYPE_BOOL = 'boolean';
|
||||
|
||||
public const FIELD_TYPE_NUMBER = 'number';
|
||||
|
||||
public const FIELD_TYPE_DATETIME = 'datetime';
|
||||
|
||||
public const FIELD_TYPE_DATE = 'date';
|
||||
|
||||
/**
|
||||
* @return bool|float|string
|
||||
*/
|
||||
public static function clean($value, $fieldType = self::FIELD_TYPE_STRING)
|
||||
{
|
||||
$clean = strip_tags(html_entity_decode($value, ENT_QUOTES));
|
||||
switch ($fieldType) {
|
||||
case self::FIELD_TYPE_BOOL:
|
||||
return (bool) $clean;
|
||||
case self::FIELD_TYPE_NUMBER:
|
||||
return (float) $clean;
|
||||
case self::FIELD_TYPE_DATETIME:
|
||||
$dateTimeValue = new \DateTime($value);
|
||||
|
||||
return (!empty($clean)) ? $dateTimeValue->format('c') : '';
|
||||
case self::FIELD_TYPE_DATE:
|
||||
$dateTimeValue = new \DateTime($value);
|
||||
|
||||
return (!empty($clean)) ? $dateTimeValue->format('Y-m-d') : '';
|
||||
default:
|
||||
return $clean;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Helper;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\PluginBundle\EventListener\PushToIntegrationTrait;
|
||||
|
||||
class EventHelper
|
||||
{
|
||||
use PushToIntegrationTrait;
|
||||
|
||||
public static function pushLead($config, $lead, EntityManagerInterface $em, IntegrationHelper $integrationHelper): bool
|
||||
{
|
||||
$contact = $em->getRepository(\Mautic\LeadBundle\Entity\Lead::class)->getEntityWithPrimaryCompany($lead);
|
||||
|
||||
static::setStaticIntegrationHelper($integrationHelper);
|
||||
$errors = [];
|
||||
|
||||
return static::pushIt($config, $contact, $errors);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,618 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Helper;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Mautic\CoreBundle\Cache\ResultCacheOptions;
|
||||
use Mautic\CoreBundle\Helper\BundleHelper;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\DateTimeHelper;
|
||||
use Mautic\CoreBundle\Helper\PathsHelper;
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Mautic\PluginBundle\Entity\Plugin;
|
||||
use Mautic\PluginBundle\Integration\AbstractIntegration;
|
||||
use Mautic\PluginBundle\Integration\UnifiedIntegrationInterface;
|
||||
use Mautic\PluginBundle\Model\PluginModel;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Twig\Environment;
|
||||
|
||||
class IntegrationHelper
|
||||
{
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private array $integrations = [];
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private array $available = [];
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private array $byFeatureList = [];
|
||||
|
||||
/**
|
||||
* @var array<int, mixed>
|
||||
*/
|
||||
private array $byPlugin = [];
|
||||
|
||||
public function __construct(
|
||||
private ContainerInterface $container,
|
||||
protected EntityManager $em,
|
||||
protected PathsHelper $pathsHelper,
|
||||
protected BundleHelper $bundleHelper,
|
||||
protected CoreParametersHelper $coreParametersHelper,
|
||||
protected Environment $twig,
|
||||
protected PluginModel $pluginModel,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of integration helper classes.
|
||||
*
|
||||
* @param array|string $specificIntegrations
|
||||
* @param array $withFeatures
|
||||
* @param bool $alphabetical
|
||||
* @param int|null $pluginFilter
|
||||
* @param bool|false $publishedOnly
|
||||
*
|
||||
* @return array<AbstractIntegration>
|
||||
*
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
*/
|
||||
public function getIntegrationObjects($specificIntegrations = null, $withFeatures = null, $alphabetical = false, $pluginFilter = null, $publishedOnly = false): array
|
||||
{
|
||||
// Build the service classes
|
||||
if ([] === $this->available) {
|
||||
// Get currently installed integrations
|
||||
$integrationSettings = $this->getIntegrationSettings();
|
||||
|
||||
// And we'll be scanning the addon bundles for additional classes, so have that data on standby
|
||||
$plugins = $this->bundleHelper->getPluginBundles();
|
||||
|
||||
// Get a list of already installed integrations
|
||||
$integrationRepo = $this->em->getRepository(Integration::class);
|
||||
// get a list of plugins for filter
|
||||
$installedPlugins = $this->pluginModel->getEntities(
|
||||
[
|
||||
'hydration_mode' => 'hydrate_array',
|
||||
'index' => 'bundle',
|
||||
'result_cache' => new ResultCacheOptions(Plugin::CACHE_NAMESPACE),
|
||||
]
|
||||
);
|
||||
|
||||
$newIntegrations = [];
|
||||
|
||||
// Scan the plugins for integration classes
|
||||
foreach ($plugins as $plugin) {
|
||||
// Do not list the integration if the bundle has not been "installed"
|
||||
if (!isset($plugin['bundle']) || !isset($installedPlugins[$plugin['bundle']])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_dir($plugin['directory'].'/Integration')) {
|
||||
$finder = new Finder();
|
||||
$finder->files()->name('*Integration.php')->in($plugin['directory'].'/Integration')->ignoreDotFiles(true);
|
||||
|
||||
$id = $installedPlugins[$plugin['bundle']]['id'];
|
||||
$this->byPlugin[$id] = [];
|
||||
$pluginReference = $this->em->getReference(Plugin::class, $id);
|
||||
$pluginNamespace = str_replace('MauticPlugin', '', $plugin['bundle']);
|
||||
|
||||
foreach ($finder as $file) {
|
||||
$integrationName = substr($file->getBaseName(), 0, -15);
|
||||
|
||||
if (!isset($integrationSettings[$integrationName])) {
|
||||
$newIntegration = new Integration();
|
||||
$newIntegration->setName($integrationName)
|
||||
->setPlugin($pluginReference);
|
||||
$integrationSettings[$integrationName] = $newIntegration;
|
||||
$integrationContainerKey = strtolower("mautic.integration.{$integrationName}");
|
||||
|
||||
// Initiate the class in order to get the features supported
|
||||
if ($this->container->has($integrationContainerKey)) {
|
||||
$this->integrations[$integrationName] = $this->container->get($integrationContainerKey);
|
||||
|
||||
$features = $this->integrations[$integrationName]->getSupportedFeatures();
|
||||
$newIntegration->setSupportedFeatures($features);
|
||||
|
||||
// Go ahead and stash it since it's built already
|
||||
$this->integrations[$integrationName]->setIntegrationSettings($newIntegration);
|
||||
|
||||
$newIntegrations[] = $newIntegration;
|
||||
|
||||
unset($newIntegration);
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Integration $settings */
|
||||
$settings = $integrationSettings[$integrationName];
|
||||
$this->available[$integrationName] = [
|
||||
'isPlugin' => true,
|
||||
'integration' => $integrationName,
|
||||
'settings' => $settings,
|
||||
'namespace' => $pluginNamespace,
|
||||
];
|
||||
|
||||
// Sort by feature and plugin for later
|
||||
$features = $settings->getSupportedFeatures();
|
||||
foreach ($features as $feature) {
|
||||
if (!isset($this->byFeatureList[$feature])) {
|
||||
$this->byFeatureList[$feature] = [];
|
||||
}
|
||||
$this->byFeatureList[$feature][] = $integrationName;
|
||||
}
|
||||
$this->byPlugin[$id][] = $integrationName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$coreIntegrationSettings = $this->getCoreIntegrationSettings();
|
||||
|
||||
// Scan core bundles for integration classes
|
||||
foreach ($this->bundleHelper->getMauticBundles() as $coreBundle) {
|
||||
if (
|
||||
// Skip plugin bundles
|
||||
str_contains($coreBundle['relative'], 'app/bundles')
|
||||
// Skip core bundles without an Integration directory
|
||||
&& is_dir($coreBundle['directory'].'/Integration')
|
||||
) {
|
||||
$finder = new Finder();
|
||||
$finder->files()->name('*Integration.php')->in($coreBundle['directory'].'/Integration')->ignoreDotFiles(true);
|
||||
|
||||
$coreBundleNamespace = str_replace('Mautic', '', $coreBundle['bundle']);
|
||||
|
||||
foreach ($finder as $file) {
|
||||
$integrationName = substr($file->getBaseName(), 0, -15);
|
||||
|
||||
if (!isset($coreIntegrationSettings[$integrationName])) {
|
||||
$newIntegration = new Integration();
|
||||
$newIntegration->setName($integrationName);
|
||||
$integrationSettings[$integrationName] = $newIntegration;
|
||||
|
||||
$integrationContainerKey = strtolower("mautic.integration.{$integrationName}");
|
||||
|
||||
// Initiate the class in order to get the features supported
|
||||
if ($this->container->has($integrationContainerKey)) {
|
||||
$this->integrations[$integrationName] = $this->container->get($integrationContainerKey);
|
||||
$features = $this->integrations[$integrationName]->getSupportedFeatures();
|
||||
$newIntegration->setSupportedFeatures($features);
|
||||
|
||||
// Go ahead and stash it since it's built already
|
||||
$this->integrations[$integrationName]->setIntegrationSettings($newIntegration);
|
||||
|
||||
$newIntegrations[] = $newIntegration;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Integration $settings */
|
||||
$settings = $coreIntegrationSettings[$integrationName] ?? $newIntegration;
|
||||
$this->available[$integrationName] = [
|
||||
'isPlugin' => false,
|
||||
'integration' => $integrationName,
|
||||
'settings' => $settings,
|
||||
'namespace' => $coreBundleNamespace,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save newly found integrations
|
||||
if (!empty($newIntegrations)) {
|
||||
$integrationRepo->saveEntities($newIntegrations);
|
||||
unset($newIntegrations);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure appropriate formats
|
||||
if (null !== $specificIntegrations && !is_array($specificIntegrations)) {
|
||||
$specificIntegrations = [$specificIntegrations];
|
||||
}
|
||||
|
||||
if (null !== $withFeatures && !is_array($withFeatures)) {
|
||||
$withFeatures = [$withFeatures];
|
||||
}
|
||||
|
||||
// Build the integrations wanted
|
||||
if (!empty($pluginFilter)) {
|
||||
// Filter by plugin
|
||||
$filteredIntegrations = $this->byPlugin[$pluginFilter];
|
||||
} elseif (!empty($specificIntegrations)) {
|
||||
// Filter by specific integrations
|
||||
$filteredIntegrations = $specificIntegrations;
|
||||
} else {
|
||||
// All services by default
|
||||
$filteredIntegrations = array_keys($this->available);
|
||||
}
|
||||
|
||||
// Filter by features
|
||||
if (!empty($withFeatures)) {
|
||||
$integrationsWithFeatures = [];
|
||||
foreach ($withFeatures as $feature) {
|
||||
if (isset($this->byFeatureList[$feature])) {
|
||||
$integrationsWithFeatures = $integrationsWithFeatures + $this->byFeatureList[$feature];
|
||||
}
|
||||
}
|
||||
|
||||
$filteredIntegrations = array_intersect($filteredIntegrations, $integrationsWithFeatures);
|
||||
}
|
||||
|
||||
$returnServices = [];
|
||||
|
||||
// Build the classes if not already
|
||||
foreach ($filteredIntegrations as $integrationName) {
|
||||
if (!isset($this->available[$integrationName]) || ($publishedOnly && !$this->available[$integrationName]['settings']->isPublished())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($this->integrations[$integrationName])) {
|
||||
$integration = $this->available[$integrationName];
|
||||
$integrationContainerKey = strtolower("mautic.integration.{$integrationName}");
|
||||
|
||||
if ($this->container->has($integrationContainerKey)) {
|
||||
$this->integrations[$integrationName] = $this->container->get($integrationContainerKey);
|
||||
$this->integrations[$integrationName]->setIntegrationSettings($integration['settings']);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->integrations[$integrationName])) {
|
||||
$returnServices[$integrationName] = $this->integrations[$integrationName];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($returnServices as $key => $value) {
|
||||
if (!$value) {
|
||||
unset($returnServices[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($alphabetical)) {
|
||||
// Sort by priority
|
||||
uasort($returnServices, function ($a, $b): int {
|
||||
$aP = (int) $a->getPriority();
|
||||
$bP = (int) $b->getPriority();
|
||||
|
||||
return $aP <=> $bP;
|
||||
});
|
||||
} else {
|
||||
// Sort by display name
|
||||
uasort($returnServices, function ($a, $b): int {
|
||||
$aName = $a->getDisplayName();
|
||||
$bName = $b->getDisplayName();
|
||||
|
||||
return strcasecmp($aName, $bName);
|
||||
});
|
||||
}
|
||||
|
||||
return $returnServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single integration object.
|
||||
*
|
||||
* @return AbstractIntegration|false
|
||||
*/
|
||||
public function getIntegrationObject($name)
|
||||
{
|
||||
$integrationObjects = $this->getIntegrationObjects($name);
|
||||
|
||||
return $integrationObjects[$name] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a count of integrations.
|
||||
*/
|
||||
public function getIntegrationCount($plugin): int
|
||||
{
|
||||
if (!is_array($plugin)) {
|
||||
$plugins = $this->coreParametersHelper->get('plugin.bundles');
|
||||
if (array_key_exists($plugin, $plugins)) {
|
||||
$plugin = $plugins[$plugin];
|
||||
} else {
|
||||
// It doesn't exist so return 0
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_dir($plugin['directory'].'/Integration')) {
|
||||
$finder = new Finder();
|
||||
$finder->files()->name('*Integration.php')->in($plugin['directory'].'/Integration')->ignoreDotFiles(true);
|
||||
|
||||
return iterator_count($finder);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns popular social media services and regex URLs for parsing purposes.
|
||||
*
|
||||
* @param bool $find If true, array of regexes to find a handle will be returned;
|
||||
* If false, array of URLs with a placeholder of %handle% will be returned
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @todo Extend this method to allow plugins to add URLs to these arrays
|
||||
*/
|
||||
public function getSocialProfileUrlRegex($find = true)
|
||||
{
|
||||
if ($find) {
|
||||
// regex to find a match
|
||||
return [
|
||||
'twitter' => "/twitter.com\/(.*?)($|\/)/",
|
||||
'facebook' => [
|
||||
"/facebook.com\/(.*?)($|\/)/",
|
||||
"/fb.me\/(.*?)($|\/)/",
|
||||
],
|
||||
'linkedin' => "/linkedin.com\/in\/(.*?)($|\/)/",
|
||||
'instagram' => "/instagram.com\/(.*?)($|\/)/",
|
||||
'pinterest' => "/pinterest.com\/(.*?)($|\/)/",
|
||||
'klout' => "/klout.com\/(.*?)($|\/)/",
|
||||
'youtube' => [
|
||||
"/youtube.com\/user\/(.*?)($|\/)/",
|
||||
"/youtu.be\/user\/(.*?)($|\/)/",
|
||||
],
|
||||
'flickr' => "/flickr.com\/photos\/(.*?)($|\/)/",
|
||||
'skype' => "/skype:(.*?)($|\?)/",
|
||||
];
|
||||
} else {
|
||||
// populate placeholder
|
||||
return [
|
||||
'twitter' => 'https://twitter.com/%handle%',
|
||||
'facebook' => 'https://facebook.com/%handle%',
|
||||
'linkedin' => 'https://linkedin.com/in/%handle%',
|
||||
'instagram' => 'https://instagram.com/%handle%',
|
||||
'pinterest' => 'https://pinterest.com/%handle%',
|
||||
'klout' => 'https://klout.com/%handle%',
|
||||
'youtube' => 'https://youtube.com/user/%handle%',
|
||||
'flickr' => 'https://flickr.com/photos/%handle%',
|
||||
'skype' => 'skype:%handle%?call',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of integration entities.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getIntegrationSettings()
|
||||
{
|
||||
return $this->em->getRepository(Integration::class)->getIntegrations();
|
||||
}
|
||||
|
||||
public function getCoreIntegrationSettings()
|
||||
{
|
||||
return $this->em->getRepository(Integration::class)->getCoreIntegrations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's social profile data from cache or integrations if indicated.
|
||||
*
|
||||
* @param \Mautic\LeadBundle\Entity\Lead $lead
|
||||
* @param array $fields
|
||||
* @param bool $refresh
|
||||
* @param string $specificIntegration
|
||||
* @param bool $persistLead
|
||||
* @param bool $returnSettings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUserProfiles($lead, $fields = [], $refresh = false, $specificIntegration = null, $persistLead = true, $returnSettings = false)
|
||||
{
|
||||
$socialCache = $lead->getSocialCache();
|
||||
$featureSettings = [];
|
||||
if ($refresh) {
|
||||
// regenerate from integrations
|
||||
$now = new DateTimeHelper();
|
||||
|
||||
// check to see if there are social profiles activated
|
||||
$socialIntegrations = $this->getIntegrationObjects($specificIntegration, ['public_profile', 'public_activity']);
|
||||
|
||||
/* @var \MauticPlugin\MauticSocialBundle\Integration\SocialIntegration $sn */
|
||||
foreach ($socialIntegrations as $integration => $sn) {
|
||||
$settings = $sn->getIntegrationSettings();
|
||||
$features = $settings->getSupportedFeatures();
|
||||
$identifierField = $this->getUserIdentifierField($sn, $fields);
|
||||
|
||||
if ($returnSettings) {
|
||||
$featureSettings[$integration] = $settings->getFeatureSettings();
|
||||
}
|
||||
|
||||
if ($identifierField && $settings->isPublished()) {
|
||||
$profile = (!isset($socialCache[$integration])) ? [] : $socialCache[$integration];
|
||||
|
||||
// clear the cache
|
||||
unset($profile['profile'], $profile['activity']);
|
||||
|
||||
if (in_array('public_profile', $features) && $sn->isAuthorized()) {
|
||||
$sn->getUserData($identifierField, $profile);
|
||||
}
|
||||
|
||||
if (in_array('public_activity', $features) && $sn->isAuthorized()) {
|
||||
$sn->getPublicActivity($identifierField, $profile);
|
||||
}
|
||||
|
||||
if (!empty($profile['profile']) || !empty($profile['activity'])) {
|
||||
if (!isset($socialCache[$integration])) {
|
||||
$socialCache[$integration] = [];
|
||||
}
|
||||
|
||||
$socialCache[$integration]['profile'] = (!empty($profile['profile'])) ? $profile['profile'] : [];
|
||||
$socialCache[$integration]['activity'] = (!empty($profile['activity'])) ? $profile['activity'] : [];
|
||||
$socialCache[$integration]['lastRefresh'] = $now->toUtcString();
|
||||
}
|
||||
} elseif (isset($socialCache[$integration])) {
|
||||
// integration is now not applicable
|
||||
unset($socialCache[$integration]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($persistLead && !empty($socialCache)) {
|
||||
$lead->setSocialCache($socialCache);
|
||||
$this->em->getRepository(\Mautic\LeadBundle\Entity\Lead::class)->saveEntity($lead);
|
||||
}
|
||||
} elseif ($returnSettings) {
|
||||
$socialIntegrations = $this->getIntegrationObjects($specificIntegration, ['public_profile', 'public_activity']);
|
||||
foreach ($socialIntegrations as $integration => $sn) {
|
||||
$settings = $sn->getIntegrationSettings();
|
||||
$featureSettings[$integration] = $settings->getFeatureSettings();
|
||||
}
|
||||
}
|
||||
|
||||
if ($specificIntegration) {
|
||||
return ($returnSettings) ? [[$specificIntegration => $socialCache[$specificIntegration]], $featureSettings]
|
||||
: [$specificIntegration => $socialCache[$specificIntegration]];
|
||||
}
|
||||
|
||||
return ($returnSettings) ? [$socialCache, $featureSettings] : $socialCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $integration
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function clearIntegrationCache($lead, $integration = false)
|
||||
{
|
||||
$socialCache = $lead->getSocialCache();
|
||||
if (!empty($integration)) {
|
||||
unset($socialCache[$integration]);
|
||||
} else {
|
||||
$socialCache = [];
|
||||
}
|
||||
$lead->setSocialCache($socialCache);
|
||||
$this->em->getRepository(\Mautic\LeadBundle\Entity\Lead::class)->saveEntity($lead);
|
||||
|
||||
return $socialCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of the HTML for share buttons.
|
||||
*/
|
||||
public function getShareButtons()
|
||||
{
|
||||
static $shareBtns = [];
|
||||
|
||||
if (empty($shareBtns)) {
|
||||
$socialIntegrations = $this->getIntegrationObjects(null, ['share_button'], true);
|
||||
|
||||
/**
|
||||
* @var string $integration
|
||||
* @var AbstractIntegration $details
|
||||
*/
|
||||
foreach ($socialIntegrations as $integration => $details) {
|
||||
/** @var Integration $settings */
|
||||
$settings = $details->getIntegrationSettings();
|
||||
|
||||
$featureSettings = $settings->getFeatureSettings();
|
||||
$apiKeys = $details->decryptApiKeys($settings->getApiKeys());
|
||||
$plugin = $settings->getPlugin();
|
||||
$shareSettings = $featureSettings['shareButton'] ?? [];
|
||||
|
||||
// add the api keys for use within the share buttons
|
||||
$shareSettings['keys'] = $apiKeys;
|
||||
$shareBtns[$integration] = $this->twig->render($plugin->getBundle()."/Integration/$integration:share.html.twig", [
|
||||
'settings' => $shareSettings,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $shareBtns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through field values available and finds the field the integration needs to obtain the user.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUserIdentifierField($integrationObject, $fields)
|
||||
{
|
||||
$identifierField = $integrationObject->getIdentifierFields();
|
||||
$identifier = (is_array($identifierField)) ? [] : false;
|
||||
$matchFound = false;
|
||||
|
||||
$findMatch = function ($f, $fields) use (&$identifierField, &$identifier, &$matchFound): void {
|
||||
if (is_array($identifier)) {
|
||||
// there are multiple fields the integration can identify by
|
||||
foreach ($identifierField as $idf) {
|
||||
$value = (is_array($fields[$f]) && isset($fields[$f]['value'])) ? $fields[$f]['value'] : $fields[$f];
|
||||
|
||||
if (!in_array($value, $identifier) && str_contains($f, $idf)) {
|
||||
$identifier[$f] = $value;
|
||||
if (count($identifier) === count($identifierField)) {
|
||||
// found enough matches so break
|
||||
$matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($identifierField === $f || str_contains($f, $identifierField)) {
|
||||
$matchFound = true;
|
||||
$identifier = (is_array($fields[$f])) ? $fields[$f]['value'] : $fields[$f];
|
||||
}
|
||||
};
|
||||
|
||||
$groups = ['core', 'social', 'professional', 'personal'];
|
||||
$keys = array_keys($fields);
|
||||
if (0 !== count(array_intersect($groups, $keys)) && count($keys) <= 4) {
|
||||
// fields are group
|
||||
foreach ($fields as $groupFields) {
|
||||
$availableFields = array_keys($groupFields);
|
||||
foreach ($availableFields as $f) {
|
||||
$findMatch($f, $groupFields);
|
||||
|
||||
if ($matchFound) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$availableFields = array_keys($fields);
|
||||
foreach ($availableFields as $f) {
|
||||
$findMatch($f, $fields);
|
||||
|
||||
if ($matchFound) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the integration's icon relative to the site root.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIconPath($integration)
|
||||
{
|
||||
$systemPath = $this->pathsHelper->getSystemPath('root');
|
||||
$bundlePath = $this->pathsHelper->getSystemPath('bundles');
|
||||
$pluginPath = $this->pathsHelper->getSystemPath('plugins');
|
||||
$genericIcon = $bundlePath.'/PluginBundle/Assets/img/generic.png';
|
||||
|
||||
if (is_array($integration)) {
|
||||
// A bundle so check for an icon
|
||||
$icon = $pluginPath.'/'.$integration['bundle'].'/Assets/img/icon.png';
|
||||
} elseif ($integration instanceof Plugin) {
|
||||
// A bundle so check for an icon
|
||||
$icon = $pluginPath.'/'.$integration->getBundle().'/Assets/img/icon.png';
|
||||
} elseif ($integration instanceof UnifiedIntegrationInterface) {
|
||||
return $integration->getIcon();
|
||||
}
|
||||
|
||||
if (file_exists($systemPath.'/'.$icon)) {
|
||||
return $icon;
|
||||
}
|
||||
|
||||
return $genericIcon;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Helper;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Mautic\PluginBundle\Entity\Plugin;
|
||||
use Mautic\PluginBundle\Event\PluginInstallEvent;
|
||||
use Mautic\PluginBundle\Event\PluginUpdateEvent;
|
||||
use Mautic\PluginBundle\PluginEvents;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Caution: none of the methods persist data.
|
||||
*/
|
||||
class ReloadHelper
|
||||
{
|
||||
public function __construct(
|
||||
private EventDispatcherInterface $eventDispatcher,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables plugins that are in the database but are missing in the filesystem.
|
||||
*/
|
||||
public function disableMissingPlugins(array $allPlugins, array $installedPlugins): array
|
||||
{
|
||||
$disabledPlugins = [];
|
||||
|
||||
foreach ($installedPlugins as $plugin) {
|
||||
if (!isset($allPlugins[$plugin->getBundle()]) && !$plugin->getIsMissing()) {
|
||||
// files are no longer found
|
||||
$plugin->setIsMissing(true);
|
||||
$disabledPlugins[$plugin->getBundle()] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
return $disabledPlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-enables plugins that were disabled because they were missing in the filesystem
|
||||
* but appeared in it again.
|
||||
*/
|
||||
public function enableFoundPlugins(array $allPlugins, array $installedPlugins): array
|
||||
{
|
||||
$enabledPlugins = [];
|
||||
|
||||
foreach ($installedPlugins as $plugin) {
|
||||
if (isset($allPlugins[$plugin->getBundle()]) && $plugin->getIsMissing()) {
|
||||
// files are no longer found
|
||||
$plugin->setIsMissing(false);
|
||||
$enabledPlugins[$plugin->getBundle()] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
return $enabledPlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates plugins that exist in the filesystem and in the database and their version changed.
|
||||
*
|
||||
* @param array<string, array<class-string, ClassMetadata>> $pluginMetadata
|
||||
* @param array<string, Plugin> $installedPlugins
|
||||
* @param array<string, Schema> $installedPluginsSchemas
|
||||
*/
|
||||
public function updatePlugins(array $allPlugins, array $installedPlugins, array $pluginMetadata, array $installedPluginsSchemas): array
|
||||
{
|
||||
$updatedPlugins = [];
|
||||
|
||||
foreach ($installedPlugins as $bundle => $plugin) {
|
||||
if (isset($allPlugins[$bundle])) {
|
||||
$pluginConfig = $allPlugins[$bundle];
|
||||
$oldVersion = $plugin->getVersion();
|
||||
$plugin = $this->mapConfigToPluginEntity($plugin, $pluginConfig);
|
||||
|
||||
// compare versions to see if an update is necessary
|
||||
if ((empty($oldVersion) && !empty($plugin->getVersion())) || (!empty($oldVersion) && -1 === version_compare($oldVersion, $plugin->getVersion()))) {
|
||||
$metadata = $pluginMetadata[$pluginConfig['namespace']] ?? null;
|
||||
$installedSchema = isset($installedPluginsSchemas[$pluginConfig['namespace']])
|
||||
? $installedPluginsSchemas[$allPlugins[$bundle]['namespace']] : null;
|
||||
|
||||
$event = new PluginUpdateEvent($plugin, $oldVersion, $metadata, $installedSchema);
|
||||
|
||||
$this->eventDispatcher->dispatch($event, PluginEvents::ON_PLUGIN_UPDATE);
|
||||
|
||||
unset($metadata, $installedSchema);
|
||||
|
||||
$updatedPlugins[$plugin->getBundle()] = $plugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $updatedPlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs plugins that does not exist in the database yet.
|
||||
*
|
||||
* @param array<string, array<class-string, ClassMetadata>> $pluginMetadata
|
||||
*/
|
||||
public function installPlugins(array $allPlugins, array $existingPlugins, array $pluginMetadata, array $installedPluginsSchemas): array
|
||||
{
|
||||
$installedPlugins = [];
|
||||
|
||||
foreach ($allPlugins as $bundle => $pluginConfig) {
|
||||
if (!isset($existingPlugins[$bundle])) {
|
||||
$entity = $this->mapConfigToPluginEntity(new Plugin(), $pluginConfig);
|
||||
|
||||
$metadata = $pluginMetadata[$pluginConfig['namespace']] ?? null;
|
||||
$installedSchema = null;
|
||||
|
||||
if (isset($installedPluginsSchemas[$pluginConfig['namespace']]) && 0 !== count($installedPluginsSchemas[$pluginConfig['namespace']]->getTables())) {
|
||||
$installedSchema = true;
|
||||
}
|
||||
|
||||
$event = new PluginInstallEvent($entity, $metadata, $installedSchema);
|
||||
|
||||
$this->eventDispatcher->dispatch($event, PluginEvents::ON_PLUGIN_INSTALL);
|
||||
|
||||
$installedPlugins[$entity->getBundle()] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
return $installedPlugins;
|
||||
}
|
||||
|
||||
private function mapConfigToPluginEntity(Plugin $plugin, array $config): Plugin
|
||||
{
|
||||
$plugin->setBundle($config['bundle']);
|
||||
|
||||
if (isset($config['config'])) {
|
||||
$details = $config['config'];
|
||||
|
||||
if (isset($details['version'])) {
|
||||
$plugin->setVersion($details['version']);
|
||||
}
|
||||
|
||||
$plugin->setName(
|
||||
$details['name'] ?? $config['base']
|
||||
);
|
||||
|
||||
if (isset($details['description'])) {
|
||||
$plugin->setDescription($details['description']);
|
||||
}
|
||||
|
||||
if (isset($details['author'])) {
|
||||
$plugin->setAuthor($details['author']);
|
||||
}
|
||||
}
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Helper;
|
||||
|
||||
use Mautic\PluginBundle\Integration\UnifiedIntegrationInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Portions modified from https://code.google.com/p/simple-php-oauth/.
|
||||
*/
|
||||
class oAuthHelper
|
||||
{
|
||||
private $clientId;
|
||||
|
||||
private $clientSecret;
|
||||
|
||||
private $accessToken;
|
||||
|
||||
private $accessTokenSecret;
|
||||
|
||||
private $callback;
|
||||
|
||||
private $settings;
|
||||
|
||||
public function __construct(
|
||||
UnifiedIntegrationInterface $integration,
|
||||
private ?Request $request = null,
|
||||
$settings = [],
|
||||
) {
|
||||
$clientId = $integration->getClientIdKey();
|
||||
$clientSecret = $integration->getClientSecretKey();
|
||||
$keys = $integration->getDecryptedApiKeys();
|
||||
$this->clientId = $keys[$clientId] ?? null;
|
||||
$this->clientSecret = $keys[$clientSecret] ?? null;
|
||||
$authToken = $integration->getAuthTokenKey();
|
||||
$this->accessToken = $keys[$authToken] ?? '';
|
||||
$this->accessTokenSecret = $settings['token_secret'] ?? '';
|
||||
$this->callback = $integration->getAuthCallbackUrl();
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function getAuthorizationHeader($url, $parameters, $method): array
|
||||
{
|
||||
// Get standard OAuth headers
|
||||
$headers = $this->getOauthHeaders();
|
||||
|
||||
if (!empty($this->settings['include_verifier']) && $this->request && $this->request->query->has('oauth_verifier')) {
|
||||
$headers['oauth_verifier'] = $this->request->query->get('oauth_verifier');
|
||||
}
|
||||
|
||||
if (!empty($this->settings['query'])) {
|
||||
// Include query in the base string if appended
|
||||
$parameters = array_merge($parameters, $this->settings['query']);
|
||||
}
|
||||
|
||||
if (!empty($this->settings['double_encode_basestring_parameters'])) {
|
||||
// Parameters must be encoded before going through buildBaseString
|
||||
array_walk($parameters, function (&$val, $key, $oauth): void {
|
||||
$val = $oauth->encode($val);
|
||||
}, $this);
|
||||
}
|
||||
|
||||
$signature = array_merge($headers, $parameters);
|
||||
|
||||
$base_info = $this->buildBaseString($url, $method, $signature);
|
||||
$composite_key = $this->getCompositeKey();
|
||||
$headers['oauth_signature'] = base64_encode(hash_hmac('sha1', $base_info, $composite_key, true));
|
||||
|
||||
return [$this->buildAuthorizationHeader($headers), 'Expect:'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get composite key for OAuth 1 signature signing.
|
||||
*/
|
||||
private function getCompositeKey(): string
|
||||
{
|
||||
if (strlen($this->accessTokenSecret) > 0) {
|
||||
$composite_key = $this->encode($this->clientSecret).'&'.$this->encode($this->accessTokenSecret);
|
||||
} else {
|
||||
$composite_key = $this->encode($this->clientSecret).'&';
|
||||
}
|
||||
|
||||
return $composite_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OAuth 1.0 Headers.
|
||||
*/
|
||||
private function getOauthHeaders(): array
|
||||
{
|
||||
$oauth = [
|
||||
'oauth_consumer_key' => $this->clientId,
|
||||
'oauth_nonce' => $this->generateNonce(),
|
||||
'oauth_signature_method' => 'HMAC-SHA1',
|
||||
'oauth_timestamp' => time(),
|
||||
'oauth_version' => '1.0',
|
||||
];
|
||||
|
||||
if (empty($this->settings['authorize_session']) && !empty($this->accessToken)) {
|
||||
$oauth['oauth_token'] = $this->accessToken;
|
||||
} elseif (!empty($this->settings['request_token'])) {
|
||||
// OAuth1.a access_token request that requires the retrieved request_token to be appended
|
||||
$oauth['oauth_token'] = $this->settings['request_token'];
|
||||
}
|
||||
|
||||
if (!empty($this->settings['append_callback']) && !empty($this->callback)) {
|
||||
$oauth['oauth_callback'] = urlencode($this->callback);
|
||||
}
|
||||
|
||||
return $oauth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build base string for OAuth 1 signature signing.
|
||||
*/
|
||||
private function buildBaseString($baseURI, $method, $params): string
|
||||
{
|
||||
$r = $this->normalizeParameters($params);
|
||||
|
||||
return $method.'&'.$this->encode($baseURI).'&'.$this->encode($r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build header for OAuth 1 authorization.
|
||||
*/
|
||||
private function buildAuthorizationHeader($oauth): string
|
||||
{
|
||||
$r = 'Authorization: OAuth ';
|
||||
$values = $this->normalizeParameters($oauth, true, true);
|
||||
|
||||
return $r.implode(', ', $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize parameters.
|
||||
*
|
||||
* @param bool $encode
|
||||
* @param bool $returnarray
|
||||
*
|
||||
* @return string|array<string,string>
|
||||
*/
|
||||
private function normalizeParameters($parameters, $encode = false, $returnarray = false, $normalized = [], $key = '')
|
||||
{
|
||||
// Sort by key
|
||||
ksort($parameters);
|
||||
|
||||
foreach ($parameters as $k => $v) {
|
||||
if (is_array($v)) {
|
||||
$normalized = $this->normalizeParameters($v, $encode, true, $normalized, $k);
|
||||
} else {
|
||||
if ($key) {
|
||||
// Multidimensional array; using foo=baz&foo=bar rather than foo[bar]=baz&foo[baz]=bar as this is
|
||||
// what the server expects when creating the signature
|
||||
$k = $key;
|
||||
}
|
||||
if ($encode) {
|
||||
$normalized[] = $this->encode($k).'="'.$this->encode($v).'"';
|
||||
} else {
|
||||
$normalized[] = $k.'='.$v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $returnarray ? $normalized : implode('&', $normalized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an encoded string according to the RFC3986.
|
||||
*/
|
||||
public function encode($string): string
|
||||
{
|
||||
return str_replace('%7E', '~', rawurlencode($string));
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth1.0 nonce generator.
|
||||
*
|
||||
* @param int $bits
|
||||
*/
|
||||
private function generateNonce($bits = 64): string
|
||||
{
|
||||
$result = '';
|
||||
$accumulatedBits = 0;
|
||||
$random = mt_getrandmax();
|
||||
for ($totalBits = 0; 0 != $random; $random >>= 1) {
|
||||
++$totalBits;
|
||||
}
|
||||
$usableBits = intval($totalBits / 8) * 8;
|
||||
|
||||
while ($accumulatedBits < $bits) {
|
||||
$bitsToAdd = min($totalBits - $usableBits, $bits - $accumulatedBits);
|
||||
if (0 != $bitsToAdd % 4) {
|
||||
// add bits in whole increments of 4
|
||||
$bitsToAdd += 4 - $bitsToAdd % 4;
|
||||
}
|
||||
|
||||
// isolate leftmost $bits_to_add from mt_rand() result
|
||||
$moreBits = mt_rand() & ((1 << $bitsToAdd) - 1);
|
||||
|
||||
// format as hex (this will be safe)
|
||||
$format_string = '%0'.($bitsToAdd / 4).'x';
|
||||
$result .= sprintf($format_string, $moreBits);
|
||||
$accumulatedBits += $bitsToAdd;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $data
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function sanitizeHeaderData(array $data): array
|
||||
{
|
||||
foreach ($data as &$value) {
|
||||
if (preg_match('/Authorization:\s+(Bearer|Basic)\s+(\S+)/', $value, $match)) {
|
||||
$value = sprintf('Authorization: %s %s', $match[1], '[REDACTED]');
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Integration;
|
||||
|
||||
/**
|
||||
* Used by SSO auth plugins that use credentials from the login form to authenticate.
|
||||
*/
|
||||
abstract class AbstractSsoFormIntegration extends AbstractSsoServiceIntegration
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSupportedFeatures()
|
||||
{
|
||||
return [
|
||||
'sso_form',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get form settings; authorization is not needed since it is done when a user logs in.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getFormSettings(): array
|
||||
{
|
||||
return [
|
||||
'requires_callback' => false,
|
||||
'requires_authorization' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Integration;
|
||||
|
||||
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
|
||||
use Mautic\UserBundle\Entity\Role;
|
||||
use Mautic\UserBundle\Form\Type\RoleListType;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
/**
|
||||
* Used by SSO auth plugins that use OAuth2, etc means of logins.
|
||||
*/
|
||||
abstract class AbstractSsoServiceIntegration extends AbstractIntegration
|
||||
{
|
||||
/**
|
||||
* Called after the user is authenticated with the 3rd party service to obtain the users
|
||||
* details.
|
||||
*
|
||||
* @param $response mixed Typically the response from request to authenticating service
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function getUser($response);
|
||||
|
||||
/**
|
||||
* Get the user role for new users.
|
||||
*
|
||||
* @return bool|\Doctrine\Common\Proxy\Proxy|object|null
|
||||
*
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
*/
|
||||
public function getUserRole()
|
||||
{
|
||||
$featureSettings = $this->settings->getFeatureSettings();
|
||||
|
||||
$role = $featureSettings['new_user_role'] ?? false;
|
||||
|
||||
if ($role) {
|
||||
return $this->em->getReference(Role::class, $role);
|
||||
}
|
||||
|
||||
throw new AuthenticationException('mautic.integration.sso.error.no_role');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if a new user should be created if authenticated and not found locally.
|
||||
*/
|
||||
public function shouldAutoCreateNewUser(): bool
|
||||
{
|
||||
$featureSettings = $this->settings->getFeatureSettings();
|
||||
|
||||
return isset($featureSettings['auto_create_user']) && (bool) $featureSettings['auto_create_user'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the callback URL to sso_login.
|
||||
*/
|
||||
public function getAuthCallbackUrl()
|
||||
{
|
||||
return $this->router->generate('mautic_sso_login_check',
|
||||
['integration' => $this->getName()],
|
||||
\Symfony\Component\Routing\Generator\UrlGeneratorInterface::ABSOLUTE_URL // absolute
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $settings
|
||||
* @param array $parameters
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function ssoAuthCallback($settings = [], $parameters = [])
|
||||
{
|
||||
$response = $this->authCallback($settings, $parameters);
|
||||
|
||||
// Get user data
|
||||
return $this->getUser($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't save the keys as they are only used to validate user login.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function extractAuthKeys($data, $tokenOverride = null)
|
||||
{
|
||||
// Prepare the keys for extraction such as renaming, setting expiry, etc
|
||||
$data = $this->prepareResponseForExtraction($data);
|
||||
|
||||
// parse the response
|
||||
$authTokenKey = $tokenOverride ?: $this->getAuthTokenKey();
|
||||
if (is_array($data) && isset($data[$authTokenKey])) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$error = $this->getErrorsFromResponse($data);
|
||||
if (empty($error)) {
|
||||
$error = $this->translator->trans('mautic.integration.error.genericerror', [], 'flashes');
|
||||
}
|
||||
|
||||
throw new AuthenticationException($error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSupportedFeatures()
|
||||
{
|
||||
return [
|
||||
'sso_service',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get form settings; authorization is not needed since it is done when a user logs in.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getFormSettings(): array
|
||||
{
|
||||
return [
|
||||
'requires_callback' => true,
|
||||
'requires_authorization' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Form|\Symfony\Component\Form\FormBuilder $builder
|
||||
* @param array $data
|
||||
* @param string $formArea
|
||||
*/
|
||||
public function appendToForm(&$builder, $data, $formArea): void
|
||||
{
|
||||
if ('features' == $formArea) {
|
||||
$builder->add('auto_create_user',
|
||||
YesNoButtonGroupType::class,
|
||||
[
|
||||
'label' => 'mautic.integration.sso.auto_create_user',
|
||||
'data' => isset($data['auto_create_user']) && (bool) $data['auto_create_user'],
|
||||
'attr' => [
|
||||
'tooltip' => 'mautic.integration.sso.auto_create_user.tooltip',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'new_user_role',
|
||||
RoleListType::class,
|
||||
[
|
||||
'label' => 'mautic.integration.sso.new_user_role',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'tooltip' => 'mautic.integration.sso.new_user_role.tooltip',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Integration;
|
||||
|
||||
class IntegrationObject
|
||||
{
|
||||
/**
|
||||
* @param string $type
|
||||
* @param string $internalType
|
||||
*/
|
||||
public function __construct(
|
||||
private $type,
|
||||
private $internalType,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getInternalType()
|
||||
{
|
||||
return $this->internalType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Integration;
|
||||
|
||||
/**
|
||||
* Interface UnifiedIntegrationInterface is used for type hinting.
|
||||
*/
|
||||
interface UnifiedIntegrationInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle;
|
||||
|
||||
use Mautic\PluginBundle\Tests\DependencyInjection\Compiler\TestPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class MauticPluginBundle extends Bundle
|
||||
{
|
||||
public function build(ContainerBuilder $container): void
|
||||
{
|
||||
if ('test' === $container->getParameter('kernel.environment')) {
|
||||
$container->addCompilerPass(new TestPass());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Model;
|
||||
|
||||
use Mautic\CoreBundle\Model\FormModel;
|
||||
use Mautic\PluginBundle\Entity\IntegrationEntity;
|
||||
use Mautic\PluginBundle\Integration\IntegrationObject;
|
||||
|
||||
/**
|
||||
* @extends FormModel<IntegrationEntity>
|
||||
*/
|
||||
class IntegrationEntityModel extends FormModel
|
||||
{
|
||||
public function getIntegrationEntityRepository()
|
||||
{
|
||||
return $this->em->getRepository(IntegrationEntity::class);
|
||||
}
|
||||
|
||||
public function logDataSync(IntegrationObject $integrationObject): void
|
||||
{
|
||||
}
|
||||
|
||||
public function getSyncedRecords(IntegrationObject $integrationObject, $integrationName, $recordList, $internalEntityId = null)
|
||||
{
|
||||
if (!$formattedRecords = $this->formatListOfContacts($recordList)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$integrationEntityRepo = $this->getIntegrationEntityRepository();
|
||||
|
||||
return $integrationEntityRepo->getIntegrationsEntityId(
|
||||
$integrationName,
|
||||
$integrationObject->getType(),
|
||||
$integrationObject->getInternalType(),
|
||||
$internalEntityId,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
$formattedRecords
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed, array<'id', mixed>>
|
||||
*/
|
||||
public function getRecordList($integrationObject): array
|
||||
{
|
||||
$recordList = [];
|
||||
|
||||
foreach ($integrationObject->getRecords() as $record) {
|
||||
$recordList[$record['Id']] = [
|
||||
'id' => $record['Id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $recordList;
|
||||
}
|
||||
|
||||
public function formatListOfContacts($recordList): ?string
|
||||
{
|
||||
if (empty($recordList)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$csList = is_array($recordList) ? implode('", "', array_keys($recordList)) : $recordList;
|
||||
|
||||
return '"'.$csList.'"';
|
||||
}
|
||||
|
||||
public function getMauticContactsById($mauticContactIds, $integrationName, $internalObject)
|
||||
{
|
||||
if (!$formattedRecords = $this->formatListOfContacts($mauticContactIds)) {
|
||||
return [];
|
||||
}
|
||||
$integrationEntityRepo = $this->getIntegrationEntityRepository();
|
||||
|
||||
return $integrationEntityRepo->getIntegrationsEntityId(
|
||||
$integrationName,
|
||||
null,
|
||||
$internalObject,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
$formattedRecords
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return IntegrationEntity|null
|
||||
*/
|
||||
public function getEntityByIdAndSetSyncDate($id, \DateTime $dateTime)
|
||||
{
|
||||
$entity = $this->getIntegrationEntityRepository()->find($id);
|
||||
if ($entity) {
|
||||
$entity->setLastSyncDate($dateTime);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Model;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Mautic\CoreBundle\Helper\BundleHelper;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Model\FormModel;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Field\FieldList;
|
||||
use Mautic\LeadBundle\Model\FieldModel;
|
||||
use Mautic\PluginBundle\Entity\Plugin;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
/**
|
||||
* @extends FormModel<Plugin>
|
||||
*/
|
||||
class PluginModel extends FormModel
|
||||
{
|
||||
public function __construct(
|
||||
protected FieldModel $leadFieldModel,
|
||||
private FieldList $fieldList,
|
||||
CoreParametersHelper $coreParametersHelper,
|
||||
private BundleHelper $bundleHelper,
|
||||
EntityManager $em,
|
||||
CorePermissions $security,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
UrlGeneratorInterface $router,
|
||||
Translator $translator,
|
||||
UserHelper $userHelper,
|
||||
LoggerInterface $mauticLogger,
|
||||
) {
|
||||
parent::__construct($em, $security, $dispatcher, $router, $translator, $userHelper, $mauticLogger, $coreParametersHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Mautic\PluginBundle\Entity\PluginRepository
|
||||
*/
|
||||
public function getRepository()
|
||||
{
|
||||
return $this->em->getRepository(Plugin::class);
|
||||
}
|
||||
|
||||
public function getIntegrationEntityRepository()
|
||||
{
|
||||
return $this->em->getRepository(\Mautic\PluginBundle\Entity\IntegrationEntity::class);
|
||||
}
|
||||
|
||||
public function getPermissionBase(): string
|
||||
{
|
||||
return 'plugin:plugins';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get lead fields used in selects/matching.
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getLeadFields(): array
|
||||
{
|
||||
return $this->fieldList->getFieldList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Company fields.
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getCompanyFields(): array
|
||||
{
|
||||
return $this->fieldList->getFieldList(true, true, ['isPublished' => true, 'object' => 'company']);
|
||||
}
|
||||
|
||||
public function saveFeatureSettings($entity): void
|
||||
{
|
||||
$this->em->persist($entity);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads config.php arrays for all plugins.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllPluginsConfig()
|
||||
{
|
||||
return $this->bundleHelper->getPluginBundles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all installed Plugin entities from database.
|
||||
*
|
||||
* @return Plugin[]
|
||||
*/
|
||||
public function getInstalledPlugins()
|
||||
{
|
||||
return $this->getEntities(
|
||||
[
|
||||
'index' => 'bundle',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns metadata for all plugins.
|
||||
*
|
||||
* @return array<string, array<class-string, ClassMetadata>>
|
||||
*/
|
||||
public function getPluginsMetadata(): array
|
||||
{
|
||||
$allMetadata = $this->em->getMetadataFactory()->getAllMetadata();
|
||||
$pluginsMetadata = [];
|
||||
|
||||
foreach ($allMetadata as $meta) {
|
||||
$namespace = $meta->namespace;
|
||||
|
||||
if (str_contains($namespace, 'MauticPlugin')) {
|
||||
$bundleName = preg_replace('/\\\Entity$/', '', $namespace);
|
||||
if (!isset($pluginsMetadata[$bundleName])) {
|
||||
$pluginsMetadata[$bundleName] = [];
|
||||
}
|
||||
$pluginsMetadata[$bundleName][$meta->getName()] = $meta;
|
||||
}
|
||||
}
|
||||
|
||||
return $pluginsMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all tables of installed plugins.
|
||||
*
|
||||
* @param array<string, array<class-string, ClassMetadata>> $pluginsMetadata
|
||||
*
|
||||
* @return array<string, array<int, Table>>
|
||||
*/
|
||||
public function getInstalledPluginTables(array $pluginsMetadata): array
|
||||
{
|
||||
$currentSchema = $this->em->getConnection()->createSchemaManager()->introspectSchema();
|
||||
$installedPluginsTables = [];
|
||||
|
||||
foreach ($pluginsMetadata as $bundleName => $pluginMetadata) {
|
||||
foreach ($pluginMetadata as $meta) {
|
||||
$table = $meta->getTableName();
|
||||
|
||||
if (!isset($installedPluginsTables[$bundleName])) {
|
||||
$installedPluginsTables[$bundleName] = [];
|
||||
}
|
||||
|
||||
if ($currentSchema->hasTable($table)) {
|
||||
$installedPluginsTables[$bundleName][] = $currentSchema->getTable($table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $installedPluginsTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates new Schema objects for all installed plugins.
|
||||
*/
|
||||
public function createPluginSchemas(array $installedPluginsTables): array
|
||||
{
|
||||
$installedPluginsSchemas = [];
|
||||
foreach ($installedPluginsTables as $bundleName => $tables) {
|
||||
$installedPluginsSchemas[$bundleName] = new Schema($tables);
|
||||
}
|
||||
|
||||
return $installedPluginsSchemas;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle;
|
||||
|
||||
/**
|
||||
* Events available for PluginEvents.
|
||||
*/
|
||||
final class PluginEvents
|
||||
{
|
||||
/**
|
||||
* The mautic.plugin_on_integration_config_save event is dispatched when an integration's configuration is saved.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginIntegrationEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PLUGIN_ON_INTEGRATION_CONFIG_SAVE = 'mautic.plugin_on_integration_config_save';
|
||||
|
||||
/**
|
||||
* The mautic.plugin_on_integration_keys_encrypt event is dispatched prior to encrypting keys to be stored into the database.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginIntegrationKeyEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PLUGIN_ON_INTEGRATION_KEYS_ENCRYPT = 'mautic.plugin_on_integration_keys_encrypt';
|
||||
|
||||
/**
|
||||
* The mautic.plugin_on_integration_keys_decrypt event is dispatched after fetching and decrypting keys from the database.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginIntegrationKeyEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PLUGIN_ON_INTEGRATION_KEYS_DECRYPT = 'mautic.plugin_on_integration_keys_decrypt';
|
||||
|
||||
/**
|
||||
* The mautic.plugin_on_integration_keys_merge event is dispatched after new keys are merged into existing ones.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginIntegrationKeyEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PLUGIN_ON_INTEGRATION_KEYS_MERGE = 'mautic.plugin_on_integration_keys_merge';
|
||||
|
||||
/**
|
||||
* The mautic.plugin_on_integration_request event is dispatched before a request is made.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginIntegrationRequestEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PLUGIN_ON_INTEGRATION_REQUEST = 'mautic.plugin_on_integration_request';
|
||||
|
||||
/**
|
||||
* The mautic.plugin_on_integration_response event is dispatched after a request is made.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginIntegrationRequestEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PLUGIN_ON_INTEGRATION_RESPONSE = 'mautic.plugin_on_integration_response';
|
||||
|
||||
/**
|
||||
* The mautic.plugin_on_integration_auth_redirect event is dispatched when an authorization URL is generated and before the user is redirected to it.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginIntegrationAuthRedirectEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PLUGIN_ON_INTEGRATION_AUTH_REDIRECT = 'mautic.plugin_on_integration_auth_redirect';
|
||||
|
||||
/**
|
||||
* The mautic.plugin.on_campaign_trigger_action event is fired when the campaign action triggers.
|
||||
*
|
||||
* The event listener receives a
|
||||
* Mautic\CampaignBundle\Event\CampaignExecutionEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ON_CAMPAIGN_TRIGGER_ACTION = 'mautic.plugin.on_campaign_trigger_action';
|
||||
|
||||
/**
|
||||
* The mautic.plugin_on_integration_get_auth_callback_url event is dispatched when generating the redirect/callback URL.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginIntegrationAuthCallbackUrlEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PLUGIN_ON_INTEGRATION_GET_AUTH_CALLBACK_URL = 'mautic.plugin_on_integration_get_auth_callback_url';
|
||||
|
||||
/**
|
||||
* The mautic.plugin_on_integration_form_display event is dispatched when fetching display settings for the integration's config form.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginIntegrationFormDisplayEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PLUGIN_ON_INTEGRATION_FORM_DISPLAY = 'mautic.plugin_on_integration_form_display';
|
||||
|
||||
/**
|
||||
* The mautic.plugin_on_integration_form_build event is dispatched when building an integration's config form.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginIntegrationFormBuildEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PLUGIN_ON_INTEGRATION_FORM_BUILD = 'mautic.plugin_on_integration_form_build';
|
||||
|
||||
/**
|
||||
* The mautic.plugin.on_form_submit_action_triggered event is dispatched when a plugin related submit action is executed.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginIntegrationFormBuildEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ON_FORM_SUBMIT_ACTION_TRIGGERED = 'mautic.plugin.on_form_submit_action_triggered';
|
||||
|
||||
/**
|
||||
* The mautic.plugin.on_plugin_update event is dispatched when a plugin is updated.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginUpdateEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ON_PLUGIN_UPDATE = 'mautic.plugin.on_plugin_update';
|
||||
|
||||
/**
|
||||
* The mautic.plugin.on_plugin_install event is dispatched when a plugin is installed.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginInstallEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ON_PLUGIN_INSTALL = 'mautic.plugin.on_plugin_install';
|
||||
|
||||
/**
|
||||
* The mautic.plugin.is_published_state_changing event is dispatched when a user tries to change the published state of a plugin.
|
||||
*
|
||||
* The event listener receives a Mautic\PluginBundle\Event\PluginPublishedEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PLUGIN_IS_PUBLISHED_STATE_CHANGING= 'mautic.plugin.is_published_state_changing';
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{% extends '@MauticForm/Action/base_form_action.html.twig' %}
|
||||
|
||||
{% block action_label %}
|
||||
{% include '@MauticCore/Helper/_tag.html.twig' with {
|
||||
tags: [
|
||||
{
|
||||
label: action.properties.integration,
|
||||
icon: 'ri-plug-line',
|
||||
color: 'warm-gray',
|
||||
attributes: securityIsGranted('plugin:plugins:manage') ? {
|
||||
href: path('mautic_plugin_index'),
|
||||
'target': '_blank'
|
||||
} : {}
|
||||
}
|
||||
]
|
||||
} %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,8 @@
|
||||
{% set contentOnly = true %}
|
||||
{% extends '@MauticCore/Default/content.html.twig' %}
|
||||
|
||||
{% block headerTitle '' %}
|
||||
|
||||
{% block content %}
|
||||
{{- assetAddScriptDeclaration('Mautic.handleIntegrationCallback("'~integration~'", "'~csrfToken~'", "'~code~'", "'~callbackUrl~'", "'~clientIdKey~'", "'~clientSecretKey~'");', 'bodyClose') -}}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,36 @@
|
||||
{% set contentOnly = true %}
|
||||
{% extends '@MauticCore/Default/content.html.twig' %}
|
||||
|
||||
{% block mauticContent 'social' %}
|
||||
{% block headerTitle '' %}
|
||||
|
||||
{% block content %}
|
||||
{% set data = data|json_encode %}
|
||||
<script>
|
||||
function postFormHandler() {
|
||||
var opener = window.opener;
|
||||
if (opener && typeof opener.postAuthCallback == 'function') {
|
||||
opener.postAuthCallback({$data});
|
||||
} else {
|
||||
Mautic.refreshIntegrationForm();
|
||||
}
|
||||
window.close()
|
||||
}
|
||||
{% if message is not empty and 'success' is same as alert %}
|
||||
(function() { postFormHandler(); })();
|
||||
{% endif %}
|
||||
</script>
|
||||
|
||||
{% if message is not empty %}
|
||||
<div class="alert alert-{{ alert }}">
|
||||
{{ message|purify }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12 text-center">
|
||||
<a class="btn btn-lg btn-primary" href="javascript:void(0);" onclick="postFormHandler();">
|
||||
{{ 'mautic.integration.closewindow'|trans }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,226 @@
|
||||
{% block integration_company_fields_row %}
|
||||
{%- set containerId = 'companyFieldsContainer' -%}
|
||||
{%- set numberOfFields = (form.offsetExists('update_mautic_company1')) ? 5 : 4 -%}
|
||||
{%- set object = 'company' -%}
|
||||
{{- block('fields_row') -}}
|
||||
{% endblock %}
|
||||
|
||||
{% block integration_fields_row %}
|
||||
{%- set containerId = 'leadFieldsContainer' -%}
|
||||
{%- set numberOfFields = (form.offsetExists('update_mautic1')) ? 5 : 4 -%}
|
||||
{%- set object = 'lead' -%}
|
||||
{{- block('fields_row') -}}
|
||||
{% endblock %}
|
||||
|
||||
{% block _integration_details_supportedFeatures_row %}
|
||||
{% set attr = form.vars.attr %}
|
||||
{% set builtin = formSettings.builtin_features|default([]) %}
|
||||
{% set showLabel = builtin|length != form.children|length %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% if showLabel %}
|
||||
<h4 class="mb-sm">{{ form.vars['label']|trans }}</h4>
|
||||
{% endif %}
|
||||
{% if formNotes.supported_features is defined and formNotes.supported_features is not empty %}
|
||||
<div class="alert alert-{{ formNotes['supported_features']['type'] }}">
|
||||
{{ formNotes['supported_features']['note']|trans }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for child in form.children %}
|
||||
{% if child.vars.value not in builtin %}
|
||||
<div class="checkbox" >
|
||||
<label>
|
||||
{{ form_widget(child, {'attr': attr}) }}
|
||||
{{ child.vars.label|trans }}
|
||||
</label>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ child.isRendered() }}
|
||||
<input type="hidden" id="{{ child.vars['id'] }}" name="{{ child.vars['full_name'] }}" value="{{ child.vars['value']|e }}" />
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block _integration_details_featureSettings_row %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h4 class="mb-sm mt-lg">
|
||||
{{ form.vars['label']|trans }}
|
||||
</h4>
|
||||
{% if formNotes.features is defined and formNotes.features is not empty %}
|
||||
<div class="alert alert-{{ formNotes['features']['type'] }}">
|
||||
{{ formNotes['features']['note']|trans }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ form_widget(form) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block fields_row %}
|
||||
{#
|
||||
Variables
|
||||
- containerId (required, string)
|
||||
- numberOfFields (required, int)
|
||||
- object (required, string)
|
||||
- form
|
||||
- specialInstructions (optional)
|
||||
If set, `alertType` is required
|
||||
- alertType (conditional)
|
||||
#}
|
||||
{# @var int $numberOfFields #}
|
||||
{%- set rowCount = 0 -%}
|
||||
{%- set indexCount = 1 -%}
|
||||
<div class="row fields-container" id="{{ containerId }}">
|
||||
|
||||
{% if specialInstructions is defined and specialInstructions is not empty %}
|
||||
<div class="alert alert-{{ alertType }}">
|
||||
{{- specialInstructions|trans -}}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if form.vars.errors|length > 0 %}
|
||||
<div class="alert alert-danger">
|
||||
{% for error in form.vars.errors %}
|
||||
<p>{{ error.message }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="{{ object }}-field form-group col-xs-12">
|
||||
<div class="row">
|
||||
<div class="mb-xs col-sm-{{ (5 == numberOfFields) ? 5 : 6 }} text-center"><h4>{{ 'mautic.plugins.integration.fields'|trans }}</h4></div>
|
||||
{% if 5 == numberOfFields -%}
|
||||
<div class="col-sm-2"></div>
|
||||
{%- endif %}
|
||||
<div class="mb-xs col-sm-{{ (5 == numberOfFields) ? 5 : 6 }} text-center"><h4>{{ 'mautic.plugins.mautic.fields'|trans }}</h4></div>
|
||||
</div>
|
||||
{% for child in form.children %}
|
||||
{% set selected = false %}
|
||||
{% set isRequired = child.vars.attr['data-required'] is defined and child.vars.attr['data-required'] is not same as 0 %}
|
||||
{% if rowCount is divisible by(numberOfFields) %}
|
||||
<div id="{{ object }}-{{ rowCount }}" class="field-container row {% if 5 != numberOfFields %}pb-md{% endif %}">
|
||||
{% endif %}
|
||||
{% set rowCount = rowCount + 1 %}
|
||||
{% if 'hidden' == child.vars.block_prefixes[1] %}
|
||||
{{ form_row(child) }}
|
||||
{% else %}
|
||||
{% set class = '' %}
|
||||
{% set remainder = rowCount % numberOfFields %}
|
||||
{% if 1 == remainder or 3 == remainder %}
|
||||
{% set class = (5 == numberOfFields) ? 'col-sm-5' : 'col-sm-6' %}
|
||||
{% elseif 2 == remainder %}
|
||||
{% set class = (5 == numberOfFields) ? 'col-sm-2' : 'col-sm-6' %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if ('label_' ~ indexCount) == child.vars.name %}
|
||||
{% if isRequired %}
|
||||
{% set name = child.vars.full_name %}
|
||||
<input type="hidden" value="{{ child.vars['attr']['data-label'] }}" name="{{ name }}" />
|
||||
{% endif %}
|
||||
<div class="pl-xs pr-xs {{ class }} {% if isRequired %}has-error{% endif %}">
|
||||
<div class="placeholder" data-placeholder="{{ child.vars.attr.placeholder }}">
|
||||
<input type="text"
|
||||
id="{{ child.vars.id }}"
|
||||
name="{{ child.vars.full_name }}"
|
||||
class="{{ child.vars.attr.class }}"
|
||||
value="{{ child.vars.attr['data-label']|default('')|e }}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{%- if 'update_mautic' in child.vars.name -%}
|
||||
<div class="pr-xs {{ class }}" style="padding-left: 8px;" data-toggle="tooltip" title="{{ 'mautic.plugin.direction.data.update'|trans }}">
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-12 ">
|
||||
<div class="choice-wrapper">
|
||||
<div class="btn-group btn-block" data-toggle="buttons" {% if child.vars['attr']['forceDirection'] %}data-force-direction="1"{% endif %}>
|
||||
{% set checked = '0' == child.vars.value %}
|
||||
<label class="btn-arrow{{ indexCount }} btn btn-ghost {% if checked %}active{% endif %} {% if child.vars['attr']['disabled'] %}disabled{% endif %}">
|
||||
<input type="radio"
|
||||
id="{{ child.vars['id'] }}_0"
|
||||
name="{{ child.vars['full_name'] }}"
|
||||
title=""
|
||||
autocomplete="false"
|
||||
value="0"
|
||||
onchange="Mautic.matchedFields({{ indexCount }}, '{{ object }}', '{{ integration }}')"
|
||||
{% if checked %}checked="checked"{% endif %}
|
||||
{% if child.vars['attr']['disabled'] %}disabled{% endif %}>
|
||||
<btn class="btn-nospin ri-arrow-left-circle-line"></btn>
|
||||
</label>
|
||||
{% set checked = '1' == child.vars.value %}
|
||||
<label class="btn-arrow{{ indexCount }} btn btn-ghost {% if checked %}active{% endif %} {% if child.vars['attr']['disabled'] %}disabled{% endif %}">
|
||||
<input type="radio" id="{{ child.vars['id'] }}_1"
|
||||
name="{{ child.vars['full_name'] }}"
|
||||
title=""
|
||||
autocomplete="false"
|
||||
value="1"
|
||||
onchange="Mautic.matchedFields({{ indexCount }}, '{{ object }}', '{{ integration }}')"
|
||||
{% if '1' == child.vars['value'] %}checked="checked"{% endif %}
|
||||
{% if child.vars['attr']['disabled'] %}disabled{% endif %}>
|
||||
<btn class="btn-nospin ri-arrow-right-circle-line"></btn>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
||||
{% if ('m_' ~ indexCount) == child.vars.name %}
|
||||
<div class="pl-xs pr-xs {{ class }}">
|
||||
{% if isRequired %}<div class="has-errors">{% endif %}
|
||||
<select id="{{ child.vars.id }}"
|
||||
name="{{ child.vars.full_name }}"
|
||||
class="{{ child.vars.attr.class }}"
|
||||
data-placeholder=""
|
||||
autocomplete="false" onchange="Mautic.matchedFields({{ indexCount }}, '{{ object }}', '{{ integration }}')">
|
||||
<option value=""></option>
|
||||
{%- set mauticChoices = child.vars.attr['data-choices'] -%}
|
||||
{% for keyLabel, options in mauticChoices %}
|
||||
{% if options is iterable %}
|
||||
<optgroup label="{{ keyLabel }}">
|
||||
{% for optionLabel, keyValue in options %}
|
||||
<option value="{{ keyValue|e }}" {% if keyValue == child.vars.data %}selected{% set selected = true %}{% elseif selected is defined and selected is empty and '-1' == keyValue %}selected{% endif %}>
|
||||
{{- optionLabel|trans -}}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% else %}
|
||||
<option value="{{ options|e }}"{% if options == child.vars.data %}selected{% set selected = true %}{% elseif selected is defined and selected is empty and '-1' == options %}selected{% endif %}>
|
||||
{{- keyLabel|trans -}}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if isRequired %}</div>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if rowCount is divisible by(numberOfFields) %}
|
||||
</div>
|
||||
{% set indexCount = indexCount + 1 %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if (indexCount - 1) < totalFields %}
|
||||
<div class="panel-footer">
|
||||
{{ include('@MauticCore/Helper/pagination.html.twig', {
|
||||
'page': page,
|
||||
'fixedPages': fixedPageNum,
|
||||
'fixedLimit': true,
|
||||
'target': '#IntegrationEditModal',
|
||||
'totalItems': totalFields,
|
||||
'jsCallback': 'Mautic.getIntegrationFields',
|
||||
'jsArguments': [
|
||||
{
|
||||
'object': object,
|
||||
'integration': integration,
|
||||
},
|
||||
],
|
||||
}) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,166 @@
|
||||
{#
|
||||
Variables
|
||||
- form
|
||||
- description
|
||||
- formSettings
|
||||
- formNotes
|
||||
- callbackUrl
|
||||
- activeTab
|
||||
- formThemes (array)
|
||||
May include one or more form themes that need to be applied
|
||||
#}
|
||||
{%- form_theme form with formThemes -%}
|
||||
<!-- form themes: {{ formThemes|join(', ') }} -->
|
||||
{%- set nSupportedFeatures = form.supportedFeatures is defined ? form.supportedFeatures|length : 0 -%}
|
||||
{%- set hasSupportedFeatures = nSupportedFeatures > 0 -%}
|
||||
{%- set nFeatureSettings = form.featureSettings is defined ? form.featureSettings|length : 0 -%}
|
||||
{%- set hasFields = ((formSettings.dynamic_contact_fields is defined and formSettings.dynamic_contact_fields is not empty) or form.featureSettings is defined) and form.featureSettings.leadFields|length > 0
|
||||
-%}
|
||||
{# Unset if set to prevent features tab from showing when there's no feature to show #}
|
||||
{%- if not hasFields %}{% do form.featureSettings.leadFields.setRendered() %}{% set nFeatureSettings = nFeatureSettings - 1 %}{% endif -%}
|
||||
{%- set hideContactFieldTab = (hasFields and formSettings.dynamic_contact_fields is defined and formSettings.dynamic_contact_fields is not empty and form.featureSettings.leadFields|length == 0) -%}
|
||||
{%- set hasFeatureSettings = (
|
||||
form.featureSettings is defined
|
||||
and (
|
||||
(hasFields and nFeatureSettings > 1)
|
||||
or
|
||||
(not hasFields and nFeatureSettings > 0)
|
||||
)
|
||||
) -%}
|
||||
{%- if not hasFeatureSettings and form.featureSettings is defined %}{% do form.featureSettings.setRendered() %}{% endif -%}
|
||||
{%- set hasCompanyFields = form.featureSettings.companyFields is defined and form.featureSettings.companyFields|length > 0 -%}
|
||||
{%- set companyFieldHtml = hasCompanyFields ? form_row(form.featureSettings.companyFields) : '' -%}
|
||||
{%- set fieldHtml = hasFields ? form_row(form.featureSettings.leadFields) -%}
|
||||
{%- set fieldLabel = hasFields ? form.featureSettings.leadFields.vars.label -%}
|
||||
{%- set fieldTabClass = (hasFields and hideContactFieldTab == false) ?: 'hide' -%}
|
||||
{%- set hasLeadFieldErrors = hasFields and formContainsErrors(form.featureSettings.leadFields) -%}
|
||||
{%- set hasCompanyFieldErrors = hasCompanyFields and formContainsErrors(form.featureSettings.companyFields) -%}
|
||||
{%- if form.featureSettings.leadFields is defined %}{% do form.featureSettings.leadFields.setRendered() %}{% endif -%}
|
||||
{%- if form.featureSettings.companyFields is defined %}{% do form.featureSettings.companyFields.setRendered() %}{% endif -%}
|
||||
|
||||
{%- if description is not empty -%}
|
||||
<div class="alert alert-info">
|
||||
{{- description|purify -}}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
||||
<!-- tabs controls -->
|
||||
<ul class="nav nav-tabs nav-tabs-line">
|
||||
<li class="{% if 'details-container' == activeTab %}active{% endif %}" id="details-tab">
|
||||
<a href="#details-container" role="tab" data-toggle="tab">{{ 'mautic.plugin.integration.tab.details'|trans }}</a>
|
||||
</li>
|
||||
|
||||
{%- if hasSupportedFeatures or hasFeatureSettings -%}
|
||||
<li class="" id="features-tab">
|
||||
<a href="#features-container" role="tab" data-toggle="tab">
|
||||
{{- 'mautic.plugin.integration.tab.features'|trans -}}
|
||||
{%- if (hasSupportedFeatures and formContainsErrors(form.supportedFeatures)) or (hasFeatureSettings and formContainsErrors(form.featureSettings, ['leadFields'])) -%}
|
||||
<i class="ri-fw ri-alert-fill text-danger"></i>
|
||||
{%- endif -%}
|
||||
</a>
|
||||
</li>
|
||||
{%- endif -%}
|
||||
|
||||
{%- if hasFields -%}
|
||||
<li class="{{ fieldTabClass }} {% if 'leadFieldsContainer' == activeTab %}active{% endif %}" id="fields-tab">
|
||||
<a href="#fields-container" role="tab" data-toggle="tab">
|
||||
{{- 'mautic.plugin.integration.tab.fieldmapping'|trans -}}
|
||||
{%- if hasLeadFieldErrors -%}
|
||||
<i class="ri-fw ri-alert-fill text-danger"></i>
|
||||
{%- endif -%}
|
||||
</a>
|
||||
</li>
|
||||
{%- endif -%}
|
||||
|
||||
{%- if companyFieldHtml is not empty -%}
|
||||
<li class="{{ fieldTabClass }} {% if 'companyFieldsContainer' == activeTab %}active{% endif %}" id="company-fields-tab">
|
||||
<a href="#company-fields-container" role="tab" data-toggle="tab">
|
||||
{{- 'mautic.plugin.integration.tab.companyfieldmapping'|trans -}}
|
||||
{%- if hasCompanyFieldErrors -%}
|
||||
<i class="ri-fw ri-alert-fill text-danger"></i>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{%- endif -%}
|
||||
</ul>
|
||||
<!--/ tabs controls -->
|
||||
|
||||
{{- form_start(form) -}}
|
||||
<div class="tab-content pa-md">
|
||||
<div class="tab-pane fade {% if 'details-container' == activeTab %}in active{% endif %} bdr-w-0" id="details-container">
|
||||
{{- form_row(form.isPublished) -}}
|
||||
{%- if form.virtual is defined %}{{ form_row(form.virtual) }}{% endif %}
|
||||
{%- if form.apiKeys is defined -%}
|
||||
{{- form_row(form.apiKeys) -}}
|
||||
{%- if formNotes.authorization is defined -%}
|
||||
<div class="alert alert-{{ formNotes.authorization.type }}">
|
||||
{{- formNotes.authorization.note|purify -}}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
||||
<div class="row">
|
||||
{%- if form.apiKeys|length > 0 and callbackUrl is not empty -%}
|
||||
<div class="well well-sm">
|
||||
{{- 'mautic.integration.callbackuri'|trans }}<br/>
|
||||
{{ include('@MauticCore/Components/code-snippet.html.twig', {
|
||||
variant: 'single',
|
||||
innerText: callbackUrl,
|
||||
className: 'layer-two mt-sm'
|
||||
}) }}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
|
||||
{%- if form.authButton is defined -%}
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-center">
|
||||
{{- form_widget(form.authButton, {'attr': {'class': 'btn btn-success btn-lg'}}) -}}
|
||||
</div>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- if formNotes.custom is defined -%}
|
||||
{%- if formNotes.custom is string -%}
|
||||
{{ formNotes.custom|purify }}
|
||||
{%- elseif formNotes.custom.custom is defined and formNotes.custom.template is string -%}
|
||||
<!-- start: "{{ formNotes.custom.template }}" -->
|
||||
{{ include(formNotes.custom.template, formNotes.custom.parameters|default([]), ignore_missing=true) }}
|
||||
<!-- end: "{{ formNotes.custom.template }}" -->
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
</div>
|
||||
|
||||
{%- if hasSupportedFeatures or hasFeatureSettings -%}
|
||||
<div class="tab-pane fade bdr-w-0" id="features-container">
|
||||
{%- if hasSupportedFeatures -%}
|
||||
{{- form_row(form.supportedFeatures, {
|
||||
'formSettings': formSettings,
|
||||
'formNotes': formNotes,
|
||||
}) -}}
|
||||
{%- endif -%}
|
||||
|
||||
{%- if hasFeatureSettings -%}
|
||||
{{ form_row(form.featureSettings, {
|
||||
'formSettings': formSettings,
|
||||
'formNotes': formNotes,
|
||||
}) -}}
|
||||
{%- endif -%}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
||||
{%- if hasFields -%}
|
||||
<div class="tab-pane fade {% if 'leadFieldsContainer' == activeTab %}in active{% endif %} bdr-w-0" id="fields-container">
|
||||
<h4 class="mb-sm">{{ fieldLabel|trans }}</h4>
|
||||
{{- fieldHtml|raw -}}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
||||
{%- if hasCompanyFields -%}
|
||||
<div class="tab-pane fade {% if 'companyFieldsContainer' == activeTab %}in active{% endif %} bdr-w-0" id="company-fields-container">
|
||||
<h4 class="mb-sm">{{ 'mautic.integration.companyfield_matches'|trans }}</h4>
|
||||
{{- companyFieldHtml|raw -}}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
@@ -0,0 +1,102 @@
|
||||
{#
|
||||
Variables
|
||||
- items
|
||||
- tmpl
|
||||
- pluginFilter
|
||||
- plugins
|
||||
#}
|
||||
{% set isIndex = ('index' == tmpl) %}
|
||||
{% set tmpl = 'list' %}
|
||||
{% set filterValue = pluginFilter.id|default(null) %}
|
||||
{% extends isIndex ? '@MauticCore/Default/content.html.twig' : '@MauticCore/Default/raw_output.html.twig' %}
|
||||
|
||||
{% block mauticContent 'integration' %}
|
||||
|
||||
{% block headerTitle %}
|
||||
{{ 'mautic.plugin.manage.plugins'|trans }} {% if pluginFilter %}- {{ pluginFilter.name }}{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block actions %}
|
||||
{{ include('@MauticCore/Helper/page_actions.html.twig', {
|
||||
'customButtons': [
|
||||
{
|
||||
'attr': {
|
||||
'data-toggle': 'ajax',
|
||||
'href': path('mautic_plugin_reload'),
|
||||
'class': 'btn btn-primary'
|
||||
},
|
||||
'btnText': 'mautic.plugin.reload.plugins'|trans,
|
||||
'iconClass': 'ri-instance-fill',
|
||||
'tooltip': 'mautic.plugin.reload.plugins.tooltip',
|
||||
},
|
||||
],
|
||||
}) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if isIndex %}
|
||||
<div id="page-list-wrapper" class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<div class="box-layout">
|
||||
<div class="row">
|
||||
<div class="col-xs-3 va-m">
|
||||
<select id="integrationFilter" onchange="Mautic.filterIntegrations(true);"
|
||||
class="form-control"
|
||||
data-placeholder="{{ 'mautic.integration.filter.all'|trans }}">
|
||||
<option value=""></option>
|
||||
{% for plugin in plugins %}
|
||||
<option {% if filterValue is same as(plugin.id) %}selected{% endif %} value="{{ plugin.id|e }}">
|
||||
{{- plugin.name -}}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-list">
|
||||
{% endif %}
|
||||
|
||||
{% if items|length > 0 %}
|
||||
<div class="pa-md">
|
||||
<div class="row shuffle-integrations native-integrations">
|
||||
{% for item in items %}
|
||||
{% if item.plugin in plugins|keys %}
|
||||
{% set pluginTitle = plugins[item.plugin].name ~ ' - ' ~ item.display %}
|
||||
{% else %}
|
||||
{% set pluginTitle = item.name ~ ' - ' ~ item.display %}
|
||||
{% endif %}
|
||||
<div class="shuffle shuffle-item grid ma-10 pull-left text-center integration plugin{{ item.plugin }} integration-{{ item.name }} {% if not item.enabled %}integration-disabled{% endif %}">
|
||||
<div class="panel ovf-h pa-10">
|
||||
<a href="{{ path((item.isBundle ? 'mautic_plugin_info' : 'mautic_plugin_config'), {'name': item.name}) }}"
|
||||
{% if item.isBundle %}data-footer="false"{% endif %}
|
||||
data-prevent-dismiss="true"
|
||||
data-toggle="ajaxmodal"
|
||||
data-target="#IntegrationEditModal"
|
||||
data-header="{{ item.display }}">
|
||||
<p><img style="height: 78px;" class="img img-responsive" src="{{ getAssetUrl(item.icon) }}" /></p>
|
||||
<h5 class="mt-20">
|
||||
<span class="ellipsis" data-toggle="tooltip" title="{{ pluginTitle }}">{{ item.display }}</span>
|
||||
</h5>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{{ include('@MauticCore/Helper/modal.html.twig', {
|
||||
'id': 'IntegrationEditModal',
|
||||
'footerButtons': true,
|
||||
}) }}
|
||||
{% else %}
|
||||
{{ include('@MauticCore/Helper/noresults.html.twig', {
|
||||
'message': 'mautic.integrations.noresults',
|
||||
'tip': 'mautic.integration.noresults.tip',
|
||||
}) }}
|
||||
{% endif %}
|
||||
|
||||
{% if isIndex %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,22 @@
|
||||
{#
|
||||
Variables
|
||||
- bundle
|
||||
- icon
|
||||
#}
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<img class="img img-responsive" style="margin: auto;" src="{{ getAssetUrl(icon) }}" />
|
||||
</div>
|
||||
|
||||
<div class="col-xs-8">
|
||||
<h3>{{ bundle.primaryDescription|purify }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if bundle.hasSecondaryDescription %}
|
||||
<div class="row mt-lg">
|
||||
<div class="col-xs-12">
|
||||
{{ bundle.secondaryDescription|purify }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Security\Permissions;
|
||||
|
||||
use Mautic\CoreBundle\Security\Permissions\AbstractPermissions;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class PluginPermissions extends AbstractPermissions
|
||||
{
|
||||
public function __construct($params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
$this->addManagePermission('plugins');
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'plugin';
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface &$builder, array $options, array $data): void
|
||||
{
|
||||
$this->addManageFormFields('plugin', 'plugins', $builder, $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Tests;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Mautic\CoreBundle\Cache\ResultCacheOptions;
|
||||
use Mautic\CoreBundle\Helper\BundleHelper;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\PathsHelper;
|
||||
use Mautic\PluginBundle\Entity\IntegrationEntityRepository;
|
||||
use Mautic\PluginBundle\Entity\IntegrationRepository;
|
||||
use Mautic\PluginBundle\Entity\Plugin;
|
||||
use Mautic\PluginBundle\Entity\PluginRepository;
|
||||
use Mautic\PluginBundle\Event\PluginIntegrationKeyEvent;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Mautic\PluginBundle\Model\PluginModel;
|
||||
use Mautic\PluginBundle\PluginEvents;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Twig\Environment;
|
||||
|
||||
class ConfigFormTest extends KernelTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
}
|
||||
|
||||
public function testConfigForm(): void
|
||||
{
|
||||
$plugins = $this->getIntegrationObject()->getIntegrationObjects();
|
||||
|
||||
foreach ($plugins as $name => $s) {
|
||||
$featureSettings = $s->getFormSettings();
|
||||
|
||||
$this->assertArrayHasKey('requires_callback', $featureSettings);
|
||||
$this->assertArrayHasKey('requires_authorization', $featureSettings);
|
||||
if ($featureSettings['requires_callback']) {
|
||||
$this->assertNotEmpty($s->getAuthCallbackUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testOauth(): void
|
||||
{
|
||||
$connectWiseHeader = ['appcookie' => 'rookie'];
|
||||
self::getContainer()->get('event_dispatcher')->addListener(
|
||||
PluginEvents::PLUGIN_ON_INTEGRATION_KEYS_DECRYPT,
|
||||
function (PluginIntegrationKeyEvent $event) use ($connectWiseHeader): PluginIntegrationKeyEvent {
|
||||
$event->setKeys($connectWiseHeader);
|
||||
|
||||
return $event;
|
||||
}
|
||||
);
|
||||
|
||||
$plugins = $this->getIntegrationObject()->getIntegrationObjects();
|
||||
$url = 'https://test.com';
|
||||
$parameters = ['a' => 'testa', 'b' => 'testb'];
|
||||
$method = 'GET';
|
||||
$authType = 'oauth2';
|
||||
|
||||
$expected = [];
|
||||
$expected['Connectwise'] = $this->getOauthData('', ['clientId' => $connectWiseHeader['appcookie']]);
|
||||
$expected['OneSignal'] = $this->getOauthData('');
|
||||
$expected['Twilio'] = $this->getOauthData('');
|
||||
$expected['Vtiger'] = $this->getOauthData('sessionName');
|
||||
$expected['Dynamics'] = $this->getOauthData('access_token');
|
||||
$expected['Salesforce'] = $this->getOauthData('access_token');
|
||||
$expected['Sugarcrm'] = $this->getOauthData('access_token');
|
||||
$expected['Zoho'] = $this->getOauthData('access_token');
|
||||
$expected['Hubspot'] = $this->getOauthData('hapikey');
|
||||
|
||||
foreach ($plugins as $index => $integration) {
|
||||
$this->assertSame($expected[$index], $integration->prepareRequest($url, $parameters, $method, ['appcookie' => 'ololo'], $authType));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $headers
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function getOauthData(string $key, array $headers = []): array
|
||||
{
|
||||
$result = [
|
||||
[
|
||||
'a' => 'testa',
|
||||
'b' => 'testb',
|
||||
$key => '',
|
||||
], [
|
||||
'oauth-token: '.$key,
|
||||
'Authorization: OAuth ',
|
||||
],
|
||||
];
|
||||
|
||||
if ([] !== $headers) {
|
||||
$result[1] = array_merge($result[1], $headers);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function testAmendLeadDataBeforeMauticPopulate(): void
|
||||
{
|
||||
$plugins = $this->getIntegrationObject()->getIntegrationObjects();
|
||||
$object = 'company';
|
||||
$data = ['company_name' => 'company_name', 'email' => 'company_email'];
|
||||
|
||||
foreach ($plugins as $integration) {
|
||||
$methodExists = method_exists($integration, 'amendLeadDataBeforeMauticPopulate');
|
||||
if ($methodExists) {
|
||||
$count = $integration->amendLeadDataBeforeMauticPopulate($data, $object);
|
||||
$this->assertGreaterThanOrEqual(0, $count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getIntegrationObject()
|
||||
{
|
||||
// create an integration object
|
||||
$pathsHelper = $this->createMock(PathsHelper::class);
|
||||
$bundleHelper = $this->createMock(BundleHelper::class);
|
||||
$pluginModel = $this->createMock(PluginModel::class);
|
||||
$coreParametersHelper = new CoreParametersHelper(self::$kernel->getContainer());
|
||||
$twig = $this->createMock(Environment::class);
|
||||
$entityManager = $this->createMock(EntityManager::class);
|
||||
|
||||
$pluginRepository = $this->createMock(PluginRepository::class);
|
||||
|
||||
$registeredPluginBundles = static::getContainer()->getParameter('mautic.plugin.bundles');
|
||||
$mauticPlugins = static::getContainer()->getParameter('mautic.bundles');
|
||||
$bundleHelper->method('getPluginBundles')->willReturn($registeredPluginBundles);
|
||||
|
||||
$bundleHelper->method('getMauticBundles')->willReturn(array_merge($mauticPlugins, $registeredPluginBundles));
|
||||
$integrationEntityRepository = $this->createMock(IntegrationEntityRepository::class);
|
||||
|
||||
$integrationRepository = $this->createMock(IntegrationRepository::class);
|
||||
|
||||
$entityManager
|
||||
->method('getRepository')
|
||||
->willReturnMap(
|
||||
[
|
||||
[Plugin::class, $pluginRepository],
|
||||
[\Mautic\PluginBundle\Entity\Integration::class, $integrationRepository],
|
||||
[\Mautic\PluginBundle\Entity\IntegrationEntity::class, $integrationEntityRepository],
|
||||
]
|
||||
);
|
||||
|
||||
$pluginModel->method('getEntities')
|
||||
->with(
|
||||
[
|
||||
'hydration_mode' => 'hydrate_array',
|
||||
'index' => 'bundle',
|
||||
'result_cache' => new ResultCacheOptions(Plugin::CACHE_NAMESPACE),
|
||||
]
|
||||
)->willReturn([
|
||||
'MauticCrmBundle' => ['id' => 1],
|
||||
]);
|
||||
|
||||
$integrationHelper = new IntegrationHelper(
|
||||
self::getContainer(),
|
||||
$entityManager,
|
||||
$pathsHelper,
|
||||
$bundleHelper,
|
||||
$coreParametersHelper,
|
||||
$twig,
|
||||
$pluginModel
|
||||
);
|
||||
|
||||
return $integrationHelper;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class PluginControllerTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testConfigurePluginSuccessValidation(): void
|
||||
{
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/plugins/config/Twilio');
|
||||
$form = $crawler->filter('form')->form();
|
||||
|
||||
$form->setValues([
|
||||
'integration_details' => [
|
||||
'isPublished' => 0,
|
||||
'apiKeys' => [
|
||||
'username' => 'valid_username',
|
||||
'password' => 'valid_password',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->client->submit($form);
|
||||
Assert::assertTrue($this->client->getResponse()->isOk());
|
||||
}
|
||||
|
||||
public function testConfigurePluginValidationError(): void
|
||||
{
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/plugins/config/Twilio');
|
||||
$form = $crawler->filter('form')->form();
|
||||
|
||||
$form->setValues([
|
||||
'integration_details' => [
|
||||
'isPublished' => 1,
|
||||
'apiKeys' => [
|
||||
'username' => '',
|
||||
'password' => 'bbb',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$crawler = $this->client->submit($form);
|
||||
Assert::assertStringContainsString('A value is required.', $crawler->filter('#integration_details_apiKeys div')->html());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\DependencyInjection\Compiler;
|
||||
|
||||
use Mautic\PluginBundle\Integration\AbstractIntegration;
|
||||
use Mautic\PluginBundle\Tests\Integration\ClientFactory;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class TestPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$container->register(ClientFactory::class, ClientFactory::class)
|
||||
->setArguments([new Reference('mautic.http.client')]);
|
||||
|
||||
foreach ($container->getDefinitions() as $definition) {
|
||||
$class = (string) $definition->getClass();
|
||||
|
||||
/** @phpstan-ignore-next-line Ignore as AbstractIntegration is deprecated */
|
||||
if (str_starts_with($class, 'Mautic') && is_subclass_of($class, AbstractIntegration::class)) {
|
||||
$definition->addMethodCall('setClientFactory', [new Reference(ClientFactory::class)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Entity;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\PluginBundle\Entity\IntegrationEntityRepository;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
/**
|
||||
* IntegrationRepository.
|
||||
*/
|
||||
class IntegrationEntityRepositoryTest extends MauticMysqlTestCase
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $prefix;
|
||||
|
||||
/**
|
||||
* @var IntegrationEntityRepository
|
||||
*/
|
||||
private $integrationEntityRepository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->prefix = static::getContainer()->getParameter('mautic.db_table_prefix');
|
||||
$this->integrationEntityRepository = $this->em->getRepository(\Mautic\PluginBundle\Entity\IntegrationEntity::class);
|
||||
}
|
||||
|
||||
public function testThatGetIntegrationsEntityIdReturnsCorrectValues(): void
|
||||
{
|
||||
$now = new \DateTimeImmutable();
|
||||
$integrationEntityId = random_int(1, 1000);
|
||||
$internalEntityId = random_int(1, 1000);
|
||||
|
||||
$this->connection->insert($this->prefix.'integration_entity', [
|
||||
'date_added' => $now->format('Y-m-d H:i:s'),
|
||||
'integration' => 'someIntegration',
|
||||
'integration_entity' => 'someIntegrationEntity',
|
||||
'integration_entity_id' => $integrationEntityId,
|
||||
'internal_entity' => 'someInternalEntity',
|
||||
'internal_entity_id' => $internalEntityId,
|
||||
'last_sync_date' => null,
|
||||
'internal' => 'someInternalValue',
|
||||
]);
|
||||
|
||||
$results = $this->integrationEntityRepository->getIntegrationsEntityId(
|
||||
'someIntegration',
|
||||
'someIntegrationEntity',
|
||||
'someInternalEntity',
|
||||
[$internalEntityId],
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
null
|
||||
);
|
||||
|
||||
Assert::assertCount(1, $results);
|
||||
Assert::assertSame($integrationEntityId, (int) $results[0]['integration_entity_id']);
|
||||
Assert::assertSame($internalEntityId, (int) $results[0]['internal_entity_id']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Entity;
|
||||
|
||||
use Mautic\PluginBundle\Entity\Plugin;
|
||||
|
||||
class PluginTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testEmptyDescription(): void
|
||||
{
|
||||
$plugin = new Plugin();
|
||||
$this->assertNull($plugin->getDescription());
|
||||
$this->assertNull($plugin->getPrimaryDescription());
|
||||
$this->assertNull($plugin->getSecondaryDescription());
|
||||
$this->assertFalse($plugin->hasSecondaryDescription());
|
||||
}
|
||||
|
||||
public function testSimpleDescription(): void
|
||||
{
|
||||
$description = 'This is the best plugin in the whole galaxy';
|
||||
$plugin = new Plugin();
|
||||
$plugin->setDescription($description);
|
||||
$this->assertEquals($description, $plugin->getDescription());
|
||||
$this->assertEquals($description, $plugin->getPrimaryDescription());
|
||||
$this->assertNull($plugin->getSecondaryDescription());
|
||||
$this->assertFalse($plugin->hasSecondaryDescription());
|
||||
}
|
||||
|
||||
public function testSecondaryDescriptionWithUnixLineEnding(): void
|
||||
{
|
||||
$description = "This is the best plugin in the whole galaxy\n---\nLearn more about it <a href=\"#\">here</a>";
|
||||
$plugin = new Plugin();
|
||||
$plugin->setDescription($description);
|
||||
$this->assertEquals($description, $plugin->getDescription());
|
||||
$this->assertEquals('This is the best plugin in the whole galaxy', $plugin->getPrimaryDescription());
|
||||
$this->assertEquals('Learn more about it <a href="#">here</a>', $plugin->getSecondaryDescription());
|
||||
$this->assertTrue($plugin->hasSecondaryDescription());
|
||||
}
|
||||
|
||||
public function testSecondaryDescriptionWithWinLineEnding(): void
|
||||
{
|
||||
$description = "This is the best plugin in the whole galaxy\n\r---\n\rLearn more about it <a href=\"#\">here</a>";
|
||||
$plugin = new Plugin();
|
||||
$plugin->setDescription($description);
|
||||
$this->assertEquals($description, $plugin->getDescription());
|
||||
$this->assertEquals('This is the best plugin in the whole galaxy', $plugin->getPrimaryDescription());
|
||||
$this->assertEquals('Learn more about it <a href="#">here</a>', $plugin->getSecondaryDescription());
|
||||
$this->assertTrue($plugin->hasSecondaryDescription());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Event;
|
||||
|
||||
use Mautic\PluginBundle\Event\PluginIsPublishedEvent;
|
||||
|
||||
class PluginIsPublishedEventTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testSettersGetters(): void
|
||||
{
|
||||
$pluginIsPublishedEvent = new PluginIsPublishedEvent(1, 'testIntegration');
|
||||
|
||||
$this->assertSame('testIntegration', $pluginIsPublishedEvent->getIntegrationName());
|
||||
$this->assertSame(1, $pluginIsPublishedEvent->getValue());
|
||||
$this->assertSame('', $pluginIsPublishedEvent->getMessage());
|
||||
$this->assertTrue($pluginIsPublishedEvent->isCanPublish());
|
||||
|
||||
$pluginIsPublishedEvent->setMessage('This is test message.');
|
||||
$this->assertSame('This is test message.', $pluginIsPublishedEvent->getMessage());
|
||||
|
||||
$pluginIsPublishedEvent->setCanPublish(false);
|
||||
$this->assertFalse($pluginIsPublishedEvent->isCanPublish());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\EventListener;
|
||||
|
||||
use Mautic\PluginBundle\Event\PluginIntegrationRequestEvent;
|
||||
use Mautic\PluginBundle\EventListener\IntegrationSubscriber;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
final class IntegrationSubscriberTest extends TestCase
|
||||
{
|
||||
public function testOnRequestLogging(): void
|
||||
{
|
||||
$event = $this->createMock(PluginIntegrationRequestEvent::class);
|
||||
$event->method('getIntegrationName')->willReturn('Integration');
|
||||
$event->method('getHeaders')->willReturn(['Authorization: Bearer some_token']);
|
||||
$event->method('getMethod')->willReturn('POST');
|
||||
$event->method('getUrl')->willReturn('https://mautic.org');
|
||||
$event->method('getParameters')->willReturn(['key' => 'value']);
|
||||
$event->method('getSettings')->willReturn(['setting' => 'value']);
|
||||
|
||||
$authorization = ['Authorization: Bearer [REDACTED]'];
|
||||
$authorization = var_export($authorization, true);
|
||||
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$matcher = $this->exactly(4);
|
||||
$logger->expects($matcher)
|
||||
->method('debug')->willReturnCallback(function (...$parameters) use ($matcher, $authorization) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame('INTEGRATION REQUEST URL: POST https://mautic.org', $parameters[0]);
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame("INTEGRATION REQUEST HEADERS: \n".$authorization.PHP_EOL, $parameters[0]);
|
||||
}
|
||||
});
|
||||
|
||||
$subscriber = new IntegrationSubscriber($logger);
|
||||
$subscriber->onRequest($event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\EventListener;
|
||||
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Event\LeadEvent;
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Mautic\PluginBundle\Entity\IntegrationEntityRepository;
|
||||
use Mautic\PluginBundle\Entity\IntegrationRepository;
|
||||
use Mautic\PluginBundle\EventListener\LeadSubscriber;
|
||||
use Mautic\PluginBundle\Model\PluginModel;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class LeadSubscriberTest extends TestCase
|
||||
{
|
||||
private LeadSubscriber $subscriber;
|
||||
|
||||
/**
|
||||
* @var IntegrationEntityRepository|MockObject
|
||||
*/
|
||||
private $integrationEntityRepository;
|
||||
|
||||
/**
|
||||
* @var IntegrationRepository|MockObject
|
||||
*/
|
||||
private $integrationRepository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$pluginModel = $this->createMock(PluginModel::class);
|
||||
$this->integrationRepository = $this->createMock(IntegrationRepository::class);
|
||||
$this->integrationEntityRepository = $this->createMock(IntegrationEntityRepository::class);
|
||||
$this->subscriber = new LeadSubscriber(
|
||||
$pluginModel,
|
||||
$this->integrationRepository
|
||||
);
|
||||
|
||||
$pluginModel->expects($this->once())
|
||||
->method('getIntegrationEntityRepository')
|
||||
->willReturn($this->integrationEntityRepository);
|
||||
}
|
||||
|
||||
public function testOnLeadSaveWithoutActiveIntegration(): void
|
||||
{
|
||||
$this->integrationRepository->expects($this->once())
|
||||
->method('getIntegrations')
|
||||
->willReturn([]);
|
||||
|
||||
$this->integrationEntityRepository->expects($this->never())
|
||||
->method('updateErrorLeads');
|
||||
|
||||
$this->subscriber->onLeadSave(new LeadEvent(new Lead()));
|
||||
}
|
||||
|
||||
public function testOnLeadSaveWithActiveIntegration(): void
|
||||
{
|
||||
$integration = new Integration();
|
||||
$integration->setIsPublished(true);
|
||||
$integration->setApiKeys(['key' => 'some']);
|
||||
$integration->setSupportedFeatures(['push_lead']);
|
||||
|
||||
$this->integrationRepository->expects($this->once())
|
||||
->method('getIntegrations')
|
||||
->willReturn([$integration]);
|
||||
|
||||
$this->integrationEntityRepository->expects($this->once())
|
||||
->method('updateErrorLeads');
|
||||
|
||||
$this->subscriber->onLeadSave(new LeadEvent(new Lead()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Exception;
|
||||
|
||||
use Mautic\PluginBundle\Exception\ApiErrorException;
|
||||
|
||||
class ApiErrorExceptionTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testShortMessage(): void
|
||||
{
|
||||
$apiErrorException = new ApiErrorException('Main Error message.');
|
||||
$this->assertEmpty($apiErrorException->getShortMessage());
|
||||
|
||||
$shortMessage = 'This is short message';
|
||||
$apiErrorException->setShortMessage($shortMessage);
|
||||
$this->assertSame($shortMessage, $apiErrorException->getShortMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Form\Constraint;
|
||||
|
||||
use Mautic\PluginBundle\Event\PluginIsPublishedEvent;
|
||||
use Mautic\PluginBundle\Form\Constraint\CanPublish;
|
||||
use Mautic\PluginBundle\Form\Constraint\CanPublishValidator;
|
||||
use Mautic\PluginBundle\PluginEvents;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Context\ExecutionContext;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
class CanPublishValidatorTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var MockObject|EventDispatcherInterface
|
||||
*/
|
||||
private $dispatcher;
|
||||
|
||||
/**
|
||||
* @var MockObject|PluginIsPublishedEvent
|
||||
*/
|
||||
private $event;
|
||||
private CanPublishValidator $canPublishValidator;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||
$this->event = $this->createMock(PluginIsPublishedEvent::class);
|
||||
|
||||
$this->canPublishValidator = new CanPublishValidator($this->dispatcher);
|
||||
}
|
||||
|
||||
public function testValidate(): void
|
||||
{
|
||||
$this->event->expects($this->once())
|
||||
->method('isCanPublish')
|
||||
->willReturn(false);
|
||||
|
||||
$this->event->expects($this->once())
|
||||
->method('getMessage')
|
||||
->willReturn('Error in validation');
|
||||
|
||||
$this->dispatcher->expects($this->once())
|
||||
->method('dispatch')
|
||||
->willReturn($this->event);
|
||||
|
||||
$this->canPublishValidator->initialize($this->createMock(ExecutionContext::class));
|
||||
|
||||
$this->canPublishValidator->validate(1, new CanPublish('testIntegration'));
|
||||
}
|
||||
|
||||
public function testEventNotDispatchedIfUnpublished(): void
|
||||
{
|
||||
$this->event->expects($this->never())
|
||||
->method('isCanPublish')
|
||||
->willReturn(false);
|
||||
|
||||
$this->event->expects($this->never())
|
||||
->method('getMessage')
|
||||
->willReturn('Error in validation');
|
||||
|
||||
$this->dispatcher->expects($this->never())
|
||||
->method('dispatch')
|
||||
->with(PluginEvents::PLUGIN_IS_PUBLISHED_STATE_CHANGING)
|
||||
->willReturn($this->event);
|
||||
|
||||
$this->canPublishValidator->initialize($this->createMock(ExecutionContext::class));
|
||||
|
||||
$this->canPublishValidator->validate(0, new CanPublish('testIntegration'));
|
||||
}
|
||||
|
||||
public function testExceptionIsThrown(): void
|
||||
{
|
||||
$this->event->expects($this->never())
|
||||
->method('isCanPublish')
|
||||
->willReturn(false);
|
||||
|
||||
$this->event->expects($this->never())
|
||||
->method('getMessage')
|
||||
->willReturn('Error in validation');
|
||||
|
||||
$this->dispatcher->expects($this->never())
|
||||
->method('dispatch')
|
||||
->with(PluginEvents::PLUGIN_IS_PUBLISHED_STATE_CHANGING)
|
||||
->willReturn($this->event);
|
||||
|
||||
$this->canPublishValidator->initialize($this->createMock(ExecutionContext::class));
|
||||
|
||||
$this->expectException(UnexpectedTypeException::class);
|
||||
|
||||
$this->canPublishValidator->validate(1, new class extends Constraint {});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Form\Type;
|
||||
|
||||
use Mautic\CoreBundle\Form\Type\StandAloneButtonType;
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Mautic\PluginBundle\Form\Type\DetailsType;
|
||||
use Mautic\PluginBundle\Form\Type\KeysType;
|
||||
use Mautic\PluginBundle\Integration\AbstractIntegration;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class DetailsTypeTest extends TestCase
|
||||
{
|
||||
public function testBuildFormRemovesHiddenKeys(): void
|
||||
{
|
||||
/** @var MockObject&FormBuilderInterface $builder */
|
||||
$builder = $this->createMock(FormBuilderInterface::class);
|
||||
$options = ['integration' => 'integration', 'lead_fields' => 'lead_fields', 'company_fields' => 'company_fields'];
|
||||
|
||||
$integrationObject = $this->createMock(AbstractIntegration::class);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('getFormDisplaySettings')
|
||||
->willReturn(['hide_keys' => ['key1', 'key3']]);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('getRequiredKeyFields')
|
||||
->willReturn(['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', 'key4' => 'value4']);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('decryptApiKeys')
|
||||
->willReturn([]);
|
||||
$integrationObject->expects(self::never())
|
||||
->method('isAuthorized');
|
||||
$integrationObject->expects(self::once())
|
||||
->method('getSupportedFeatures')
|
||||
->willReturn([]);
|
||||
|
||||
$integration = $this->createMock(Integration::class);
|
||||
$integration->method('getApiKeys')
|
||||
->willReturn([]);
|
||||
$integration->expects(self::never())
|
||||
->method('getId');
|
||||
$integration->expects(self::never())
|
||||
->method('getSupportedFeatures');
|
||||
|
||||
$options['integration_object'] = $integrationObject;
|
||||
$options['data'] = $integration;
|
||||
|
||||
$calls = 0;
|
||||
$builder->expects(self::never())
|
||||
->method('setAction');
|
||||
$builder->expects(self::atLeastOnce())
|
||||
->method('add')
|
||||
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use (&$calls, $builder): FormBuilderInterface {
|
||||
if ('apiKeys' === $key) {
|
||||
++$calls;
|
||||
self::assertSame(KeysType::class, $fieldFQCN);
|
||||
self::assertArrayHasKey('integration_keys', $options);
|
||||
self::assertSame(['key2' => 'value2', 'key4' => 'value4'], $options['integration_keys']);
|
||||
}
|
||||
|
||||
if ('authButton' === $key) {
|
||||
++$calls;
|
||||
}
|
||||
|
||||
if ('supportedFeatures' === $key) {
|
||||
++$calls;
|
||||
}
|
||||
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$integrationObject->expects(self::once())
|
||||
->method('modifyForm')
|
||||
->with($builder, $options);
|
||||
|
||||
$form = new DetailsType();
|
||||
$form->buildForm($builder, $options);
|
||||
|
||||
self::assertSame(1, $calls);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('authorizedDataProvider')]
|
||||
public function testBuildFormRequiresAuthorization(bool $isAuthorized, string $label): void
|
||||
{
|
||||
/** @var MockObject&FormBuilderInterface $builder */
|
||||
$builder = $this->createMock(FormBuilderInterface::class);
|
||||
$options = ['integration' => 'integration', 'lead_fields' => 'lead_fields', 'company_fields' => 'company_fields'];
|
||||
|
||||
$integrationObject = $this->createMock(AbstractIntegration::class);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('getFormDisplaySettings')
|
||||
->willReturn(['hide_keys' => ['key3'], 'requires_authorization' => true]);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('getRequiredKeyFields')
|
||||
->willReturn(['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', 'key4' => 'value4']);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('decryptApiKeys')
|
||||
->willReturn(['decrypted']);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('isAuthorized')
|
||||
->willReturn($isAuthorized);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('getSupportedFeatures')
|
||||
->willReturn([]);
|
||||
|
||||
$integration = $this->createMock(Integration::class);
|
||||
$integration->method('getApiKeys')
|
||||
->willReturn([]);
|
||||
$integration->expects(self::never())
|
||||
->method('getId');
|
||||
$integration->expects(self::never())
|
||||
->method('getSupportedFeatures');
|
||||
|
||||
$options['integration_object'] = $integrationObject;
|
||||
$options['data'] = $integration;
|
||||
|
||||
$calls = 0;
|
||||
$builder->expects(self::never())
|
||||
->method('setAction');
|
||||
$builder->expects(self::atLeastOnce())
|
||||
->method('add')
|
||||
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use ($label, &$calls, $builder): FormBuilderInterface {
|
||||
if ('apiKeys' === $key) {
|
||||
++$calls;
|
||||
self::assertSame(KeysType::class, $fieldFQCN);
|
||||
self::assertArrayHasKey('integration_keys', $options);
|
||||
self::assertSame(['key1' => 'value1', 'key2' => 'value2', 'key4' => 'value4'], $options['integration_keys']);
|
||||
}
|
||||
|
||||
if ('authButton' === $key) {
|
||||
++$calls;
|
||||
self::assertSame(StandAloneButtonType::class, $fieldFQCN);
|
||||
self::assertArrayHasKey('label', $options);
|
||||
self::assertSame('mautic.integration.form.'.$label, $options['label']);
|
||||
}
|
||||
|
||||
if ('supportedFeatures' === $key) {
|
||||
++$calls;
|
||||
}
|
||||
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$integrationObject->expects(self::once())
|
||||
->method('modifyForm')
|
||||
->with($builder, $options);
|
||||
|
||||
$form = new DetailsType();
|
||||
$form->buildForm($builder, $options);
|
||||
|
||||
self::assertSame(2, $calls);
|
||||
}
|
||||
|
||||
public static function authorizedDataProvider(): \Generator
|
||||
{
|
||||
yield 'authorized' => [true, 'reauthorize'];
|
||||
yield 'not authorized' => [false, 'authorize'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $expectedFeatures
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('withFeaturesProvider')]
|
||||
public function testBuildFormWithFeatures(?int $integrationId, array $expectedFeatures): void
|
||||
{
|
||||
/** @var MockObject&FormBuilderInterface $builder */
|
||||
$builder = $this->createMock(FormBuilderInterface::class);
|
||||
$options = ['integration' => 'integration', 'lead_fields' => 'lead_fields', 'company_fields' => 'company_fields'];
|
||||
|
||||
$integrationObject = $this->createMock(AbstractIntegration::class);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('getFormDisplaySettings')
|
||||
->willReturn(['hide_keys' => ['key1']]);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('getRequiredKeyFields')
|
||||
->willReturn(['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', 'key4' => 'value4']);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('decryptApiKeys')
|
||||
->willReturn(['decrypted']);
|
||||
$integrationObject->expects(self::never())
|
||||
->method('isAuthorized');
|
||||
$integrationObject->expects(self::once())
|
||||
->method('getSupportedFeatures')
|
||||
->willReturn(['non-configured']);
|
||||
|
||||
$integration = $this->createMock(Integration::class);
|
||||
$integration->method('getApiKeys')
|
||||
->willReturn([]);
|
||||
$integration->expects(self::once())
|
||||
->method('getId')
|
||||
->willReturn($integrationId);
|
||||
$integration->expects(self::once())
|
||||
->method('getSupportedFeatures')
|
||||
->willReturn(['configured']);
|
||||
|
||||
$options['integration_object'] = $integrationObject;
|
||||
$options['data'] = $integration;
|
||||
|
||||
$calls = 0;
|
||||
$builder->expects(self::never())
|
||||
->method('setAction');
|
||||
$builder->expects(self::atLeastOnce())
|
||||
->method('add')
|
||||
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use ($expectedFeatures, &$calls, $builder): FormBuilderInterface {
|
||||
if ('apiKeys' === $key) {
|
||||
++$calls;
|
||||
self::assertSame(KeysType::class, $fieldFQCN);
|
||||
self::assertArrayHasKey('integration_keys', $options);
|
||||
self::assertSame(['key2' => 'value2', 'key3' => 'value3', 'key4' => 'value4'], $options['integration_keys']);
|
||||
}
|
||||
|
||||
if ('supportedFeatures' === $key) {
|
||||
++$calls;
|
||||
self::assertSame(ChoiceType::class, $fieldFQCN);
|
||||
self::assertArrayHasKey('choices', $options);
|
||||
self::assertSame(['mautic.integration.form.feature.non-configured' => 'non-configured'], $options['choices']);
|
||||
self::assertArrayHasKey('data', $options);
|
||||
self::assertSame($expectedFeatures, $options['data']);
|
||||
}
|
||||
|
||||
if ('authButton' === $key) {
|
||||
++$calls;
|
||||
}
|
||||
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$integrationObject->expects(self::once())
|
||||
->method('modifyForm')
|
||||
->with($builder, $options);
|
||||
|
||||
$form = new DetailsType();
|
||||
$form->buildForm($builder, $options);
|
||||
|
||||
self::assertSame(2, $calls);
|
||||
}
|
||||
|
||||
public static function withFeaturesProvider(): \Generator
|
||||
{
|
||||
yield 'create integration' => [null, ['non-configured']];
|
||||
yield 'edit integration' => [1, ['configured']];
|
||||
}
|
||||
|
||||
public function testBuildFormWithAction(): void
|
||||
{
|
||||
$action = 'the_action';
|
||||
$options = ['action' => $action, 'integration' => 'integration', 'lead_fields' => 'lead_fields', 'company_fields' => 'company_fields'];
|
||||
|
||||
/** @var MockObject&FormBuilderInterface $builder */
|
||||
$builder = $this->createMock(FormBuilderInterface::class);
|
||||
|
||||
$integrationObject = $this->createMock(AbstractIntegration::class);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('getFormDisplaySettings')
|
||||
->willReturn([]);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('getRequiredKeyFields')
|
||||
->willReturn(['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', 'key4' => 'value4']);
|
||||
$integrationObject->expects(self::once())
|
||||
->method('decryptApiKeys')
|
||||
->willReturn(['decrypted']);
|
||||
$integrationObject->expects(self::never())
|
||||
->method('isAuthorized');
|
||||
$integrationObject->expects(self::once())
|
||||
->method('getSupportedFeatures');
|
||||
|
||||
$integration = $this->createMock(Integration::class);
|
||||
$integration->method('getApiKeys')
|
||||
->willReturn([]);
|
||||
$integration->expects(self::never())
|
||||
->method('getId');
|
||||
$integration->expects(self::never())
|
||||
->method('getSupportedFeatures');
|
||||
|
||||
$options['integration_object'] = $integrationObject;
|
||||
$options['data'] = $integration;
|
||||
|
||||
$calls = 0;
|
||||
$builder->expects(self::once())
|
||||
->method('setAction')
|
||||
->with($action);
|
||||
$builder->expects(self::atLeastOnce())
|
||||
->method('add')
|
||||
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use (&$calls, $builder): FormBuilderInterface {
|
||||
if ('apiKeys' === $key) {
|
||||
++$calls;
|
||||
self::assertSame(KeysType::class, $fieldFQCN);
|
||||
self::assertArrayHasKey('integration_keys', $options);
|
||||
self::assertSame(['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', 'key4' => 'value4'], $options['integration_keys']);
|
||||
}
|
||||
|
||||
if ('supportedFeatures' === $key) {
|
||||
++$calls;
|
||||
}
|
||||
|
||||
if ('authButton' === $key) {
|
||||
++$calls;
|
||||
}
|
||||
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$integrationObject->expects(self::once())
|
||||
->method('modifyForm')
|
||||
->with($builder, $options);
|
||||
|
||||
$form = new DetailsType();
|
||||
$form->buildForm($builder, $options);
|
||||
|
||||
self::assertSame(1, $calls);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Form\Type;
|
||||
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Mautic\PluginBundle\Entity\Plugin;
|
||||
use Mautic\PluginBundle\Form\Type\IntegrationCampaignsType;
|
||||
use Mautic\PluginBundle\Form\Type\IntegrationConfigType;
|
||||
use Mautic\PluginBundle\Form\Type\IntegrationsListType;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Mautic\PluginBundle\Integration\AbstractIntegration;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
|
||||
class IntegrationsListTypeTest extends TestCase
|
||||
{
|
||||
public function testDataDoesNotHaveIntegration(): void
|
||||
{
|
||||
$pluginName = 'plugin name';
|
||||
|
||||
$integration1 = $this->createMock(Integration::class);
|
||||
$integration1->expects(self::once())
|
||||
->method('isPublished')
|
||||
->willReturn(false);
|
||||
$integration1->expects(self::never())
|
||||
->method('getPlugin');
|
||||
|
||||
$plugin = $this->createMock(Plugin::class);
|
||||
$plugin->expects(self::once())
|
||||
->method('getName')
|
||||
->willReturn($pluginName);
|
||||
|
||||
$integration2 = $this->createMock(Integration::class);
|
||||
$integration2->expects(self::once())
|
||||
->method('isPublished')
|
||||
->willReturn(true);
|
||||
$integration2->expects(self::once())
|
||||
->method('getPlugin')
|
||||
->willReturn($plugin);
|
||||
|
||||
$integrationInstance1 = $this->createMock(AbstractIntegration::class);
|
||||
$integrationInstance1->expects(self::once())
|
||||
->method('getIntegrationSettings')
|
||||
->willReturn($integration1);
|
||||
|
||||
$integrationInstance2 = $this->createMock(AbstractIntegration::class);
|
||||
$integrationInstance2->expects(self::once())
|
||||
->method('getIntegrationSettings')
|
||||
->willReturn($integration2);
|
||||
$integrationInstance2->expects(self::once())
|
||||
->method('getDisplayName')
|
||||
->willReturn('Integration 2');
|
||||
$integrationInstance2->expects(self::once())
|
||||
->method('getName')
|
||||
->willReturn('integration-2');
|
||||
|
||||
$integrationHelper = $this->createMock(IntegrationHelper::class);
|
||||
$integrationHelper->expects(self::once())
|
||||
->method('getIntegrationObjects')
|
||||
->with(null, 'features', true)
|
||||
->willReturn(['integration1' => $integrationInstance1, 'integration2' => $integrationInstance2]);
|
||||
$integrationHelper->method('getIntegrationObject')
|
||||
->willReturn($this->createMock(AbstractIntegration::class));
|
||||
|
||||
$callsForm = 0;
|
||||
|
||||
/** @var MockObject&FormInterface $form */
|
||||
$form = $this->createMock(FormInterface::class);
|
||||
|
||||
$form->method('add')
|
||||
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use (&$callsForm, $form): FormInterface {
|
||||
if ('config' === $key) {
|
||||
++$callsForm;
|
||||
self::assertSame(IntegrationConfigType::class, $fieldFQCN);
|
||||
self::assertArrayHasKey('integration', $options);
|
||||
self::assertNull($options['integration']);
|
||||
self::assertArrayHasKey('data', $options);
|
||||
self::assertSame([], $options['data']);
|
||||
}
|
||||
|
||||
if ('campaign_member_status' === $key) {
|
||||
++$callsForm;
|
||||
self::assertSame(IntegrationCampaignsType::class, $fieldFQCN);
|
||||
self::assertArrayHasKey('attr', $options);
|
||||
self::assertSame('integration-campaigns-status hide', $options['attr']['class']);
|
||||
self::assertArrayHasKey('data', $options);
|
||||
self::assertSame([], $options['data']);
|
||||
}
|
||||
|
||||
return $form;
|
||||
});
|
||||
|
||||
$data = [];
|
||||
|
||||
$formEvent = $this->createMock(FormEvent::class);
|
||||
$formEvent->expects(self::once())
|
||||
->method('getForm')
|
||||
->willReturn($form);
|
||||
$formEvent->expects(self::once())
|
||||
->method('getData')
|
||||
->willReturn($data);
|
||||
|
||||
/** @var MockObject&FormBuilderInterface $builder */
|
||||
$builder = $this->createMock(FormBuilderInterface::class);
|
||||
|
||||
$callsBuilder = 0;
|
||||
$builder->method('add')
|
||||
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use ($pluginName, &$callsBuilder, $builder): FormBuilderInterface {
|
||||
if ('integration' === $key) {
|
||||
++$callsBuilder;
|
||||
self::assertSame(ChoiceType::class, $fieldFQCN);
|
||||
self::assertArrayHasKey('choices', $options);
|
||||
self::assertSame([
|
||||
'' => '',
|
||||
$pluginName => [
|
||||
'Integration 2' => 'integration-2',
|
||||
],
|
||||
], $options['choices']);
|
||||
}
|
||||
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$calledCallback = false;
|
||||
$builder->expects(self::exactly(2))
|
||||
->method('addEventListener')
|
||||
->willReturnCallback(static function (string $eventName, callable $callback) use ($formEvent, &$calledCallback, $builder): FormBuilderInterface {
|
||||
self::assertContains($eventName, [FormEvents::PRE_SET_DATA, FormEvents::PRE_SUBMIT]);
|
||||
|
||||
if (!$calledCallback) {
|
||||
$calledCallback = true;
|
||||
$callback($formEvent);
|
||||
}
|
||||
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$integrationsListType = new IntegrationsListType($integrationHelper);
|
||||
$integrationsListType->buildForm($builder, ['supported_features' => 'features']);
|
||||
|
||||
self::assertSame(1, $callsBuilder);
|
||||
self::assertSame(2, $callsForm);
|
||||
}
|
||||
|
||||
public function testDataHaveIntegration(): void
|
||||
{
|
||||
$pluginName = 'plugin name';
|
||||
|
||||
$integration1 = $this->createMock(Integration::class);
|
||||
$integration1->expects(self::once())
|
||||
->method('isPublished')
|
||||
->willReturn(false);
|
||||
$integration1->expects(self::never())
|
||||
->method('getPlugin');
|
||||
|
||||
$plugin = $this->createMock(Plugin::class);
|
||||
$plugin->expects(self::once())
|
||||
->method('getName')
|
||||
->willReturn($pluginName);
|
||||
|
||||
$integration2 = $this->createMock(Integration::class);
|
||||
$integration2->expects(self::once())
|
||||
->method('isPublished')
|
||||
->willReturn(true);
|
||||
$integration2->expects(self::once())
|
||||
->method('getPlugin')
|
||||
->willReturn($plugin);
|
||||
|
||||
$integrationInstance1 = $this->createMock(AbstractIntegration::class);
|
||||
$integrationInstance1->expects(self::once())
|
||||
->method('getIntegrationSettings')
|
||||
->willReturn($integration1);
|
||||
|
||||
$integrationInstance2 = $this->createMock(AbstractIntegration::class);
|
||||
$integrationInstance2->expects(self::once())
|
||||
->method('getIntegrationSettings')
|
||||
->willReturn($integration2);
|
||||
$integrationInstance2->expects(self::once())
|
||||
->method('getDisplayName')
|
||||
->willReturn('Integration 2');
|
||||
$integrationInstance2->expects(self::once())
|
||||
->method('getName')
|
||||
->willReturn('integration-2');
|
||||
|
||||
$integrationHelper = $this->createMock(IntegrationHelper::class);
|
||||
$integrationHelper->expects(self::once())
|
||||
->method('getIntegrationObjects')
|
||||
->with(null, 'features', true)
|
||||
->willReturn(['integration1' => $integrationInstance1, 'integration2' => $integrationInstance2]);
|
||||
$integrationHelper->method('getIntegrationObject')
|
||||
->willReturn($this->createMock(AbstractIntegration::class));
|
||||
|
||||
$callsForm = 0;
|
||||
$form = $this->createMock(FormInterface::class);
|
||||
$form->method('add')
|
||||
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use ($integrationInstance1, &$callsForm, $form): FormInterface {
|
||||
if ('config' === $key) {
|
||||
++$callsForm;
|
||||
self::assertSame(IntegrationConfigType::class, $fieldFQCN);
|
||||
self::assertArrayHasKey('integration', $options);
|
||||
self::assertSame($integrationInstance1, $options['integration']);
|
||||
self::assertArrayHasKey('data', $options);
|
||||
self::assertSame(['config' => 'test'], $options['data']);
|
||||
}
|
||||
|
||||
if ('campaign_member_status' === $key) {
|
||||
++$callsForm;
|
||||
self::assertSame(IntegrationCampaignsType::class, $fieldFQCN);
|
||||
self::assertArrayHasKey('attr', $options);
|
||||
self::assertSame('integration-campaigns-status', $options['attr']['class']);
|
||||
self::assertArrayHasKey('data', $options);
|
||||
self::assertSame([
|
||||
'campaign_member_status' => true,
|
||||
'some' => 'other',
|
||||
], $options['data']);
|
||||
}
|
||||
|
||||
return $form;
|
||||
});
|
||||
|
||||
$data = [
|
||||
'integration' => 'integration1',
|
||||
'config' => [
|
||||
'config' => 'test',
|
||||
],
|
||||
'campaign_member_status' => [
|
||||
'campaign_member_status' => true,
|
||||
'some' => 'other',
|
||||
],
|
||||
];
|
||||
|
||||
$formEvent = $this->createMock(FormEvent::class);
|
||||
$formEvent->expects(self::exactly(2))
|
||||
->method('getForm')
|
||||
->willReturn($form);
|
||||
$formEvent->expects(self::exactly(2))
|
||||
->method('getData')
|
||||
->willReturn($data);
|
||||
|
||||
$callsBuilder = 0;
|
||||
$builder = $this->createMock(FormBuilderInterface::class);
|
||||
\assert($builder instanceof FormBuilderInterface);
|
||||
$builder->method('add')
|
||||
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use ($pluginName, &$callsBuilder, $builder): FormBuilderInterface {
|
||||
if ('integration' === $key) {
|
||||
++$callsBuilder;
|
||||
self::assertSame(ChoiceType::class, $fieldFQCN);
|
||||
self::assertArrayHasKey('choices', $options);
|
||||
self::assertSame([
|
||||
'' => '',
|
||||
$pluginName => [
|
||||
'Integration 2' => 'integration-2',
|
||||
],
|
||||
], $options['choices']);
|
||||
}
|
||||
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$calledCallback = 0;
|
||||
$builder->expects(self::exactly(2))
|
||||
->method('addEventListener')
|
||||
->willReturnCallback(static function (string $eventName, callable $callback) use ($formEvent, &$calledCallback, $builder): FormBuilderInterface {
|
||||
self::assertContains($eventName, [FormEvents::PRE_SET_DATA, FormEvents::PRE_SUBMIT]);
|
||||
|
||||
++$calledCallback;
|
||||
$callback($formEvent);
|
||||
|
||||
return $builder;
|
||||
});
|
||||
|
||||
$integrationsListType = new IntegrationsListType($integrationHelper);
|
||||
$integrationsListType->buildForm($builder, ['supported_features' => 'features']);
|
||||
|
||||
self::assertSame(1, $callsBuilder);
|
||||
self::assertSame(4, $callsForm, 'Because callback is called twice due to coverage.');
|
||||
self::assertSame(2, $calledCallback);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Helper;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Mautic\PluginBundle\Entity\Plugin;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
/**
|
||||
* A stub Base Bundle class which implements stub methods for testing purposes.
|
||||
*/
|
||||
abstract class PluginBundleBaseStub extends Bundle
|
||||
{
|
||||
public static function onPluginInstall(Plugin $plugin, $metadata = null, $installedSchema = null): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PluginController::reloadAction when the addon version does not match what's installed.
|
||||
*/
|
||||
public static function onPluginUpdate(Plugin $plugin, $metadata = null, ?Schema $installedSchema = null)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Helper;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Mautic\PluginBundle\Entity\Plugin;
|
||||
use Mautic\PluginBundle\Event\PluginInstallEvent;
|
||||
use Mautic\PluginBundle\Event\PluginUpdateEvent;
|
||||
use Mautic\PluginBundle\Helper\ReloadHelper;
|
||||
use Mautic\PluginBundle\PluginEvents;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class ReloadHelperTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
private ReloadHelper $helper;
|
||||
|
||||
private array $sampleAllPlugins = [];
|
||||
|
||||
private array $sampleMetaData = [];
|
||||
|
||||
private array $sampleSchemas = [];
|
||||
|
||||
/**
|
||||
* @var MockObject&EventDispatcherInterface
|
||||
*/
|
||||
private MockObject $eventDispatcher;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||
$this->helper = new ReloadHelper($this->eventDispatcher);
|
||||
|
||||
$this->sampleMetaData = [
|
||||
'MauticPlugin\MauticZapierBundle' => [
|
||||
'MauticPlugin\MauticZapierBundle\Entity\SomeTest' => $this->createMock(ClassMetadata::class),
|
||||
],
|
||||
];
|
||||
|
||||
$sampleSchema = $this->createMock(Schema::class);
|
||||
$sampleSchema->method('getTables')
|
||||
->willReturn([]);
|
||||
|
||||
$this->sampleSchemas = [
|
||||
'MauticPlugin\MauticZapierBundle' => $sampleSchema,
|
||||
];
|
||||
|
||||
$this->sampleAllPlugins = [
|
||||
'MauticZapierBundle' => [
|
||||
'isPlugin' => true,
|
||||
'base' => 'MauticZapier',
|
||||
'bundle' => 'MauticZapierBundle',
|
||||
'namespace' => 'MauticPlugin\MauticZapierBundle',
|
||||
'symfonyBundleName' => 'MauticZapierBundle',
|
||||
'bundleClass' => PluginBundleBaseStub::class,
|
||||
'permissionClasses' => [],
|
||||
'relative' => 'plugins/MauticZapierBundle',
|
||||
'directory' => '/Users/jan/dev/mautic/plugins/MauticZapierBundle',
|
||||
'config' => [
|
||||
'name' => 'Zapier Integration',
|
||||
'description' => 'Zapier lets you connect Mautic with 1100+ other apps',
|
||||
'version' => '1.0',
|
||||
'author' => 'Mautic',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testDisableMissingPlugins(): void
|
||||
{
|
||||
$sampleInstalledPlugins = [
|
||||
'MauticZapierBundle' => $this->createSampleZapierPlugin(),
|
||||
'MauticHappierBundle' => $this->createSampleHappierPlugin(),
|
||||
];
|
||||
|
||||
$disabledPlugins = $this->helper->disableMissingPlugins($this->sampleAllPlugins, $sampleInstalledPlugins);
|
||||
|
||||
$this->assertEquals(1, count($disabledPlugins));
|
||||
$this->assertEquals('Happier Integration', $disabledPlugins['MauticHappierBundle']->getName());
|
||||
$this->assertTrue($disabledPlugins['MauticHappierBundle']->isMissing());
|
||||
}
|
||||
|
||||
public function testEnableFoundPlugins(): void
|
||||
{
|
||||
$zapierPlugin = $this->createSampleZapierPlugin();
|
||||
$zapierPlugin->setIsMissing(true);
|
||||
$sampleInstalledPlugins = [
|
||||
'MauticZapierBundle' => $zapierPlugin,
|
||||
];
|
||||
|
||||
$enabledPlugins = $this->helper->enableFoundPlugins($this->sampleAllPlugins, $sampleInstalledPlugins);
|
||||
|
||||
$this->assertEquals(1, count($enabledPlugins));
|
||||
$this->assertEquals('Zapier Integration', $enabledPlugins['MauticZapierBundle']->getName());
|
||||
$this->assertFalse($enabledPlugins['MauticZapierBundle']->isMissing());
|
||||
}
|
||||
|
||||
public function testUpdatePlugins(): void
|
||||
{
|
||||
$this->sampleAllPlugins['MauticZapierBundle']['config']['version'] = '1.0.1';
|
||||
$this->sampleAllPlugins['MauticZapierBundle']['config']['description'] = 'Updated description';
|
||||
$sampleInstalledPlugins = [
|
||||
'MauticZapierBundle' => $this->createSampleZapierPlugin(),
|
||||
'MauticHappierBundle' => $this->createSampleHappierPlugin(),
|
||||
];
|
||||
$plugin = $this->createSampleZapierPlugin();
|
||||
$plugin->setVersion('1.0.1');
|
||||
$plugin->setDescription('Updated description');
|
||||
$event = new PluginUpdateEvent(
|
||||
$plugin,
|
||||
'1.0',
|
||||
$this->sampleMetaData['MauticPlugin\MauticZapierBundle'],
|
||||
$this->sampleSchemas['MauticPlugin\MauticZapierBundle']
|
||||
);
|
||||
$this->eventDispatcher->expects($this->once())->method('dispatch')->with($event, PluginEvents::ON_PLUGIN_UPDATE);
|
||||
$updatedPlugins = $this->helper->updatePlugins($this->sampleAllPlugins, $sampleInstalledPlugins, $this->sampleMetaData, $this->sampleSchemas);
|
||||
|
||||
$this->assertEquals(1, count($updatedPlugins));
|
||||
$this->assertEquals('Zapier Integration', $updatedPlugins['MauticZapierBundle']->getName());
|
||||
$this->assertEquals('1.0.1', $updatedPlugins['MauticZapierBundle']->getVersion());
|
||||
$this->assertEquals('Updated description', $updatedPlugins['MauticZapierBundle']->getDescription());
|
||||
}
|
||||
|
||||
public function testInstallPlugins(): void
|
||||
{
|
||||
$sampleInstalledPlugins = [
|
||||
'MauticHappierBundle' => $this->createSampleHappierPlugin(),
|
||||
];
|
||||
$event = new PluginInstallEvent(
|
||||
$this->createSampleZapierPlugin(),
|
||||
$this->sampleMetaData['MauticPlugin\MauticZapierBundle'],
|
||||
null
|
||||
);
|
||||
$this->eventDispatcher->expects($this->once())->method('dispatch')->with($event, PluginEvents::ON_PLUGIN_INSTALL);
|
||||
|
||||
$installedPlugins = $this->helper->installPlugins($this->sampleAllPlugins, $sampleInstalledPlugins, $this->sampleMetaData, $this->sampleSchemas);
|
||||
|
||||
$this->assertEquals(1, count($installedPlugins));
|
||||
$this->assertEquals('Zapier Integration', $installedPlugins['MauticZapierBundle']->getName());
|
||||
$this->assertEquals('1.0', $installedPlugins['MauticZapierBundle']->getVersion());
|
||||
$this->assertEquals('MauticZapierBundle', $installedPlugins['MauticZapierBundle']->getBundle());
|
||||
$this->assertEquals('Mautic', $installedPlugins['MauticZapierBundle']->getAuthor());
|
||||
$this->assertEquals('Zapier lets you connect Mautic with 1100+ other apps', $installedPlugins['MauticZapierBundle']->getDescription());
|
||||
$this->assertFalse($installedPlugins['MauticZapierBundle']->isMissing());
|
||||
}
|
||||
|
||||
private function createSampleZapierPlugin()
|
||||
{
|
||||
$plugin = new Plugin();
|
||||
$plugin->setName('Zapier Integration');
|
||||
$plugin->setDescription('Zapier lets you connect Mautic with 1100+ other apps');
|
||||
$plugin->isMissing(false);
|
||||
$plugin->setBundle('MauticZapierBundle');
|
||||
$plugin->setVersion('1.0');
|
||||
$plugin->setAuthor('Mautic');
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
|
||||
private function createSampleHappierPlugin()
|
||||
{
|
||||
$plugin = new Plugin();
|
||||
$plugin->setName('Happier Integration');
|
||||
$plugin->setDescription('Happier lets you connect Mautic with 1100+ other apps');
|
||||
$plugin->isMissing(false);
|
||||
$plugin->setBundle('MauticHappierBundle');
|
||||
$plugin->setVersion('1.0');
|
||||
$plugin->setAuthor('Mautic');
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Helper;
|
||||
|
||||
use Mautic\PluginBundle\Helper\oAuthHelper;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class oAuthHelperTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @param array<int, string> $headers
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('dataForHashSensitiveHeaderData')]
|
||||
public function testHashSensitiveHeaderData(string $authorization, array $headers): void
|
||||
{
|
||||
$hashedHeaders = oAuthHelper::sanitizeHeaderData($headers);
|
||||
|
||||
$this->assertStringContainsString(sprintf('Authorization: %s [REDACTED]', $authorization), $hashedHeaders[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Generator<string, array<int, string|array<int, string>>>
|
||||
*/
|
||||
public static function dataForHashSensitiveHeaderData(): \Generator
|
||||
{
|
||||
yield 'For Bearer' => [
|
||||
'Bearer',
|
||||
[
|
||||
'Authorization: Bearer SME-ASA',
|
||||
],
|
||||
];
|
||||
|
||||
yield 'For Basic' => [
|
||||
'Basic',
|
||||
[
|
||||
'Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Integration;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use Mautic\PluginBundle\Integration\AbstractIntegration;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class AbstractIntegrationTest extends AbstractIntegrationTestCase
|
||||
{
|
||||
public function testPopulatedLeadDataReturnsIntAndNotDncEntityForMauticContactIsContactableByEmail(): void
|
||||
{
|
||||
/**
|
||||
* @var MockObject&AbstractIntegration
|
||||
*/
|
||||
$integration = $this->getMockBuilder(AbstractIntegration::class)
|
||||
->setConstructorArgs([
|
||||
$this->dispatcher,
|
||||
$this->cache,
|
||||
$this->em,
|
||||
$this->request,
|
||||
$this->router,
|
||||
$this->translator,
|
||||
$this->logger,
|
||||
$this->encryptionHelper,
|
||||
$this->leadModel,
|
||||
$this->companyModel,
|
||||
$this->pathsHelper,
|
||||
$this->notificationModel,
|
||||
$this->fieldModel,
|
||||
$this->integrationEntityModel,
|
||||
$this->doNotContact,
|
||||
$this->fieldsWithUniqueIdentifier,
|
||||
])
|
||||
->onlyMethods(['getName', 'getAuthenticationType', 'getAvailableLeadFields'])
|
||||
->getMock();
|
||||
|
||||
$integration->method('getAvailableLeadFields')
|
||||
->willReturn(
|
||||
[
|
||||
'dnc' => [
|
||||
'type' => 'bool',
|
||||
'required' => false,
|
||||
'label' => 'DNC',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
['dnc' => 0],
|
||||
$integration->populateLeadData(
|
||||
['id' => 1],
|
||||
[
|
||||
'leadFields' => [
|
||||
'dnc' => 'mauticContactIsContactableByEmail',
|
||||
],
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $parameters
|
||||
* @param mixed[] $settings
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('requestProvider')]
|
||||
public function testMakeRequest(string $uri, array $parameters, string $method, array $settings, object $assertRequest): void
|
||||
{
|
||||
/**
|
||||
* @var MockObject&AbstractIntegration
|
||||
*/
|
||||
$integration = $this->getMockBuilder(AbstractIntegration::class)
|
||||
->setConstructorArgs([
|
||||
$this->dispatcher,
|
||||
$this->cache,
|
||||
$this->em,
|
||||
$this->request,
|
||||
$this->router,
|
||||
$this->translator,
|
||||
$this->logger,
|
||||
$this->encryptionHelper,
|
||||
$this->leadModel,
|
||||
$this->companyModel,
|
||||
$this->pathsHelper,
|
||||
$this->notificationModel,
|
||||
$this->fieldModel,
|
||||
$this->integrationEntityModel,
|
||||
$this->doNotContact,
|
||||
$this->fieldsWithUniqueIdentifier,
|
||||
])
|
||||
->onlyMethods(['getName', 'getAuthenticationType', 'makeHttpClient'])
|
||||
->getMock();
|
||||
|
||||
$integration->method('makeHttpClient')
|
||||
->willReturn(
|
||||
new class($assertRequest) extends Client {
|
||||
public function __construct(
|
||||
private object $assertRequest,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $options
|
||||
*/
|
||||
public function request(string $method, $uri = '', array $options = []): ResponseInterface
|
||||
{
|
||||
$this->assertRequest->assert($method, $uri, $options);
|
||||
|
||||
return new Response();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertEquals([], $integration->makeRequest($uri, $parameters, $method, $settings));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<mixed[]>
|
||||
*/
|
||||
public static function requestProvider(): iterable
|
||||
{
|
||||
// Test with JSON.
|
||||
yield [
|
||||
'https://some.uri',
|
||||
['this will be' => 'encoded to json string'],
|
||||
'POST',
|
||||
[
|
||||
'ignore_event_dispatch' => true,
|
||||
'encode_parameters' => 'json',
|
||||
],
|
||||
new class {
|
||||
/**
|
||||
* @param mixed[] $options
|
||||
*/
|
||||
public function assert(string $method, string $uri = '', array $options = []): void
|
||||
{
|
||||
Assert::assertSame('POST', $method);
|
||||
Assert::assertSame('https://some.uri', $uri);
|
||||
Assert::assertSame(
|
||||
[
|
||||
RequestOptions::BODY => '{"this will be":"encoded to json string"}',
|
||||
'headers' => ['Content-Type' => 'application/json'],
|
||||
'timeout' => 10,
|
||||
],
|
||||
$options
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
// Test with form params.
|
||||
yield [
|
||||
'https://some.uri',
|
||||
['this will be' => 'encoded to form array'],
|
||||
'POST',
|
||||
['ignore_event_dispatch' => true],
|
||||
new class {
|
||||
/**
|
||||
* @param mixed[] $options
|
||||
*/
|
||||
public function assert(string $method, string $uri = '', array $options = []): void
|
||||
{
|
||||
Assert::assertSame('POST', $method);
|
||||
Assert::assertSame('https://some.uri', $uri);
|
||||
Assert::assertSame(
|
||||
[
|
||||
RequestOptions::FORM_PARAMS => ['this will be' => 'encoded to form array'],
|
||||
'headers' => [],
|
||||
'timeout' => 10,
|
||||
],
|
||||
$options
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Integration;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Mautic\CoreBundle\Helper\CacheStorageHelper;
|
||||
use Mautic\CoreBundle\Helper\EncryptionHelper;
|
||||
use Mautic\CoreBundle\Helper\PathsHelper;
|
||||
use Mautic\CoreBundle\Model\NotificationModel;
|
||||
use Mautic\LeadBundle\Field\FieldsWithUniqueIdentifier;
|
||||
use Mautic\LeadBundle\Model\CompanyModel;
|
||||
use Mautic\LeadBundle\Model\DoNotContact;
|
||||
use Mautic\LeadBundle\Model\FieldModel;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Mautic\PluginBundle\Model\IntegrationEntityModel;
|
||||
use Monolog\Logger;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\Routing\Router;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class AbstractIntegrationTestCase extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var EventDispatcherInterface&MockObject
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* @var CacheStorageHelper&MockObject
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @var EntityManager&MockObject
|
||||
*/
|
||||
protected $em;
|
||||
|
||||
/**
|
||||
* @var Session&MockObject
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* @var RequestStack&MockObject
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* @var Router&MockObject
|
||||
*/
|
||||
protected $router;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface&MockObject
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
/**
|
||||
* @var Logger&MockObject
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @var EncryptionHelper&MockObject
|
||||
*/
|
||||
protected $encryptionHelper;
|
||||
|
||||
/**
|
||||
* @var LeadModel&MockObject
|
||||
*/
|
||||
protected $leadModel;
|
||||
|
||||
/**
|
||||
* @var CompanyModel&MockObject
|
||||
*/
|
||||
protected $companyModel;
|
||||
|
||||
/**
|
||||
* @var PathsHelper&MockObject
|
||||
*/
|
||||
protected $pathsHelper;
|
||||
|
||||
/**
|
||||
* @var NotificationModel&MockObject
|
||||
*/
|
||||
protected $notificationModel;
|
||||
|
||||
/**
|
||||
* @var FieldModel&MockObject
|
||||
*/
|
||||
protected $fieldModel;
|
||||
|
||||
/**
|
||||
* @var IntegrationEntityModel&MockObject
|
||||
*/
|
||||
protected $integrationEntityModel;
|
||||
|
||||
/**
|
||||
* @var DoNotContact&MockObject
|
||||
*/
|
||||
protected $doNotContact;
|
||||
|
||||
/**
|
||||
* @var MockObject&FieldsWithUniqueIdentifier
|
||||
*/
|
||||
protected MockObject $fieldsWithUniqueIdentifier;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||
$this->cache = $this->createMock(CacheStorageHelper::class);
|
||||
$this->em = $this->createMock(EntityManager::class);
|
||||
$this->session = $this->createMock(Session::class);
|
||||
$this->request = $this->createMock(RequestStack::class);
|
||||
$this->router = $this->createMock(Router::class);
|
||||
$this->translator = $this->createMock(TranslatorInterface::class);
|
||||
$this->logger = $this->createMock(Logger::class);
|
||||
$this->encryptionHelper = $this->createMock(EncryptionHelper::class);
|
||||
$this->leadModel = $this->createMock(LeadModel::class);
|
||||
$this->companyModel = $this->createMock(CompanyModel::class);
|
||||
$this->pathsHelper = $this->createMock(PathsHelper::class);
|
||||
$this->notificationModel = $this->createMock(NotificationModel::class);
|
||||
$this->fieldModel = $this->createMock(FieldModel::class);
|
||||
$this->integrationEntityModel = $this->createMock(IntegrationEntityModel::class);
|
||||
$this->doNotContact = $this->createMock(DoNotContact::class);
|
||||
$this->fieldsWithUniqueIdentifier = $this->createMock(FieldsWithUniqueIdentifier::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PluginBundle\Tests\Integration;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
class ClientFactory
|
||||
{
|
||||
private Client $httpClient;
|
||||
|
||||
public function __construct(Client $httpClient)
|
||||
{
|
||||
$this->httpClient = $httpClient;
|
||||
}
|
||||
|
||||
public function __invoke(): Client
|
||||
{
|
||||
return $this->httpClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
mautic.plugin.notice.reloaded="%added% new plugins were installed and %updated% updated."
|
||||
mautic.plugin.notice.saved="Settings for the %name% integration have been saved"
|
||||
mautic.integration.auth.invalid.state="Invalid session. Please try again."
|
||||
mautic.integration.error.genericerror="There was an unknown error encountered when trying to obtain the access token."
|
||||
mautic.integration.error.oauthfail="Authorization failed with the error message, '%error%'"
|
||||
mautic.integration.notfound="%name% was not found!"
|
||||
mautic.integration.notice.oauthsuccess="Authorization was successful."
|
||||
mautic.integration.notice.saved="Settings saved"
|
||||
mautic.integration.sso.error.no_email="Authenticated user does not have an email."
|
||||
mautic.integration.sso.error.no_name="Authenticated user does not have a first and last name."
|
||||
mautic.integration.sso.error.no_role="Authenticated user does not have a role."
|
||||
mautic.integration.sso.error.no_username="Authenticated user does not have a username."
|
||||
@@ -0,0 +1,96 @@
|
||||
mautic.campaign.plugin.leadpush="Push contact"
|
||||
mautic.integration.callbackuri="If applicable, use the following as the callback URL (may also be called the return URI) when configuring your application:"
|
||||
mautic.integration.closewindow="Close Window"
|
||||
mautic.integration.error="%name% Error"
|
||||
mautic.integration.error.generic_contact_name="Contact ID# %id%"
|
||||
mautic.integration.error.refreshtoken_expired="The refresh token has expired. Re-authorization is required."
|
||||
mautic.integration.filter.all="Show all plugins"
|
||||
mautic.integration.form.authorize="Authorize App"
|
||||
mautic.integration.form.enabled="Is enabled?"
|
||||
mautic.integration.form.feature.login_button="Login Button"
|
||||
mautic.integration.form.feature.public_activity="Display public activity"
|
||||
mautic.integration.form.feature.public_profile="Display public profile and enable profile to contact field matching"
|
||||
mautic.integration.form.feature.push_lead="Triggered action push contacts to integration"
|
||||
mautic.integration.form.feature.settings="Feature Specific Settings"
|
||||
mautic.integration.form.feature.share_button="Display share button on landing page social widget"
|
||||
mautic.integration.form.feature.sso_service="Single Sign On - Service Authentication"
|
||||
mautic.integration.form.feature.sso_form="Single Sign On - Form Authentication"
|
||||
mautic.integration.form.features="Enabled features"
|
||||
mautic.integration.form.field_match_notes="If the values are empty for the Mautic object, a value of 'Unknown' will be sent. If the integration field is a pick list, be sure the list values of Mautic's field match those of the integration."
|
||||
mautic.integration.form.lead.unknown="Unknown"
|
||||
mautic.integration.form.profile="Public Profile"
|
||||
mautic.integration.form.reauthorize="Reauthorize App"
|
||||
mautic.integration.form.savefirst="Required keys are missing in order to authenticate. Please enter the keys then save."
|
||||
mautic.integration.form.sharebutton="Share Buttons"
|
||||
mautic.integration.integrations="Integrations"
|
||||
mautic.integration.integration="Integration"
|
||||
mautic.integration.integration.tooltip="Select the integration to be used."
|
||||
mautic.integration.keyfield.api="API Key"
|
||||
mautic.integration.keyfield.appid="App ID"
|
||||
mautic.integration.keyfield.appsecret="App Secret"
|
||||
mautic.integration.keyfield.clientid="Client ID"
|
||||
mautic.integration.keyfield.clientsecret="Client Secret"
|
||||
mautic.integration.keyfield.consumerid="Consumer ID"
|
||||
mautic.integration.keyfield.consumersecret="Consumer Secret"
|
||||
mautic.integration.keyfield.username="Username"
|
||||
mautic.integration.keyfield.password="Password"
|
||||
mautic.integration.leadfield_matches="Assign available integration fields to local contact fields."
|
||||
mautic.integration.companyfield_matches="Assign available integration fields to local company fields."
|
||||
mautic.integration.missingkeys="Keys are not available for this transaction to take place. Please verify your settings then try again."
|
||||
mautic.integration.noresults.tip="Expecting integrations but see none? Enable the associated addon via the Addon Manager! For example, the Social Media addon must be enabled in order for Facebook to be listed."
|
||||
mautic.integration.sso.auto_create_user="Automatically create local user?"
|
||||
mautic.integration.sso.auto_create_user.tooltip="If the user is authenticated and does not exist locally, a new local user will be created."
|
||||
mautic.integration.sso.new_user_role="Role for created user"
|
||||
mautic.integration.sso.new_user_role.tooltip="If new user creation is enabled, select the role the new user should be assigned."
|
||||
mautic.integrations.noresults=""
|
||||
mautic.plugin.actions="Addon actions"
|
||||
mautic.plugin.actions.facebookLogin="Facebook Login"
|
||||
mautic.plugin.actions.push_lead="Push contact to integration"
|
||||
mautic.plugin.actions.tooltip="Push a contact to the selected integration."
|
||||
mautic.plugin.actions.social_share="Social Networks Share Icons"
|
||||
mautic.plugin.actions.social_share_tooltip="Adds social network icons to share form"
|
||||
mautic.plugin.actions.twitterLogin="Twitter Login"
|
||||
mautic.plugin.command.fetch.leads="Command to fetch contacts from integration"
|
||||
mautic.plugin.command.fetch.leads.starting="Fetch contacts command is starting"
|
||||
mautic.plugin.command.fetch.contacts.starting="Fetching contacts..."
|
||||
mautic.plugin.command.fetch.leads.events_executed="Number of leads/contacts fetched: %events%"
|
||||
mautic.plugin.command.fetch.leads.events_executed_breakout="%updated% contacts were updated and %created% contacts were created"
|
||||
mautic.plugin.command.fetch.companies.events_executed="Number of companies fetched: %events%"
|
||||
mautic.plugin.command.fetch.companies.events_executed_breakout="%updated% companies were updated and %created% companies were created"
|
||||
mautic.plugin.command.push.leads.events_executed="Number of contacts processed: %events%"
|
||||
mautic.plugin.form.add.fields="Add Field"
|
||||
mautic.plugin.plugins="Plugins"
|
||||
mautic.plugin.integration.tab.details="Enabled/Auth"
|
||||
mautic.plugin.integration.tab.features="Features"
|
||||
mautic.plugin.integration.tab.fieldmapping="Contact Mapping"
|
||||
mautic.plugin.integration.tab.companyfieldmapping="Company Mapping"
|
||||
mautic.plugin.manage.plugins="Manage Plugins"
|
||||
mautic.plugin.permissions.plugins="Plugins - User has access to"
|
||||
mautic.plugin.permissions.header="Plugin Permissions"
|
||||
mautic.plugin.point.action="Addon triggers"
|
||||
mautic.plugin.reload.plugins="Install/Upgrade Plugins"
|
||||
mautic.plugin.reload.plugins.tooltip="Upload the plugin via FTP or some other protocol to the plugins directory then click this button to install/upgrade."
|
||||
mautic.integration.form.feature.get_leads="Pull contacts and/or companies from integration"
|
||||
mautic.plugin.command.push.leads.activity="Push activity timeline to %integration% mautic object"
|
||||
mautic.plugin.command.fetch.companies="Fetching companies"
|
||||
mautic.plugin.command.fetch.companies.starting="Fetch companies command is starting"
|
||||
mautic.plugin.command.pushing.leads="Updating/creating leads from Mautic to %integration%"
|
||||
mautic.plugin.command.fetch.pushing.leads.events_executed="Number of contacts pushed: %updated% updated, %created% created, %errored% had errors and %ignored% were ignored (likely duplicates or didn't match field criteria)"
|
||||
mautic.plugins.integration.fields="Integration fields"
|
||||
mautic.plugins.mautic.direction="Direction"
|
||||
mautic.plugins.mautic.fields="Mautic fields"
|
||||
mautic.plugin.direction.data.update="Pick direction of data update"
|
||||
mautic.integration.form.feature.push_leads="Push contacts and/or companies to this integration"
|
||||
mautic.plugin.integration.campaign_members="Integration Campaign Members"
|
||||
mautic.plugin.integration.contact.timeline.link="Contact's timeline link"
|
||||
mautic.plugin.integration.campaigns="Push contacts to this integration campaign"
|
||||
mautic.plugin.config.campaign.member.chooseone="Choose a campaign to insert contacts into"
|
||||
mautic.plugin.integration.campaigns.member.status="Campaign member status"
|
||||
mautic.integrations.blanks="Update blank values"
|
||||
mautic.integrations.form.blanks="This will update blank values regardless of data priority, on both Integration and Mautic."
|
||||
mautic.plugin.command.notauthorized="%s is not authorized"
|
||||
mautic.plugin.integration.contact.donotcontact.email="Do not contact by email"
|
||||
mautic.plugin.command.pushing.companies="Updating/creating companies from Mautic to %integration%"
|
||||
mautic.plugin.command.fetch.pushing.companies.events_executed="Number of companies pushed: %updated% updated, %created% created, %errored% had errors and %ignored% were ignored (likely duplicates or didn't match field criteria)"
|
||||
mautic.integrations.update.dnc.by.date="Use latest updated Do Not Contact record"
|
||||
mautic.integrations.form.update.dnc.by.date.label="Select this option if you wish to update the Do not contact field by the latest updated on either systems"
|
||||
@@ -0,0 +1,2 @@
|
||||
mautic.plugin.field.required_mapping_missing="At least one required field is not mapped."
|
||||
mautic.lead_list.not_allowed_plugin_publish="You are not allowed to publish this plugin due to insufficient configurations."
|
||||
Reference in New Issue
Block a user