Initial commit: CloudOps infrastructure platform

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -0,0 +1,64 @@
/*
* @copyright 2014 Mautic Contributors. All rights reserved
* @author Mautic
*
* @link http://mautic.org
*
* @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
*/
Mautic.testFullContactApi = function (btn) {
mQuery(btn).prop('disabled', true);
var apikey = mQuery('#integration_details_apiKeys_apikey').val();
var d = new Date();
var month = d.getMonth() + 1;
var period = d.getFullYear() + '-' + ((month < 10) ? '0' + month : month);
var months = new Array();
months[0] = "January";
months[1] = "February";
months[2] = "March";
months[3] = "April";
months[4] = "May";
months[5] = "June";
months[6] = "July";
months[7] = "August";
months[8] = "September";
months[9] = "October";
months[10] = "November";
months[11] = "December";
var dateString = months[month - 1] + ' ' + d.getFullYear();
var EOL = String.fromCharCode(13);
mQuery.get('https://api.fullcontact.com/v2/stats.json?apiKey=' + apikey + '&period=' + period, function (stats) {
var person = null;
var company = null;
var free = null;
mQuery.each(stats.metrics, function (i, m) {
if ('200' === m.metricId) {
person = m;
} else if ('company_200' === m.metricId) {
company = m;
} else if ('200_free' === m.metricId) {
free = m;
}
});
var result = 'Plan Details: ' + stats.plan + EOL + EOL +
'Quick Usage Stats for ' + dateString + ':' + EOL;
if (person) {
result += ' - Person API: ' + person.usage + ' matches used from ' + person.planLevel + ' (' + person.remaining + ' remaining)' + EOL;
}
if (company) {
result += ' - Company API: ' + company.usage + ' matches used from ' + company.planLevel + ' (' + company.remaining + ' remaining)' + EOL;
}
if (free) {
result += ' - Name/Location/Stats: ' + free.usage + ' matches used from ' + free.planLevel + ' (' + free.remaining + ' remaining)' + EOL;
}
mQuery('#integration_details_apiKeys_stats').val(result);
}).fail(function(error) {
mQuery('#integration_details_apiKeys_stats').val((error.responseJSON && error.responseJSON.message)?error.responseJSON.message:'Error: ' + JSON.stringify(error));
});
mQuery(btn).prop('disabled', false);
};

View File

@@ -0,0 +1,62 @@
<?php
return [
'name' => 'FullContact',
'description' => 'Enables integration with FullContact for contact and company lookup',
'version' => '1.0',
'author' => 'Mautic',
'routes' => [
'public' => [
'mautic_plugin_fullcontact_index' => [
'path' => '/fullcontact/callback',
'controller' => 'MauticPlugin\MauticFullContactBundle\Controller\PublicController::callbackAction',
],
],
'main' => [
'mautic_plugin_fullcontact_action' => [
'path' => '/fullcontact/{objectAction}/{objectId}',
'controller' => 'MauticPlugin\MauticFullContactBundle\Controller\FullContactController::executeAction',
],
],
],
'services' => [
'others' => [
'mautic.plugin.fullcontact.lookup_helper' => [
'class' => MauticPlugin\MauticFullContactBundle\Helper\LookupHelper::class,
'arguments' => [
'mautic.helper.integration',
'mautic.helper.user',
'monolog.logger.mautic',
'router',
'mautic.lead.model.lead',
'mautic.lead.model.company',
],
],
],
'integrations' => [
'mautic.integration.fullcontact' => [
'class' => MauticPlugin\MauticFullContactBundle\Integration\FullContactIntegration::class,
'arguments' => [
'event_dispatcher',
'mautic.helper.cache_storage',
'doctrine.orm.entity_manager',
'request_stack',
'router',
'translator',
'monolog.logger.mautic',
'mautic.helper.encryption',
'mautic.lead.model.lead',
'mautic.lead.model.company',
'mautic.helper.paths',
'mautic.core.model.notification',
'mautic.lead.model.field',
'mautic.plugin.model.integration_entity',
'mautic.lead.model.dnc',
'mautic.lead.field.fields_with_unique_identifier',
],
],
],
],
];

View File

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

View File

