Spaces:
No application file
No application file
| 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; | |
| } | |
| } | |