Initial commit: CloudOps infrastructure platform

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

View File

@@ -0,0 +1,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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}