@@ -0,0 +1,520 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Controller;
use Mautic\FormBundle\Controller\FormController;
use Mautic\LeadBundle\Entity\Company;
use Mautic\LeadBundle\Entity\Lead;
use MauticPlugin\MauticFullContactBundle\Form\Type\BatchLookupType;
use MauticPlugin\MauticFullContactBundle\Form\Type\LookupType;
use MauticPlugin\MauticFullContactBundle\Helper\LookupHelper;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class FullContactController extends FormController
{
/**
* @param string $objectId
*
* @return JsonResponse
*
* @throws \InvalidArgumentException
*/
public function lookupPersonAction(Request $request, LookupHelper $lookupHelper, $objectId = '')
{
if ('POST' === $request->getMethod()) {
$data = $request->request->all()['fullcontact_lookup'] ?? [];
$objectId = $data['objectId'];
}
/** @var \Mautic\LeadBundle\Model\LeadModel $model */
$model = $this->getModel('lead');
$lead = $model->getEntity($objectId);
if (!$this->security->hasEntityAccess(
'lead:leads:editown',
'lead:leads:editother',
$lead->getPermissionUser()
)
) {
$this->addFlashMessage(
$this->translator->trans('mautic.plugin.fullcontact.forbidden'),
[],
'error'
);
return new JsonResponse(
[
'closeModal' => true,
'flashes' => $this->getFlashContent(),
]
);
}
if ('GET' === $request->getMethod()) {
$route = $this->generateUrl(
'mautic_plugin_fullcontact_action',
[
'objectAction' => 'lookupPerson',
]
);
return $this->delegateView(
[
'viewParameters' => [
'form' => $this->createForm(
LookupType::class,
[
'objectId' => $objectId,
],
[
'action' => $route,
]
)->createView(),
'lookupItem' => $lead->getEmail(),
],
'contentTemplate' => '@MauticFullContact/FullContact/lookup.html.twig',
'passthroughVars' => [
'activeLink' => '#mautic_contact_index',
'mauticContent' => 'lead',
'route' => $route,
],
]
);
} else {
if ('POST' === $request->getMethod()) {
try {
$lookupHelper->lookupContact($lead, array_key_exists('notify', $data));
$this->addFlashMessage(
'mautic.lead.batch_leads_affected',
[
'%count%' => 1,
]
);
} catch (\Exception $ex) {
$this->addFlashMessage(
$ex->getMessage(),
[],
'error'
);
}
return new JsonResponse(
[
'closeModal' => true,
'flashes' => $this->getFlashContent(),
]
);
}
}
return new Response('Bad Request', 400);
}
/**
* @return JsonResponse
*
* @throws \InvalidArgumentException
*/
public function batchLookupPersonAction(Request $request, LookupHelper $lookupHelper)
{
/** @var \Mautic\LeadBundle\Model\LeadModel $model */
$model = $this->getModel('lead');
if ('GET' === $request->getMethod()) {
$data = $request->query->all()['fullcontact_batch_lookup'] ?? [];
} else {
$data = $request->request->all()['fullcontact_batch_lookup'] ?? [];
}
$entities = [];
if (array_key_exists('ids', $data)) {
$ids = $data['ids'];
if (!is_array($ids)) {
$ids = json_decode($ids, true);
}
if (is_array($ids) && count($ids)) {
$entities = $model->getEntities(
[
'filter' => [
'force' => [
[
'column' => 'l.id',
'expr' => 'in',
'value' => $ids,
],
],
],
'ignore_paginator' => true,
]
);
}
}
$lookupEmails = [];
if ($count = count($entities)) {
/** @var Lead $lead */
foreach ($entities as $lead) {
if ($this->security->hasEntityAccess(
'lead:leads:editown',
'lead:leads:editother',
$lead->getPermissionUser()
)
&& $lead->getEmail()
) {
$lookupEmails[$lead->getId()] = $lead->getEmail();
}
}
$count = count($lookupEmails);
}
if (0 === $count) {
$this->addFlashMessage(
$this->translator->trans('mautic.plugin.fullcontact.empty'),
[],
'error'
);
return new JsonResponse(
[
'closeModal' => true,
'flashes' => $this->getFlashContent(),
]
);
} else {
if ($count > 20) {
$this->addFlashMessage(
$this->translator->trans('mautic.plugin.fullcontact.toomany'),
[],
'error'
);
return new JsonResponse(
[
'closeModal' => true,
'flashes' => $this->getFlashContent(),
]
);
}
}
if ('GET' === $request->getMethod()) {
$route = $this->generateUrl(
'mautic_plugin_fullcontact_action',
[
'objectAction' => 'batchLookupPerson',
]
);
return $this->delegateView(
[
'viewParameters' => [
'form' => $this->createForm(
BatchLookupType::class,
[],
[
'action' => $route,
]
)->createView(),
'lookupItems' => array_values($lookupEmails),
],
'contentTemplate' => '@MauticFullContact/FullContact/batchLookup.html.twig',
'passthroughVars' => [
'activeLink' => '#mautic_contact_index',
'mauticContent' => 'leadBatch',
'route' => $route,
],
]
);
} else {
if ('POST' === $request->getMethod()) {
$notify = array_key_exists('notify', $data);
foreach ($lookupEmails as $id => $lookupEmail) {
if ($lead = $model->getEntity($id)) {
try {
$lookupHelper->lookupContact($lead, $notify);
} catch (\Exception $ex) {
$this->addFlashMessage(
$ex->getMessage(),
[],
'error'
);
--$count;
}
}
}
if ($count) {
$this->addFlashMessage(
'mautic.lead.batch_leads_affected',
[
'%count%' => $count,
]
);
}
return new JsonResponse(
[
'closeModal' => true,
'flashes' => $this->getFlashContent(),
]
);
}
}
return new Response('Bad Request', 400);
}
/***************** COMPANY ***********************/
/**
* @param string $objectId
*
* @return JsonResponse
*
* @throws \InvalidArgumentException
*/
public function lookupCompanyAction(Request $request, LookupHelper $lookupHelper, $objectId = '')
{
if ('POST' === $request->getMethod()) {
$data = $request->request->all()['fullcontact_lookup'] ?? [];
$objectId = $data['objectId'];
}
/** @var \Mautic\LeadBundle\Model\CompanyModel $model */
$model = $this->getModel('lead.company');
/** @var Company $company */
$company = $model->getEntity($objectId);
if ('GET' === $request->getMethod()) {
$route = $this->generateUrl(
'mautic_plugin_fullcontact_action',
[
'objectAction' => 'lookupCompany',
]
);
$website = $company->getFieldValue('companywebsite');
if (!$website) {
$this->addFlashMessage(
$this->translator->trans('mautic.plugin.fullcontact.compempty'),
[],
'error'
);
return new JsonResponse(
[
'closeModal' => true,
'flashes' => $this->getFlashContent(),
]
);
}
$parse = parse_url($website);
return $this->delegateView(
[
'viewParameters' => [
'form' => $this->createForm(
LookupType::class,
[
'objectId' => $objectId,
],
[
'action' => $route,
]
)->createView(),
'lookupItem' => $parse['host'],
],
'contentTemplate' => '@MauticFullContact/FullContact/lookup.html.twig',
'passthroughVars' => [
'activeLink' => '#mautic_company_index',
'mauticContent' => 'company',
'route' => $route,
],
]
);
} else {
if ('POST' === $request->getMethod()) {
try {
$lookupHelper->lookupCompany($company, array_key_exists('notify', $data));
$this->addFlashMessage(
'mautic.company.batch_companies_affected',
[
'%count%' => 1,
]
);
} catch (\Exception $ex) {
$this->addFlashMessage(
$ex->getMessage(),
[],
'error'
);
}
return new JsonResponse(
[
'closeModal' => true,
'flashes' => $this->getFlashContent(),
]
);
}
}
return new Response('Bad Request', 400);
}
/**
* @return JsonResponse
*
* @throws \InvalidArgumentException
*/
public function batchLookupCompanyAction(Request $request, LookupHelper $lookupHelper)
{
/** @var \Mautic\LeadBundle\Model\CompanyModel $model */
$model = $this->getModel('lead.company');
if ('GET' === $request->getMethod()) {
$data = $request->query->all()['fullcontact_batch_lookup'] ?? [];
} else {
$data = $request->request->all()['fullcontact_batch_lookup'] ?? [];
}
$entities = [];
if (array_key_exists('ids', $data)) {
$ids = $data['ids'];
if (!is_array($ids)) {
$ids = json_decode($ids, true);
}
if (is_array($ids) && count($ids)) {
$entities = $model->getEntities(
[
'filter' => [
'force' => [
[
'column' => 'comp.id',
'expr' => 'in',
'value' => $ids,
],
],
],
'ignore_paginator' => true,
]
);
}
}
$lookupWebsites = [];
if ($count = count($entities)) {
/** @var Company $company */
foreach ($entities as $company) {
if ($company->getFieldValue('companywebsite')) {
$website = $company->getFieldValue('companywebsite');
$parse = parse_url($website);
if (!isset($parse['host'])) {
continue;
}
$lookupWebsites[$company->getId()] = $parse['host'];
}
}
$count = count($lookupWebsites);
}
if (0 === $count) {
$this->addFlashMessage(
$this->translator->trans('mautic.plugin.fullcontact.compempty'),
[],
'error'
);
return new JsonResponse(
[
'closeModal' => true,
'flashes' => $this->getFlashContent(),
]
);
} else {
if ($count > 20) {
$this->addFlashMessage(
$this->translator->trans('mautic.plugin.fullcontact.comptoomany'),
[],
'error'
);
return new JsonResponse(
[
'closeModal' => true,
'flashes' => $this->getFlashContent(),
]
);
}
}
if ('GET' === $request->getMethod()) {
$route = $this->generateUrl(
'mautic_plugin_fullcontact_action',
[
'objectAction' => 'batchLookupCompany',
]
);
return $this->delegateView(
[
'viewParameters' => [
'form' => $this->createForm(
BatchLookupType::class,
[],
[
'action' => $route,
]
)->createView(),
'lookupItems' => array_values($lookupWebsites),
],
'contentTemplate' => '@MauticFullContact/FullContact/batchLookup.html.twig',
'passthroughVars' => [
'activeLink' => '#mautic_company_index',
'mauticContent' => 'companyBatch',
'route' => $route,
],
]
);
} else {
if ('POST' === $request->getMethod()) {
$notify = array_key_exists('notify', $data);
foreach ($lookupWebsites as $id => $lookupWebsite) {
if ($company = $model->getEntity($id)) {
try {
$lookupHelper->lookupCompany($company, $notify);
} catch (\Exception $ex) {
$this->addFlashMessage(
$ex->getMessage(),
[],
'error'
);
--$count;
}
}
}
if ($count) {
$this->addFlashMessage(
'mautic.company.batch_companies_affected',
[
'%count%' => $count,
]
);
}
return new JsonResponse(
[
'closeModal' => true,
'flashes' => $this->getFlashContent(),
]
);
}
}
return new Response('Bad Request', 400);
}
}

