mautic / app /bundles /PluginBundle /Helper /IntegrationHelper.php
chrisbryan17's picture
Upload folder using huggingface_hub
d2897cd verified
raw
history blame contribute delete
24.9 kB
<?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;
}
}