View File

@@ -0,0 +1,388 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Controller;
use Mautic\FormBundle\Controller\FormController;
use Mautic\LeadBundle\Entity\Company;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\UserBundle\Entity\User;
use Mautic\UserBundle\Model\UserModel;
use MauticPlugin\MauticFullContactBundle\Helper\LookupHelper;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class PublicController extends FormController
{
/**
* Write a notification.
*
* @param string $message Message of the notification
* @param string $header Header for message
* @param string $iconClass CSS class for the icon (e.g. ri-eye-line)
* @param User|null $user User object; defaults to current user
*/
public function addNewNotification($message, $header, $iconClass, User $user): void
{
/** @var \Mautic\CoreBundle\Model\NotificationModel $notificationModel */
$notificationModel = $this->getModel('core.notification');
$notificationModel->addNotification($message, 'FullContact', false, $header, $iconClass, null, $user);
}
/**
* @return Response
*
* @throws \InvalidArgumentException
*/
public function callbackAction(Request $request, LookupHelper $lookupHelper, LoggerInterface $mauticLogger)
{
if (!$request->request->has('result') || !$request->request->has('webhookId')) {
return new Response('ERROR');
}
$result = json_decode($request->request->all()['result'] ?? [], true);
$oid = $request->request->get('webhookId', '');
$validatedRequest = $lookupHelper->validateRequest($oid);
if (!$validatedRequest || !is_array($result)) {
return new Response('ERROR');
}
if ('company' == $validatedRequest['type']) {
return $this->compcallbackAction($mauticLogger, $result, $validatedRequest);
}
$notify = $validatedRequest['notify'];
try {
/** @var \Mautic\LeadBundle\Model\LeadModel $model */
$model = $this->getModel('lead');
/** @var Lead $lead */
$lead = $validatedRequest['entity'];
$currFields = $lead->getFields(true);
$org = [];
if (array_key_exists('organizations', $result)) {
/** @var array $organizations */
$organizations = $result['organizations'];
foreach ($organizations as $organization) {
if (array_key_exists('isPrimary', $organization) && !empty($organization['isPrimary'])) {
$org = $organization;
break;
}
}
if (0 === count($org) && 0 !== count($result['organizations'])) {
// primary not found, use the first one if exists
$org = $result['organizations'][0];
}
}
$loc = [];
if (array_key_exists('demographics', $result)
&& array_key_exists(
'locationDeduced',
$result['demographics']
)
) {
$loc = $result['demographics']['locationDeduced'];
}
$data = [];
/** @var array $socialProfiles */
$socialProfiles = [];
if (array_key_exists('socialProfiles', $result)) {
$socialProfiles = $result['socialProfiles'];
}
foreach (['facebook', 'foursquare', 'instagram', 'linkedin', 'twitter'] as $p) {
foreach ($socialProfiles as $socialProfile) {
if (array_key_exists('type', $socialProfile) && $socialProfile['type'] === $p && empty($currFields[$p]['value'])) {
$data[$p] = array_key_exists('url', $socialProfile) ? $socialProfile['url'] : '';
break;
}
}
}
if (array_key_exists('contactInfo', $result)) {
if (array_key_exists(
'familyName',
$result['contactInfo']
)
&& empty($currFields['lastname']['value'])
) {
$data['lastname'] = $result['contactInfo']['familyName'];
}
if (array_key_exists(
'givenName',
$result['contactInfo']
)
&& empty($currFields['firstname']['value'])
) {
$data['firstname'] = $result['contactInfo']['givenName'];
}
if ((array_key_exists('websites', $result['contactInfo'])
&& count(
$result['contactInfo']['websites']
))
&& empty($currFields['website']['value'])
) {
$data['website'] = $result['contactInfo']['websites'][0]['url'];
}
if ((array_key_exists('chats', $result['contactInfo'])
&& array_key_exists(
'skype',
$result['contactInfo']['chats']
))
&& empty($currFields['skype']['value'])
) {
$data['skype'] = $result['contactInfo']['chats']['skype']['handle'];
}
}
if (array_key_exists('name', $org) && empty($currFields['company']['value'])) {
$data['company'] = $org['name'];
}
if (array_key_exists('title', $org) && empty($currFields['position']['value'])) {
$data['position'] = $org['title'];
}
if ((array_key_exists('city', $loc)
&& array_key_exists(
'name',
$loc['city']
))
&& empty($currFields['city']['value'])
) {
$data['city'] = $loc['city']['name'];
}
if ((array_key_exists('state', $loc)
&& array_key_exists(
'name',
$loc['state']
))
&& empty($currFields['state']['value'])
) {
$data['state'] = $loc['state']['name'];
}
if ((array_key_exists('country', $loc)
&& array_key_exists(
'name',
$loc['country']
))
&& empty($currFields['country']['value'])
) {
$data['country'] = $loc['country']['name'];
}
$mauticLogger->log('debug', 'SET FIELDS: '.print_r($data, true));
// Unset the nonce so that it's not used again
$socialCache = $lead->getSocialCache();
unset($socialCache['fullcontact']['nonce']);
$lead->setSocialCache($socialCache);
$model->setFieldValues($lead, $data);
$model->getRepository()->saveEntity($lead);
if ($notify && (!isset($lead->imported) || !$lead->imported)) {
/** @var UserModel $userModel */
$userModel = $this->getModel('user');
if ($user = $userModel->getEntity($notify)) {
$this->addNewNotification(
sprintf($this->translator->trans('mautic.plugin.fullcontact.contact_retrieved'), $lead->getEmail()),
'FullContact Plugin',
'ri-search-line',
$user
);
}
}
} catch (\Exception $ex) {
try {
if ($notify && $lead && (!isset($lead->imported) || !$lead->imported)) {
/** @var UserModel $userModel */
$userModel = $this->getModel('user');
if ($user = $userModel->getEntity($notify)) {
$this->addNewNotification(
sprintf(
$this->translator->trans('mautic.plugin.fullcontact.unable'),
$lead->getEmail(),
$ex->getMessage()
),
'FullContact Plugin',
'ri-error-warning-line',
$user
);
}
}
} catch (\Exception $ex2) {
$mauticLogger->log('error', 'FullContact: '.$ex2->getMessage());
}
}
return new Response('OK');
}
/**
* This is only called internally.
*
* @throws \InvalidArgumentException
*/
private function compcallbackAction(LoggerInterface $mauticLogger, $result, $validatedRequest): Response
{
$notify = $validatedRequest['notify'];
try {
/** @var \Mautic\LeadBundle\Model\CompanyModel $model */
$model = $this->getModel('lead.company');
/** @var Company $company */
$company = $validatedRequest['entity'];
$currFields = $company->getFields(true);
$org = [];
$loc = [];
$phone = [];
$fax = [];
$email = [];
if (array_key_exists('organization', $result)) {
$org = $result['organization'];
if (array_key_exists('contactInfo', $result['organization'])) {
if (array_key_exists('addresses', $result['organization']['contactInfo'])
&& count(
$result['organization']['contactInfo']['addresses']
)
) {
$loc = $result['organization']['contactInfo']['addresses'][0];
}
if (array_key_exists('emailAddresses', $result['organization']['contactInfo'])
&& count(
$result['organization']['contactInfo']['emailAddresses']
)
) {
$email = $result['organization']['contactInfo']['emailAddresses'][0];
}
if (array_key_exists('phoneNumbers', $result['organization']['contactInfo'])
&& count(
$result['organization']['contactInfo']['phoneNumbers']
)
) {
$phone = $result['organization']['contactInfo']['phoneNumbers'][0];
foreach ($result['organization']['contactInfo']['phoneNumbers'] as $phoneNumber) {
if (array_key_exists('label', $phoneNumber)
&& 0 >= strpos(
strtolower($phoneNumber['label']),
'fax'
)
) {
$fax = $phoneNumber;
}
}
}
}
}
$data = [];
if (array_key_exists('addressLine1', $loc) && empty($currFields['companyaddress1']['value'])) {
$data['companyaddress1'] = $loc['addressLine1'];
}
if (array_key_exists('addressLine2', $loc) && empty($currFields['companyaddress2']['value'])) {
$data['companyaddress2'] = $loc['addressLine2'];
}
if (array_key_exists('value', $email) && empty($currFields['companyemail']['value'])) {
$data['companyemail'] = $email['value'];
}
if (array_key_exists('number', $phone) && empty($currFields['companyphone']['value'])) {
$data['companyphone'] = $phone['number'];
}
if (array_key_exists('locality', $loc) && empty($currFields['companycity']['value'])) {
$data['companycity'] = $loc['locality'];
}
if (array_key_exists('postalCode', $loc) && empty($currFields['companyzipcode']['value'])) {
$data['companyzipcode'] = $loc['postalCode'];
}
if (array_key_exists('region', $loc) && empty($currFields['companystate']['value'])) {
$data['companystate'] = $loc['region']['name'];
}
if (array_key_exists('country', $loc) && empty($currFields['companycountry']['value'])) {
$data['companycountry'] = $loc['country']['name'];
}
if (array_key_exists('name', $org) && empty($currFields['companydescription']['value'])) {
$data['companydescription'] = $org['name'];
}
if (array_key_exists(
'approxEmployees',
$org
)
&& empty($currFields['companynumber_of_employees']['value'])
) {
$data['companynumber_of_employees'] = $org['approxEmployees'];
}
if (array_key_exists('number', $fax) && empty($currFields['companyfax']['value'])) {
$data['companyfax'] = $fax['number'];
}
$mauticLogger->log('debug', 'SET FIELDS: '.print_r($data, true));
// Unset the nonce so that it's not used again
$socialCache = $company->getSocialCache();
unset($socialCache['fullcontact']['nonce']);
$company->setSocialCache($socialCache);
$model->setFieldValues($company, $data);
$model->getRepository()->saveEntity($company);
if ($notify) {
/** @var UserModel $userModel */
$userModel = $this->getModel('user');
if ($user = $userModel->getEntity($notify)) {
$this->addNewNotification(
sprintf($this->translator->trans('mautic.plugin.fullcontact.company_retrieved'), $company->getName()),
'FullContact Plugin',
'ri-search-line',
$user
);
}
}
} catch (\Exception $ex) {
try {
if ($notify && $company) {
/** @var UserModel $userModel */
$userModel = $this->getModel('user');
if ($user = $userModel->getEntity($notify)) {
$this->addNewNotification(
sprintf(
$this->translator->trans('mautic.plugin.fullcontact.unable'),
$company->getName(),
$ex->getMessage()
),
'FullContact Plugin',
'ri-error-warning-line',
$user
);
}
}
} catch (\Exception $ex2) {
$mauticLogger->log('error', 'FullContact: '.$ex2->getMessage());
}
}
return new Response('OK');
}
}

View File

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

View File

@@ -0,0 +1,143 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\EventListener;
use Mautic\CoreBundle\CoreEvents;
use Mautic\CoreBundle\Event\CustomButtonEvent;
use Mautic\CoreBundle\Twig\Helper\ButtonHelper;
use Mautic\PluginBundle\Helper\IntegrationHelper;
use MauticPlugin\MauticFullContactBundle\Integration\FullContactIntegration;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class ButtonSubscriber implements EventSubscriberInterface
{
public function __construct(
private IntegrationHelper $helper,
private TranslatorInterface $translator,
private RouterInterface $router,
) {
}
public static function getSubscribedEvents(): array
{
return [
CoreEvents::VIEW_INJECT_CUSTOM_BUTTONS => ['injectViewButtons', 0],
];
}
public function injectViewButtons(CustomButtonEvent $event): void
{
// get api_key from plugin settings
/** @var FullContactIntegration $myIntegration */
$myIntegration = $this->helper->getIntegrationObject('FullContact');
if (false === $myIntegration || !$myIntegration->getIntegrationSettings()->getIsPublished()) {
return;
}
if (str_starts_with($event->getRoute(), 'mautic_contact_')) {
$event->addButton(
[
'attr' => [
'class' => 'btn btn-ghost btn-sm btn-nospin',
'data-toggle' => 'ajaxmodal',
'data-target' => '#MauticSharedModal',
'onclick' => 'this.href=\''.
$this->router->generate(
'mautic_plugin_fullcontact_action',
['objectAction' => 'batchLookupPerson']
).
'?\' + mQuery.param({\'fullcontact_batch_lookup\':{\'ids\':JSON.parse(Mautic.getCheckedListIds(false, true))}});return true;',
'data-header' => $this->translator->trans('mautic.plugin.fullcontact.button.caption'),
],
'btnText' => $this->translator->trans('mautic.plugin.fullcontact.button.caption'),
'iconClass' => 'ri-search-line',
],
ButtonHelper::LOCATION_BULK_ACTIONS
);
if ($event->getItem()) {
$lookupContactButton = [
'attr' => [
'data-toggle' => 'ajaxmodal',
'data-target' => '#MauticSharedModal',
'data-header' => $this->translator->trans(
'mautic.plugin.fullcontact.lookup.header',
['%item%' => $event->getItem()->getEmail()]
),
'href' => $this->router->generate(
'mautic_plugin_fullcontact_action',
['objectId' => $event->getItem()->getId(), 'objectAction' => 'lookupPerson']
),
],
'btnText' => $this->translator->trans('mautic.plugin.fullcontact.button.caption'),
'iconClass' => 'ri-search-line',
];
$event
->addButton(
$lookupContactButton,
ButtonHelper::LOCATION_PAGE_ACTIONS,
['mautic_contact_action', ['objectAction' => 'view']]
)
->addButton(
$lookupContactButton,
ButtonHelper::LOCATION_LIST_ACTIONS,
'mautic_contact_index'
);
}
} else {
if (str_starts_with($event->getRoute(), 'mautic_company_')) {
$event->addButton(
[
'attr' => [
'class' => 'btn btn-ghost btn-sm btn-nospin',
'data-toggle' => 'ajaxmodal',
'data-target' => '#MauticSharedModal',
'onclick' => 'this.href=\''.
$this->router->generate(
'mautic_plugin_fullcontact_action',
['objectAction' => 'batchLookupCompany']
).
'?\' + mQuery.param({\'fullcontact_batch_lookup\':{\'ids\':JSON.parse(Mautic.getCheckedListIds(false, true))}});return true;',
'data-header' => $this->translator->trans(
'mautic.plugin.fullcontact.button.caption'
),
],
'btnText' => $this->translator->trans('mautic.plugin.fullcontact.button.caption'),
'iconClass' => 'ri-search-line',
],
ButtonHelper::LOCATION_BULK_ACTIONS
);
if ($event->getItem()) {
$lookupCompanyButton = [
'attr' => [
'data-toggle' => 'ajaxmodal',
'data-target' => '#MauticSharedModal',
'data-header' => $this->translator->trans(
'mautic.plugin.fullcontact.lookup.header',
['%item%' => $event->getItem()->getName()]
),
'href' => $this->router->generate(
'mautic_plugin_fullcontact_action',
['objectId' => $event->getItem()->getId(), 'objectAction' => 'lookupCompany']
),
],
'btnText' => $this->translator->trans('mautic.plugin.fullcontact.button.caption'),
'iconClass' => 'ri-search-line',
];
$event
->addButton(
$lookupCompanyButton,
ButtonHelper::LOCATION_LIST_ACTIONS,
'mautic_company_index'
);
}
}
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\EventListener;
use Mautic\LeadBundle\Event\CompanyEvent;
use Mautic\LeadBundle\Event\LeadEvent;
use Mautic\LeadBundle\LeadEvents;
use MauticPlugin\MauticFullContactBundle\Helper\LookupHelper;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LeadSubscriber implements EventSubscriberInterface
{
public function __construct(
private LookupHelper $lookupHelper,
) {
}
public static function getSubscribedEvents(): array
{
return [
LeadEvents::LEAD_POST_SAVE => ['leadPostSave', 0],
LeadEvents::COMPANY_POST_SAVE => ['companyPostSave', 0],
];
}
public function leadPostSave(LeadEvent $event): void
{
$this->lookupHelper->lookupContact($event->getLead(), true, true);
}
public function companyPostSave(CompanyEvent $event): void
{
$this->lookupHelper->lookupCompany($event->getCompany(), true, true);
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Exception;
class ApiException extends BaseException
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Exception;
class BaseException extends \Exception
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Exception;
class NoCreditException extends BaseException
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Exception;
class NotImplementedException extends BaseException
{
}

View File

@@ -0,0 +1,55 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Form\Type;
use Mautic\CoreBundle\Form\Type\FormButtonsType;
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @extends AbstractType<array<string, mixed>>
*/
class BatchLookupType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('ids', \Symfony\Component\Form\Extension\Core\Type\HiddenType::class);
$builder->add(
'buttons',
FormButtonsType::class,
[
'apply_text' => false,
'save_text' => 'mautic.core.form.submit',
'cancel_onclick' => 'javascript:void(0);',
'cancel_attr' => [
'data-dismiss' => 'modal',
],
]
);
$builder->add(
'notify',
YesNoButtonGroupType::class,
[
'label' => 'mautic.plugin.fullcontact.notify',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'data' => true,
'required' => false,
]
);
if (!empty($options['action'])) {
$builder->setAction($options['action']);
}
}
public function getBlockPrefix(): string
{
return 'fullcontact_batch_lookup';
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Form\Type;
use Mautic\CoreBundle\Form\Type\FormButtonsType;
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @extends AbstractType<array<string, mixed>>
*/
class LookupType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add(
'objectId',
HiddenType::class,
[
'attr' => [
'value' => $options['data']['objectId'],
],
]
);
$builder->add(
'buttons',
FormButtonsType::class,
[
'apply_text' => false,
'save_text' => 'mautic.core.form.submit',
'cancel_onclick' => 'javascript:void(0);',
'cancel_attr' => [
'data-dismiss' => 'modal',
],
]
);
$builder->add(
'notify',
YesNoButtonGroupType::class,
[
'label' => 'mautic.plugin.fullcontact.notify',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'data' => true,
'required' => false,
]
);
if (!empty($options['action'])) {
$builder->setAction($options['action']);
}
}
public function getBlockPrefix(): string
{
return 'fullcontact_lookup';
}
}

View File

@@ -0,0 +1,197 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Helper;
use Mautic\CoreBundle\Helper\EncryptionHelper;
use Mautic\CoreBundle\Helper\UserHelper;
use Mautic\LeadBundle\Entity\Company;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Model\CompanyModel;
use Mautic\LeadBundle\Model\LeadModel;
use Mautic\PluginBundle\Helper\IntegrationHelper;
use MauticPlugin\MauticFullContactBundle\Integration\FullContactIntegration;
use MauticPlugin\MauticFullContactBundle\Services\FullContact_Company;
use MauticPlugin\MauticFullContactBundle\Services\FullContact_Person;
use Monolog\Logger;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class LookupHelper
{
/**
* @var bool|FullContactIntegration
*/
protected $integration;
public function __construct(
IntegrationHelper $integrationHelper,
protected UserHelper $userHelper,
protected Logger $logger,
protected Router $router,
protected LeadModel $leadModel,
protected CompanyModel $companyModel,
) {
$this->integration = $integrationHelper->getIntegrationObject('FullContact');
}
/**
* @param bool $notify
* @param bool $checkAuto
*/
public function lookupContact(Lead $lead, $notify = false, $checkAuto = false): void
{
if (!$lead->getEmail()) {
return;
}
/** @var FullContact_Person $fullcontact */
if ($fullcontact = $this->getFullContact()) {
if (!$checkAuto || ($checkAuto && $this->integration->shouldAutoUpdate())) {
try {
[$cacheId, $webhookId, $cache] = $this->getCache($lead, $notify);
if (!array_key_exists($cacheId, $cache['fullcontact'])) {
$fullcontact->setWebhookUrl(
$this->router->generate(
'mautic_plugin_fullcontact_index',
[],
UrlGeneratorInterface::ABSOLUTE_URL
),
$webhookId
);
$res = $fullcontact->lookupByEmail($lead->getEmail());
// Prevent from filling up the cache
$cache['fullcontact'] = [
$cacheId => serialize($res),
'nonce' => $cache['fullcontact']['nonce'],
];
$lead->setSocialCache($cache);
if ($checkAuto) {
$this->leadModel->getRepository()->saveEntity($lead);
} else {
$this->leadModel->saveEntity($lead);
}
}
} catch (\Exception $ex) {
$this->logger->log('error', 'Error while using FullContact to lookup '.$lead->getEmail().': '.$ex->getMessage());
}
}
}
}
/**
* @param bool $notify
* @param bool $checkAuto
*/
public function lookupCompany(Company $company, $notify = false, $checkAuto = false): void
{
if (!$website = $company->getFieldValue('companywebsite')) {
return;
}
/** @var FullContact_Company $fullcontact */
if ($fullcontact = $this->getFullContact(false)) {
if (!$checkAuto || ($checkAuto && $this->integration->shouldAutoUpdate())) {
try {
$parse = parse_url($website);
[$cacheId, $webhookId, $cache] = $this->getCache($company, $notify);
if (isset($parse['host']) && !array_key_exists($cacheId, $cache['fullcontact'])) {
$fullcontact->setWebhookUrl(
$this->router->generate(
'mautic_plugin_fullcontact_index',
[],
UrlGeneratorInterface::ABSOLUTE_URL
),
$webhookId
);
$res = $fullcontact->lookupByDomain($parse['host']);
// Prevent from filling up the cache
$cache['fullcontact'] = [
$cacheId => serialize($res),
'nonce' => $cache['fullcontact']['nonce'],
];
$company->setSocialCache($cache);
if ($checkAuto) {
$this->companyModel->getRepository()->saveEntity($company);
} else {
$this->companyModel->saveEntity($company);
}
}
} catch (\Exception $ex) {
$this->logger->log('error', 'Error while using FullContact to lookup '.$parse['host'].': '.$ex->getMessage());
}
}
}
}
public function validateRequest($oid)
{
// prefix#entityId#hour#userId#nonce
[$w, $id, $hour, $uid, $nonce] = explode('#', $oid, 5);
$notify = (str_contains($w, '_notify') && $uid) ? $uid : false;
$type = (str_starts_with($w, 'fullcontactcomp')) ? 'company' : 'person';
switch ($type) {
case 'person':
$entity = $this->leadModel->getEntity($id);
break;
case 'company':
$entity = $this->companyModel->getEntity($id);
break;
}
if ($entity) {
$socialCache = $entity->getSocialCache();
$cacheId = $w.'#'.$id.'#'.$hour;
if (isset($socialCache['fullcontact'][$cacheId]) && !empty($socialCache['fullcontact']['nonce']) && !empty($nonce)
&& $socialCache['fullcontact']['nonce'] === $nonce
) {
return [
'notify' => $notify,
'entity' => $entity,
'type' => $type,
];
}
}
return false;
}
/**
* @param bool $person
*
* @return bool|FullContact_Company|FullContact_Person
*/
protected function getFullContact($person = true)
{
if (!$this->integration || !$this->integration->getIntegrationSettings()->getIsPublished()) {
return false;
}
// get api_key from plugin settings
$keys = $this->integration->getDecryptedApiKeys();
return ($person) ? new FullContact_Person($keys['apikey']) : new FullContact_Company($keys['apikey']);
}
protected function getCache($entity, $notify): array
{
/** @var User $user */
$user = $this->userHelper->getUser();
$nonce = substr(EncryptionHelper::generateKey(), 0, 16);
$cacheId = sprintf('fullcontact%s%s#', $entity instanceof Company ? 'comp' : '', $notify ? '_notify' : '').$entity->getId().'#'.gmdate('YmdH');
$webhookId = $cacheId.'#'.$user->getId().'#'.$nonce;
$cache = $entity->getSocialCache();
if (!isset($cache['fullcontact'])) {
$cache['fullcontact'] = [];
}
$cache['fullcontact']['nonce'] = $nonce;
return [$cacheId, $webhookId, $cache];
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Integration;
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
use Mautic\PluginBundle\Integration\AbstractIntegration;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class FullContactIntegration extends AbstractIntegration
{
public function getName(): string
{
return 'FullContact';
}
/**
* Return's authentication method such as oauth2, oauth1a, key, etc.
*/
public function getAuthenticationType(): string
{
return 'none';
}
/**
* Return array of key => label elements that will be converted to inputs to
* obtain from the user.
*
* @return array<string, string>
*/
public function getRequiredKeyFields(): array
{
// Do not rename field. fullcontact.js depends on it
return [
'apikey' => 'mautic.integration.fullcontact.apikey',
];
}
/**
* @param FormBuilder|Form $builder
* @param array $data
* @param string $formArea
*/
public function appendToForm(&$builder, $data, $formArea): void
{
if ('keys' === $formArea) {
$builder->add(
'test_api',
ButtonType::class,
[
'label' => 'mautic.plugin.fullcontact.test_api',
'attr' => [
'class' => 'btn btn-primary',
'style' => 'margin-bottom: 10px',
'onclick' => 'Mautic.testFullContactApi(this)',
],
]
);
$builder->add(
'stats',
TextareaType::class,
[
'label_attr' => ['class' => 'control-label'],
'label' => 'mautic.plugin.fullcontact.stats',
'required' => false,
'attr' => [
'class' => 'form-control',
'rows' => '6',
'readonly' => 'readonly',
],
]
);
$builder->add(
'auto_update',
YesNoButtonGroupType::class,
[
'label' => 'mautic.plugin.fullcontact.auto_update',
'data' => isset($data['auto_update']) && (bool) $data['auto_update'],
'attr' => [
'tooltip' => 'mautic.plugin.fullcontact.auto_update.tooltip',
],
]
);
}
}
public function shouldAutoUpdate(): bool
{
$featureSettings = $this->getKeys();
return isset($featureSettings['auto_update']) && (bool) $featureSettings['auto_update'];
}
/**
* @return string|array
*/
public function getFormNotes($section)
{
if ('custom' === $section) {
return [
'template' => '@MauticFullContact/Integration/form.html.twig',
'parameters' => [
'mauticUrl' => $this->router->generate('mautic_plugin_fullcontact_index', [], UrlGeneratorInterface::ABSOLUTE_URL),
],
];
}
return parent::getFormNotes($section);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
{#
Variables
- form
- lookupItems
#}
<div class="alert alert-info">{{ 'mautic.plugin.fullcontact.submit_items'|trans }}</div>
<div style="margin-top: 10px">
<ul class="list-group" style="max-height: 400px;overflow-y: auto">
{% for item in lookupItems %}
<li class="list-group-item">{{ item }}</li>
{% endfor %}
</ul>
</div>
<script>
(function () {
var ids = Mautic.getCheckedListIds(false, true);
if (mQuery('#fullcontact_batch_lookup_ids').length) {
mQuery('#fullcontact_batch_lookup_ids').val(ids);
}
})();
</script>
{{ form(form) }}

View File

@@ -0,0 +1,12 @@
{#
Variables
- form
- lookupItem
#}
<div class="alert alert-info">{{ 'mautic.plugin.fullcontact.submit'|trans }}</div>
<div style="margin-top: 10px">
<ul class="list-group" style="max-height: 400px;overflow-y: auto">
<li class="list-group-item">{{ lookupItem }}</li>
</ul>
</div>
{{ form(form) }}

View File

@@ -0,0 +1,8 @@
{{ includeScript('plugins/MauticFullContactBundle/Assets/js/fullcontact.js') }}
<div class="well well-sm" style="margin-bottom:0 !important;">
<p>{{ 'mautic.plugin.fullcontact.webhook'|trans }}</p>
<div class="alert alert-warning">
{{ 'mautic.plugin.fullcontact.public_info'|trans|purify }}
</div>
<input type="text" readonly="readonly" value="{{ mauticUrl }}" class="form-control" title="url"/>
</div>

View File

@@ -0,0 +1,187 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Services;
use MauticPlugin\MauticFullContactBundle\Exception\NoCreditException;
use MauticPlugin\MauticFullContactBundle\Exception\NotImplementedException;
/**
* This class handles the actually HTTP request to the FullContact endpoint.
*
* @author Keith Casey <contrib@caseysoftware.com>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
*/
class FullContact_Base
{
public const REQUEST_LATENCY = 0.2;
public const USER_AGENT = 'caseysoftware/fullcontact-php-0.9.0';
private \DateTime $_next_req_time;
// protected $_baseUri = 'https://requestbin.fullcontact.com/1ailj6d1?';
protected $_baseUri = 'https://api.fullcontact.com/';
protected $_version = 'v2';
protected $_resourceUri = '';
protected $_webhookUrl;
protected $_webhookId;
protected $_webhookJson = false;
protected $_supportedMethods = [];
public $response_obj;
public $response_code;
public $response_json;
/**
* Slow down calls to the FullContact API if needed.
*/
private function _wait_for_rate_limit(): void
{
$now = new \DateTime();
if ($this->_next_req_time->getTimestamp() > $now->getTimestamp()) {
$t = $this->_next_req_time->getTimestamp() - $now->getTimestamp();
sleep($t);
}
}
/**
* @param mixed[] $hdr
*/
private function _update_rate_limit($hdr): void
{
$remaining = (float) $hdr['X-Rate-Limit-Remaining'];
$reset = (float) $hdr['X-Rate-Limit-Reset'];
$spacing = $reset / (1.0 + $remaining);
$delay = $spacing - self::REQUEST_LATENCY;
$this->_next_req_time = new \DateTime('now + '.$delay.' seconds');
}
/**
* The base constructor Sets the API key available from here:
* http://fullcontact.com/getkey.
*
* @param string $_apiKey
*/
public function __construct(
protected $_apiKey,
) {
$this->_next_req_time = new \DateTime('@0');
}
/**
* This sets the webhook url for all requests made for this service
* instance. To unset, just use setWebhookUrl(null).
*
* @author David Boskovic <me@david.gs> @dboskovic
*
* @param string $url
* @param string $id
* @param bool $json
*
* @return object
*/
public function setWebhookUrl($url, $id = null, $json = false)
{
$this->_webhookUrl = $url;
$this->_webhookId = $id;
$this->_webhookJson = $json;
return $this;
}
/**
* This is a pretty close copy of my work on the Contactually PHP library
* available here: http://github.com/caseysoftware/contactually-php.
*
* @author Keith Casey <contrib@caseysoftware.com>
* @author David Boskovic <me@david.gs> @dboskovic
*
* @param array $params
* @param array $postData
*
* @return object
*
* @throws NoCreditException
* @throws NotImplementedException
*/
protected function _execute($params = [], $postData = null)
{
if (null === $postData && !in_array($params['method'], $this->_supportedMethods, true)) {
throw new NotImplementedException(self::class.' does not support the ['.$params['method'].'] method');
}
if (array_key_exists('method', $params)) {
unset($params['method']);
}
$this->_wait_for_rate_limit();
$params['apiKey'] = $this->_apiKey;
if ($this->_webhookUrl) {
$params['webhookUrl'] = $this->_webhookUrl;
}
if ($this->_webhookId) {
$params['webhookId'] = $this->_webhookId;
}
if ($this->_webhookJson) {
$params['webhookBody'] = 'json';
}
$fullUrl = $this->_baseUri.$this->_version.$this->_resourceUri.
'?'.http_build_query($params);
// open connection
$connection = curl_init($fullUrl);
curl_setopt($connection, CURLOPT_RETURNTRANSFER, true);
curl_setopt($connection, CURLOPT_USERAGENT, self::USER_AGENT);
curl_setopt($connection, CURLOPT_HEADER, true); // return HTTP headers with response
if (null !== $postData) {
curl_setopt($connection, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($connection, CURLOPT_POSTFIELDS, json_encode($postData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
curl_setopt($connection, CURLOPT_POST, true);
}
// execute request
$resp = curl_exec($connection);
[$response_headers, $this->response_json] = explode("\r\n\r\n", $resp, 2);
// $response_headers now has a string of the HTTP headers
// $response_json is the body of the HTTP response
$headers = [];
foreach (explode("\r\n", $response_headers) as $i => $line) {
if (0 === $i) {
$headers['http_code'] = $line;
} else {
[$key, $value] = explode(': ', $line);
$headers[$key] = $value;
}
}
$this->response_code = curl_getinfo($connection, CURLINFO_HTTP_CODE);
$this->response_obj = json_decode($this->response_json);
if ('403' === $this->response_code) {
throw new NoCreditException($this->response_obj->message);
} else {
if ('200' === $this->response_code) {
$this->_update_rate_limit($headers);
}
}
return $this->response_obj;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Services;
/**
* This class handles everything related to the Company lookup API.
*
* @author Adam Curtis <me@alc.im>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
*/
class FullContact_Batch extends FullContact_Base
{
protected $_resourceUri = '/batch.json';
/**
* @param array $requests
*
* @throws \MauticPlugin\MauticFullContactBundle\Exception\FullContact_Exception_NoCredit
* @throws \MauticPlugin\MauticFullContactBundle\Exception\FullContact_Exception_NotImplemented
*/
public function sendRequests($requests)
{
$this->_execute([], ['requests' => $requests]);
return $this->response_obj;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Services;
/**
* This class handles everything related to the Company lookup API.
*
* @author Adam Curtis <me@alc.im>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
*/
class FullContact_Company extends FullContact_Base
{
/**
* Supported lookup methods.
*
* @var array
*/
protected $_supportedMethods = ['domain'];
protected $_resourceUri = '/company/lookup.json';
public function lookupByDomain($search)
{
$this->_execute(['domain' => $search, 'method' => 'domain']);
return $this->response_obj;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Services;
/**
* This class just tells us what icons we have available.
*
* @author Keith Casey <contrib@caseysoftware.com>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
*/
class FullContact_Icon extends FullContact_Base
{
protected $_supportedMethods = ['available'];
protected $_resourceUri = '/icon/';
public function available()
{
$this->_execute(['method' => 'available']);
return $this->response_obj;
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Services;
/**
* This class handles all the Location information.
*
* @author Keith Casey <contrib@caseysoftware.com>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
*/
class FullContact_Location extends FullContact_Base
{
/**
* Supported lookup methods.
*
* @var array
*/
protected $_supportedMethods = ['normalizer', 'enrichment'];
protected $_resourceUri = '';
/**
* This takes a name and breaks it into its individual parts.
*
* @param type $casing -> valid values are uppercase, lowercase, titlecase
*
* @return type
*/
public function normalizer($place, $includeZeroPopulation = false, $casing = 'titlecase')
{
$includeZeroPopulation = ($includeZeroPopulation) ? 'true' : 'false';
$this->_resourceUri = '/address/locationNormalizer.json';
$this->_execute(['place' => $place, 'includeZeroPopulation' => $includeZeroPopulation,
'method' => 'normalizer', 'casing' => $casing, ]);
return $this->response_obj;
}
public function enrichment($place, $includeZeroPopulation = false, $casing = 'titlecase')
{
$includeZeroPopulation = ($includeZeroPopulation) ? 'true' : 'false';
$this->_resourceUri = '/address/locationEnrichment.json';
$this->_execute(['place' => $place, 'includeZeroPopulation' => $includeZeroPopulation,
'method' => 'enrichment', 'casing' => $casing, ]);
return $this->response_obj;
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Services;
/**
* This class handles everything related to names that aren't person-based info lookup.
*
* @author Keith Casey <contrib@caseysoftware.com>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
*/
class FullContact_Name extends FullContact_Base
{
/**
* Supported lookup methods.
*
* @var array
*/
protected $_supportedMethods = ['normalizer', 'deducer', 'similarity', 'stats', 'parser'];
protected $_resourceUri = '';
/**
* This takes a name and breaks it into its individual parts.
*
* @param type $name
* @param type $casing -> valid values are uppercase, lowercase, titlecase
*
* @return type
*/
public function normalizer($name, $casing = 'titlecase')
{
$this->_resourceUri = '/name/normalizer.json';
$this->_execute(['q' => $name, 'method' => 'normalizer', 'casing' => $casing]);
return $this->response_obj;
}
/**
* This resolves a person's name from either their email address or a
* username. This is basically a wrapper for the Person lookup methods.
*
* @param type $type -> valid values are email and username
* @param type $casing -> valid values are uppercase, lowercase, titlecase
*
* @return type
*/
public function deducer($value, $type = 'email', $casing = 'titlecase')
{
$this->_resourceUri = '/name/deducer.json';
$this->_execute([$type => $value, 'method' => 'deducer', 'casing' => $casing]);
return $this->response_obj;
}
/**
* These are two names to compare.
*
* @param type $name1
* @param type $name2
* @param type $casing
*
* @return type
*/
public function similarity($name1, $name2, $casing = 'titlecase')
{
$this->_resourceUri = '/name/similarity.json';
$this->_execute(['q1' => $name1, 'q2' => $name2, 'method' => 'similarity', 'casing' => $casing]);
return $this->response_obj;
}
public function stats($value, $type = 'givenName', $casing = 'titlecase')
{
$this->_resourceUri = '/name/stats.json';
$this->_execute([$type => $value, 'method' => 'stats', 'casing' => $casing]);
return $this->response_obj;
}
public function parser($name, $casing = 'titlecase')
{
$this->_resourceUri = '/name/parser.json';
$this->_execute(['q' => $name, 'method' => 'parser', 'casing' => $casing]);
return $this->response_obj;
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace MauticPlugin\MauticFullContactBundle\Services;
/**
* This class handles everything related to the Person lookup API.
*
* @author Keith Casey <contrib@caseysoftware.com>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
*/
class FullContact_Person extends FullContact_Base
{
/**
* Supported lookup methods.
*
* @var array
*/
protected $_supportedMethods = ['email', 'phone', 'twitter'];
protected $_resourceUri = '/person.json';
public function lookupByEmail($search)
{
$this->_execute(['email' => $search, 'method' => 'email']);
return $this->response_obj;
}
public function lookupByEmailMD5($search)
{
$this->_execute(['emailMD5' => $search, 'method' => 'email']);
return $this->response_obj;
}
public function lookupByPhone($search)
{
$this->_execute(['phone' => $search, 'method' => 'phone']);
return $this->response_obj;
}
public function lookupByTwitter($search)
{
$this->_execute(['twitter' => $search, 'method' => 'twitter']);
return $this->response_obj;
}
}

View File

@@ -0,0 +1 @@
mautic.company.batch_companies_affected="{0} 0 companies affected|{1} 1 company affected|]1,Inf[ %count% companies affected"

View File

@@ -0,0 +1,21 @@
mautic.integration.fullcontact.apikey="FullContact API Key"
mautic.plugin.fullcontact.button.caption="Lookup using FullContact"
mautic.plugin.fullcontact.lookup.header="FullContact - Lookup information for %item%"
mautic.plugin.fullcontact.test_api="Test API and get Stats"
mautic.plugin.fullcontact.stats="Test Results"
mautic.plugin.fullcontact.toomany="You can only lookup 20 contacts at once!"
mautic.plugin.fullcontact.comptoomany="You can only lookup 20 companies at once!"
mautic.plugin.fullcontact.empty="There are no contacts to lookup!"
mautic.plugin.fullcontact.compempty="There are no company domains to lookup!<br/>(Company website is empty?)"
mautic.plugin.fullcontact.forbidden="You don't have permissions to update this contact"
mautic.plugin.fullcontact.compforbidden="You don't have permissions to update this company"
mautic.plugin.fullcontact.auto_update="Automatically update on save?"
mautic.plugin.fullcontact.auto_update.tooltip="WARNING: This could easily exhaust your quota of API calls per month."
mautic.plugin.fullcontact.notify="Show a notification when the information has been received"
mautic.plugin.fullcontact.webhook="The plugin will use the following as the Webhook URL for FullContact:"
mautic.plugin.fullcontact.public_info="<strong>Warning!</strong> This must be a public accessible URL for the Webhook to work."
mautic.plugin.fullcontact.submit="Click submit to lookup the information for:"
mautic.plugin.fullcontact.submit_items="Click submit to lookup the information for the selected item(s)."
mautic.plugin.fullcontact.company_retrieved="The company information for %s has been retrieved"
mautic.plugin.fullcontact.contact_retrieved="The contact information for %s has been retrieved"
mautic.plugin.fullcontact.unable="Unable to save the information for %s: %s"

View File

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