<?php
/**
 * Created by PhpStorm.
 * User: nbush
 * Date: 8/8/18
 * Time: 11:07 AM.
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;
use Mautic\LeadBundle\Entity\Lead;
use Symfony\Component\Form\Extension\Core\Type\UrlType;

/**
 * Class AbstractNeustarEnhancerIntegration.
 */
abstract class AbstractNeustarIntegration extends AbstractEnhancerIntegration
{
    use NonFreeEnhancerTrait {
        appendToForm as appendNonFree;
    }

    const NEUSTAR_PREFIX = 'Neustar';

    protected static $serviceKeysDict = [
        '1'    => [
            'format' => '/^\d{10}$/',
            'desc'   => 'mautic.enhancer.neustar.keys.phone.primary',
            'alias'  => 'lead.phone',
        ],
        '2'    => [
            'format' => '/^\d{10}$/',
            'desc'   => 'mautic.enhancer.neustar.keys.phone.secondary',
            //'alias' => '',
        ],
        '572'  => [
            'desc'  => 'mautic.enhancer.neustar.keys.email',
            'alias' => 'lead.email',
        ],
        '574'  => [
            'format' => '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', //in UTC
            'desc'   => 'mautic.enhancer.neustar.keys.date',
            'alias'  => 'lead.date_added',
        ],
        '875'  => [
            'desc' => 'mautic.enhancer.neustar.keys.scoring_xml',
        ],
        '1390' => [
            'desc'  => 'mautic.enhancer.neustar.keys.address.street',
            'alias' => ['lead.address1', 'lead.address2'],
        ],
        '1391' => [
            'desc'  => 'mautic.enhancer.neustar.keys.address.city',
            'alias' => 'lead.city',
        ],
        '1392' => [
            'desc'  => 'mautic.enhancer.neustar.keys.address.state',
            'alias' => 'lead.state',
        ],
        '1393' => [
            'desc'  => 'mautic.enhancer.neustar.keys.address.zip_code',
            'alias' => 'lead.zipcode',
        ],
        '1395' => [
            'required' => true,
            'format'   => '/^\w+,\w+(,\w)?$/',                       // (,\w)?$/', //Last,First,MiddleIn
            'desc'     => 'mautic.enhancer.neustar.keys.name',
            'alias'    => ['lead.lastname', 'lead.firstname', ['substr', 'extendedField.middlename', 0, 1]],
        ],
        '3251' => [
            'required' => true,
        ],
        '3256' => [
            'required' => true,
            'alias'    => ['setting.mkChannel', 'setting.mkVendorId', 'extendedField.military'],
        ],
    ];

    /**
     * @return array
     */
    public function getRequiredKeyFields()
    {
        return [
            'username'  => 'mautic.enhancer.neustar.required_key.username',
            'password'  => 'mautic.enhancer.neustar.required_key.password',
            'serviceId' => 'mautic.enhancer.neustar.required_key.service_id',
        ];
    }

    /**
     * @param \Symfony\Component\Form\FormBuilderInterface $builder
     * @param array                                        $data
     * @param string                                       $formArea
     */
    public function appendToForm(&$builder, $data, $formArea)
    {
        if ('features' === $formArea) {
            $builder
                ->add(
                    'endpoint',
                    UrlType::class,
                    [
                        'label'      => $this->translator->trans('mautic.enhancer.neustar.query.endpoint.label'),
                        'data'       => isset($data['endpoint']) ? $data['endpoint'] : '',
                        'required'   => true,
                        'label_attr' => ['class' => 'control-label'],
                        'attr'       => [
                            'class'   => 'form-control',
                            'tooltip' => $this->translator->trans('mautic.enhancer.neustar.query.endpoint.tooltip'),
                        ],
                    ]
                );
        }
        $this->appendNonFree($builder, $data, $formArea);
    }

    /**
     * @return string
     */
    public function getName()
    {
        return self::NEUSTAR_PREFIX.$this->getNeustarIntegrationName();
    }

    /**
     * Friendly name of implementation.
     *
     * @return string
     */
    abstract protected function getNeustarIntegrationName();

    /**
     * @return string
     */
    public function getDisplayName()
    {
        return ucwords(sprintf('%s %s Scoring', self::NEUSTAR_PREFIX, $this->getNeustarIntegrationName()));
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthenticationType()
    {
        return 'None';
    }

    /**
     * @param Lead $lead
     *
     * @return bool
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function doEnhancement(Lead $lead)
    {
        $keys = $this->getKeys();

        $query = [
            'username' => $keys['username'],
            'password' => $keys['password'],
            'svcid'    => $keys['serviceId'],
            'elems'    => $this->getNeustarElementId(),
            'version'  => '1.0',
            'transid'  => '1',
        ];

        foreach ($this->getNeustarServiceKeys() as $serviceKey) {
            $query['key'.$serviceKey] = $this->getServiceIdData($lead, $serviceKey);
        }

        $settings = $this->getIntegrationSettings()->getFeatureSettings();

        $neustarClient = new Client();
        /** @var Response $neustarResponse */
        $neustarResponse = $neustarClient->request('GET', $settings['endpoint'], ['query' => $query, 'timeout' => 3, 'connect_timeout' => 2]);

        return $this->processResponse($lead, $neustarResponse);
    }

    /**
     * Element ID of implementation.
     *
     * @return string
     */
    abstract protected function getNeustarElementId();

    /**
     * Returns a list of keys use to build the query.
     *
     * @return array
     */
    abstract protected function getNeustarServiceKeys();

    /**
     * Retrieve data for given service id.
     *
     * @param Lead lead
     * @param int
     *
     * @return string
     */
    abstract protected function getServiceIdData(Lead $lead, $serviceId);

    /**
     * process the neustar response.
     *
     * @param Lead     $lead
     * @param Response $neustarResponse
     *
     * @return bool
     */
    abstract protected function processResponse(Lead $lead, Response $neustarResponse);

    /**
     * Convert a DOM Document into a nested array.
     *
     * @param $root
     *
     * @return array|mixed
     */
    protected function domDocumentArray($root)
    {
        $result = [];

        if ($root->hasAttributes()) {
            foreach ($root->attributes as $attribute) {
                $result['@attributes'][$attribute->name] = $attribute->value;
            }
        }

        if ($root->hasChildNodes()) {
            if (1 == $root->childNodes->length) {
                $child = $root->childNodes->item(0);
                if (in_array($child->nodeType, [XML_TEXT_NODE, XML_CDATA_SECTION_NODE]) && !empty($child->nodeValue)) {
                    $result['_value'] = $child->nodeValue;

                    return 1 == count($result)
                        ? $result['_value']
                        : $result;
                }
            }
            $groups = [];
            foreach ($root->childNodes as $child) {
                if (!isset($result[$child->nodeName])) {
                    $result[$child->nodeName] = $this->domDocumentArray($child);
                } else {
                    if (!isset($groups[$child->nodeName])) {
                        $result[$child->nodeName] = [$result[$child->nodeName]];
                        $groups[$child->nodeName] = 1;
                    }
                    $result[$child->nodeName][] = $this->domDocumentArray($child);
                }
            }
        }

        return $result;
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use Mautic\LeadBundle\Entity\Lead;

/**
 * Class PhoneToPartsIntegration.
 */
class PhoneToPartsIntegration extends AbstractEnhancerIntegration
{
    /**
     * @return string
     */
    public function getName()
    {
        return 'PhoneToParts';
    }

    /**
     * @return string
     */
    public function getDisplayName()
    {
        return 'Split Phone Into Parts';
    }

    /**
     * @returns array[]
     */
    protected function getEnhancerFieldArray()
    {
        return [
            'ptp_area_code'   => [
                'label' => 'Area Code',
            ],
            'ptp_prefix'      => [
                'label' => 'Prefix',
            ],
            'ptp_line_number' => [
                'label' => 'Line Number',
            ],
        ];
    }

    /**
     * @param Lead $lead
     *
     * @return bool
     */
    public function doEnhancement(Lead $lead)
    {
        $phone = preg_replace('/\D+/', '', $lead->getPhone());
        if (empty($phone)) {
            $phone = preg_replace('/\D+/', '', $lead->getMobile());
        }
        $phone = substr($phone, -10);

        if (10 === strlen($phone)) {
            $lead->addUpdatedField('ptp_area_code', substr($phone, -10, 3));
            $lead->addUpdatedField('ptp_prefix', substr($phone, -7, 3));
            $lead->addUpdatedField('ptp_line_number', substr($phone, -4, 4));

            return true;
        }

        return false;
    }

    /**
     * @param \Symfony\Component\Form\FormBuilder $builder
     * @param array                               $data
     * @param string                              $formArea
     */
    public function appendToForm(&$builder, $data, $formArea)
    {
        if ('features' === $formArea) {
            $builder->add(
                'autorun_enabled',
                \Symfony\Component\Form\Extension\Core\Type\HiddenType::class,
                [
                    'data' => true,
                ]
            );
        }
    }

    /**
     * Get the type of authentication required for this API.  Values can be none, key, oauth2 or callback
     * (will call $this->authenticationTypeCallback).
     *
     * @return string
     */
    public function getAuthenticationType()
    {
        return 'none';
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use Doctrine\ORM\OptimisticLockException;
use Exception;
use Joomla\Http\HttpFactory;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadField;
use Mautic\LeadBundle\Event\LeadFieldEvent;
use Mautic\LeadBundle\Helper\FormFieldHelper;
use Mautic\LeadBundle\LeadEvents;
use Mautic\PluginBundle\Event\PluginIntegrationRequestEvent;
use Mautic\PluginBundle\Exception\ApiErrorException;
use Mautic\PluginBundle\Integration\AbstractIntegration;
use Mautic\PluginBundle\PluginEvents;
use MauticPlugin\MauticEnhancerBundle\Event\ContactLedgerContextEvent;
use MauticPlugin\MauticEnhancerBundle\Event\MauticEnhancerEvent;
use MauticPlugin\MauticEnhancerBundle\MauticEnhancerEvents;
use Symfony\Component\Form\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer;

/**
 * Class AbstractEnhancerIntegration.
 *
 * @method string getAuthenticationType()
 * @method string getName()
 */
abstract class AbstractEnhancerIntegration extends AbstractIntegration
{
    /** @var array */
    protected $config;

    /** @var \Mautic\CampaignBundle\Entity\Campaign */
    protected $campaign;

    /** @var bool */
    protected $isPush = false;

    public function buildEnhancerFields()
    {
        $integration = $this->getIntegrationSettings();

        $exists = array_keys(
            $this->fieldModel->getFieldList(false)
        );

        if ($integration->getIsPublished()) {
            /** @var LeadField[] $newFields */
            $newFields      = [];
            $possibleFields = $this->getEnhancerFieldArray();
            foreach ($possibleFields as $alias => $attributes) {
                if (in_array($alias, $exists)) {
                    // The field already exists
                    continue;
                }

                $newField = new LeadField();
                $newField->setAlias($alias);

                $attributes = array_merge($this->enhancerFieldDefaults(), $attributes);
                if (!isset($attributes['label'])) {
                    $attributes['label'] = implode(' ', array_map('ucfirst', explode('_', $alias)));
                }

                switch ($attributes['type']) {
                    case 'boolean':
                        if (!isset($attibutes['properties'])) {
                            $attributes['properties'] = ['no' => 'No', 'yes' => 'Yes'];
                        }
                        break;
                    case 'number':
                        if (!isset($attibutes['properties'])) {
                            $attributes['properties'] = [
                                'roundmode' => NumberToLocalizedStringTransformer::ROUND_HALF_UP,
                                'scale'     => 0,   // precision changed to scale in symfony 2.7
                                'precision' => 0,   // trusting the validator to do the right thing
                            ];
                        }
                        break;
                    case 'time': //intentional no break
                        $attributes['is_listable'] = false;
                    // no break
                    default:
                        if (!isset($attributes['properties'])) {
                            $attributes['properties'] = [];
                        }
                }

                $result = FormFieldHelper::validateProperties($attributes['type'], $attributes['properties']);
                if (!$result[0]) {
                    $this->logger->error('Installation Failed: "'.$alias.''.$result[1].'"');
                    $this->em->rollback();

                    return;
                }

                foreach ($attributes as $attribute => $value) {
                    //convert snake case to camel case
                    $method = 'set'.implode('', array_map('ucfirst', explode('_', $attribute)));

                    try {
                        call_user_func(
                            [$newField, $method],
                            $value
                        );
                    } catch (\Exception $e) {
                        $this->logger->error('Failed to set attribute: "'.$e->getMessage().'"');
                    }
                }

                $this->fieldModel->setTimestamps($newField, true);

                $event = new LeadFieldEvent($newField, true);
                $event = $this->dispatcher->dispatch(LeadEvents::FIELD_PRE_SAVE, $event);
                // implicitly call flush once all fields are ready to be added
                // which allows us to rollback the entire integration install
                $this->fieldModel->getRepository()->saveEntity($newField, false);
                $this->dispatcher->dispatch(LeadEvents::FIELD_POST_SAVE, $event);

                $newFields[] = $newField;
            }

            try {
                $this->em->flush();
            } catch (OptimisticLockException $ole) {
                $this->logger->error($this->getDisplayName().' failed to install: '.$ole->getMessage());
                //add flash failure message?
            }
        }
    }

    /**
     * @returns array[]
     */
    abstract protected function getEnhancerFieldArray();

    /**
     * @return array
     */
    private function enhancerFieldDefaults()
    {
        return [
            'is_published' => true,
            'type'         => 'text',
            'group'        => 'enhancement',
            'object'       => $this->getLeadFieldClassName(),
        ];
    }

    /**
     * @return string
     */
    private function getLeadFieldClassName()
    {
        return class_exists('MauticPlugin\MauticExtendedFieldBundle\MauticExtendedFieldBundle')
            ? 'extendedField'
            : 'lead';
    }

    /**
     * @return string
     */
    public function getDisplayName()
    {
        $spacedName = preg_replace('/([a-z])([A-Z])/', '$1 $2', $this->getName());

        return sprintf('%s Data Enhancer', $spacedName);
    }

    /**
     * @param array $settings
     *
     * @return array
     */
    public function getFormLeadFields($settings = [])
    {
        static $fields = [];

        if (empty($fields)) {
            $name      = $this->getName();
            $available = $this->getAvailableLeadFields($settings);
            if (empty($available) || !is_array($available)) {
                return [];
            }

            foreach ($available as $field => $details) {
                $label            = empty($details['label']) ? false : $details['label'];
                $matchedFieldName = $this->matchFieldName($field);

                switch ($details['type']) {
                    case 'string':
                    case 'boolean':
                        $fields[$matchedFieldName] = (!$label)
                            ? $this->translator->transConditional(
                                "mautic.integration.common.{$matchedFieldName}",
                                "mautic.integration.{$name}.{$matchedFieldName}.label"
                            )
                            : $label;
                        break;
                    case 'object':
                        if (isset($details['fields'])) {
                            foreach ($details['fields'] as $property) {
                                $matchedFieldName          = $this->matchFieldName($field, $property);
                                $fields[$matchedFieldName] = (!$label)
                                    ? $this->translator->transConditional(
                                        "mautic.integration.common.{$matchedFieldName}",
                                        "mautic.integration.{$name}.{$matchedFieldName}.label"
                                    )
                                    : $label;
                            }
                        } else {
                            $fields[$field] = (!$label)
                                ? $this->translator->transConditional(
                                    "mautic.integration.common.{$matchedFieldName}",
                                    "mautic.integration.{$name}.{$matchedFieldName}.label"
                                )
                                : $label;
                        }
                        break;
                    case 'array_object':
                        if ('urls' == $field || 'url' == $field) {
                            foreach ($details['fields'] as $property) {
                                $fields["{$property}Urls"] = (!$label)
                                    ? $this->translator->transConditional(
                                        "mautic.integration.common.{$property}Urls",
                                        "mautic.integration.{$name}.{$property}Urls"
                                    )
                                    : $label;
                            }
                        } elseif (isset($details['fields'])) {
                            foreach ($details['fields'] as $property) {
                                $matchedFieldName          = $this->matchFieldName($field, $property);
                                $fields[$matchedFieldName] = (!$label)
                                    ? $this->translator->transConditional(
                                        "mautic.integration.common.{$matchedFieldName}",
                                        "mautic.integration.{$name}.{$matchedFieldName}.label"
                                    )
                                    : $label;
                            }
                        } else {
                            $fields[$matchedFieldName] = (!$label)
                                ? $this->translator->transConditional(
                                    "mautic.integration.common.{$matchedFieldName}",
                                    "mautic.integration.{$name}.{$matchedFieldName}.label"
                                )
                                : $label;
                        }
                        break;
                }
            }
            if ($this->sortFieldsAlphabetically()) {
                uasort($fields, 'strnatcmp');
            }
        }

        return $fields;
    }

    /**
     * @return string[]
     */
    public function getSupportedFeatures()
    {
        return ['push_lead'];
    }

    /**
     * @param Lead  $lead
     * @param array $config
     *
     * @return bool
     */
    public function pushLead(Lead &$lead, array $config = [])
    {
        $this->logger->debug('Pushing to Enhancer '.$this->getName(), $config);

        if (!$this->getIntegrationSettings()->getIsPublished()) {
            return true;
        }

        $this->config = $config;
        $this->isPush = true;

        try {
            if ($this->doEnhancement($lead)) {
                $this->saveLead($lead);
            }
        } catch (\Exception $exception) {
            $this->logIntegrationError(
                new ApiErrorException(
                    'There was an issue using enhancer: '.$this->getName(),
                    0,
                    $exception
                ),
                $lead
            );
        }
        $event = new MauticEnhancerEvent($this, $lead, $this->getCampaign());
        $this->dispatcher->dispatch(MauticEnhancerEvents::ENHANCER_COMPLETED, $event);

        // Always return true to prevent campaign actions from being halted, even if an enhancer fails.
        return true;
    }

    /**
     * @param Lead $lead
     *
     * @return bool
     */
    abstract public function doEnhancement(Lead $lead);

    /**
     * @param $lead
     */
    public function saveLead($lead)
    {
        $event = new ContactLedgerContextEvent(
            $this->campaign, $this, 'enhanced', $lead
        );
        $this->dispatcher->dispatch(
            'mautic.contactledger.context_create',
            $event
        );
        $this->leadModel->saveEntity($lead);
    }

    /**
     * @return bool|\Doctrine\Common\Proxy\Proxy|\Mautic\CampaignBundle\Entity\Campaign|null|object
     */
    private function getCampaign()
    {
        if (!$this->campaign) {
            $config = $this->config;
            try {
                if (is_int($config['campaignId'])) {
                    // In the future a core fix may provide the correct campaign id.
                    $this->campaign = $this->em->getReference(
                        'Mautic\CampaignBundle\Enitity\Campaign',
                        $config['campaignId']
                    );
                } else {
                    // Otherwise we must obtain it from the unit of work.
                    /** @var \Doctrine\ORM\UnitOfWork $identityMap */
                    $identityMap = $this->em->getUnitOfWork()->getIdentityMap();
                    if (isset($identityMap['Mautic\CampaignBundle\Entity\LeadEventLog'])) {
                        /** @var \Mautic\CampaignBundle\Entity\LeadEventLog $leadEventLog */
                        foreach ($identityMap['Mautic\CampaignBundle\Entity\LeadEventLog'] as $leadEventLog) {
                            $properties = $leadEventLog->getEvent()->getProperties();
                            if (
                                $properties['_token'] === $config['_token']
                                && $properties['campaignId'] === $config['campaignId']
                            ) {
                                $this->campaign = $leadEventLog->getCampaign();
                                break;
                            }
                        }
                    }
                }
            } catch (\Exception $e) {
            }
        }

        return $this->campaign;
    }

    /**
     * @param Lead $lead
     */
    public function applyCost($lead)
    {
        $costPerEnhancement = $this->getCostPerEnhancement();
        if ($costPerEnhancement) {
            $attribution = $lead->getFieldValue('attribution');
            // $lead->attribution -= $costPerEnhancement;
            $lead->addUpdatedField(
                'attribution',
                $attribution - $costPerEnhancement,
                $attribution
            );
        }
    }

    /**
     * Return null if there is no cost attributed to the integration.
     */
    public function getCostPerEnhancement()
    {
        return null;
    }

    /**
     * @return int
     */
    public function getId()
    {
        return $this->getIntegrationSettings()->getId();
    }

    /**
     * Make a basic call using cURL to get the data.
     * TODO migrate to Guzzle instead.
     *
     * @param        $url
     * @param array  $parameters
     * @param string $method
     * @param array  $settings
     *
     * @return mixed|string
     */
    public function makeRequest($url, $parameters = [], $method = 'GET', $settings = [])
    {
        // If not authorizing the session itself, check isAuthorized which will refresh tokens if applicable
        if (empty($settings['authorize_session'])) {
            $this->isAuthorized();
        }

        $method   = strtoupper($method);
        $authType = (empty($settings['auth_type'])) ? $this->getAuthenticationType() : $settings['auth_type'];

        list($parameters, $headers) = $this->prepareRequest($url, $parameters, $method, $settings, $authType);

        if (empty($settings['ignore_event_dispatch'])) {
            $event = $this->dispatcher->dispatch(
                PluginEvents::PLUGIN_ON_INTEGRATION_REQUEST,
                new PluginIntegrationRequestEvent($this, $url, $parameters, $headers, $method, $settings, $authType)
            );

            $headers    = $event->getHeaders();
            $parameters = $event->getParameters();
        }

        if (!isset($settings['query'])) {
            $settings['query'] = [];
        }

        if (isset($parameters['append_to_query'])) {
            $settings['query'] = array_merge(
                $settings['query'],
                $parameters['append_to_query']
            );

            unset($parameters['append_to_query']);
        }

        if (isset($parameters['post_append_to_query'])) {
            $postAppend = $parameters['post_append_to_query'];
            unset($parameters['post_append_to_query']);
        }

        if (!$this->isConfigured()) {
            return [
                'error' => [
                    'message' => $this->translator->trans(
                        'mautic.integration.missingkeys'
                    ),
                ],
            ];
        }

        if ('GET' == $method && !empty($parameters)) {
            $parameters = array_merge($settings['query'], $parameters);
            $query      = http_build_query($parameters);
            $url .= (false === strpos($url, '?')) ? '?'.$query : '&'.$query;
        } elseif (!empty($settings['query'])) {
            $query = http_build_query($settings['query']);
            $url .= (false === strpos($url, '?')) ? '?'.$query : '&'.$query;
        }

        if (isset($postAppend)) {
            $url .= $postAppend;
        }

        // Check for custom content-type header
        if (!empty($settings['content_type'])) {
            $settings['encoding_headers_set'] = true;
            $headers[]                        = "Content-Type: {$settings['content_type']}";
        }

        if ('GET' !== $method) {
            if (!empty($parameters)) {
                if ('oauth1a' == $authType) {
                    $parameters = http_build_query($parameters);
                }
                if (!empty($settings['encode_parameters'])) {
                    if ('json' == $settings['encode_parameters']) {
                        //encode the arguments as JSON
                        $parameters = json_encode($parameters);
                        if (empty($settings['encoding_headers_set'])) {
                            $headers[] = 'Content-Type: application/json';
                        }
                    }
                }
            } elseif (isset($settings['post_data'])) {
                $parameters = $settings['post_data'];
            }
        }

        $options = [
            CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_1,
            CURLOPT_HEADER         => 1,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_FOLLOWLOCATION => 0,
            CURLOPT_REFERER        => $this->getRefererUrl(),
            CURLOPT_USERAGENT      => $this->getUserAgent(),
        ];

        if (isset($settings['curl_options']) && is_array($settings['curl_options'])) {
            $options = $settings['curl_options'] + $options;
        }

        if (isset($settings['ssl_verifypeer'])) {
            $options[CURLOPT_SSL_VERIFYPEER] = $settings['ssl_verifypeer'];
        }

        $connector = HttpFactory::getHttp(
            [
                'transport.curl' => $options,
            ]
        );

        $parseHeaders = (isset($settings['headers'])) ? array_merge($headers, $settings['headers']) : $headers;
        // HTTP library requires that headers are in key => value pairs
        $headers = [];
        if (is_array($parseHeaders)) {
            foreach ($parseHeaders as $key => $value) {
                if (false !== strpos($value, ':')) {
                    list($key, $value) = explode(':', $value);
                    $key               = trim($key);
                    $value             = trim($value);
                }

                $headers[$key] = $value;
            }
        }

        try {
            $timeout = (isset($settings['request_timeout'])) ? (int) $settings['request_timeout'] : 10;
            switch ($method) {
                case 'GET':
                    $result = $connector->get($url, $headers, $timeout);
                    break;
                case 'POST':
                case 'PUT':
                case 'PATCH':
                    $connectorMethod = strtolower($method);
                    $result          = $connector->$connectorMethod($url, $parameters, $headers, $timeout);
                    break;
                case 'DELETE':
                    $result = $connector->delete($url, $headers, $timeout);
                    break;
            }
        } catch (Exception $exception) {
            $this->handleEnchancerException($this->settings->getName(), $exception);

            return ['error' => ['message' => $exception->getMessage(), 'code' => $exception->getCode()]];
        }
        if (empty($settings['ignore_event_dispatch'])) {
            $event->setResponse($result);
            $this->dispatcher->dispatch(
                PluginEvents::PLUGIN_ON_INTEGRATION_RESPONSE,
                $event
            );
        }
        if (!empty($settings['return_raw'])) {
            return $result;
        } else {
            $response = $this->parseCallbackResponse($result->body, !empty($settings['authorize_session']));

            return $response;
        }
    }

    /**
     * @param string    $className
     * @param Exception $exception
     */
    public function handleEnchancerException($className, $exception)
    {
        if (function_exists('newrelic_notice_error')) {
            call_user_func(
                'newrelic_notice_error',
                'Enhancer Connection Error: '.$className,
                $exception
            );
        }
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Command;

use Mautic\CoreBundle\Command\ModeratedCommand;
use MauticPlugin\MauticEnhancerBundle\Model\CityStatePostalCodeModel;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Class InstallCityStatePostalCodeDataCommand.
 */
class InstallCityStatePostalCodeDataCommand extends ModeratedCommand
{
    protected function configure()
    {
        $this
            ->setName('mautic:integration:enhancer:installcspcdata')
            ->setDescription('Imports postal code, city, state, county, latitude, longitude and country')
            ->setHelp(
                'This command will download and rebuild the CityStateFromPostalCode reference table. It uses the file located at http://download.geonames.org/export/zip/allCountries.zip as its data source.'
            );
    }

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     *
     * @return bool
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        try {
            /** @var CityStatePostalCodeModel $model */
            $model = $this->getContainer()->get('mautic.enhancer.model.citystatepostalcode');
            if ($model->updateReferenceTable()) {
                $output->writeln('Reference data successfully loaded. CityStateFromPostalCode is ready for use.');

                return true;
            }
        } catch (\Exception $e) {
            $output->write('CityStatePostalCode: '.$e->getMessage());
        }
        $output->writeln('Failed to load reference table. CityStateFromPostalCode is not ready.');

        return false;
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\EventListener;

use Mautic\CoreBundle\EventListener\CommonSubscriber;
use Mautic\LeadBundle\Event\LeadEvent;
use Mautic\LeadBundle\LeadEvents;
use Mautic\PluginBundle\Exception\ApiErrorException;
use MauticPlugin\MauticEnhancerBundle\Helper\EnhancerHelper;

/**
 * Class LeadSubsciber.
 */
class LeadSubscriber extends CommonSubscriber
{
    /** @var \MauticPlugin\MauticEnhancerBundle\Helper\EnhancerHelper */
    protected $enhancerHelper;

    /** @var array Leads that have already ran the auto-enhancers in this session by ID. */
    protected $leadsEnhanced = [];

    /**
     * LeadSubscriber constructor.
     *
     * @param EnhancerHelper $helper
     */
    public function __construct(EnhancerHelper $helper)
    {
        $this->enhancerHelper = $helper;
    }

    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return [
            LeadEvents::LEAD_PRE_SAVE => ['doAutoRunEnhancements', 10],
        ];
    }

    /**
     * Runs enhancements before the Lead is persisted.
     *
     * @param LeadEvent $event
     *
     * @throws ApiErrorException
     */
    public function doAutoRunEnhancements(LeadEvent $event)
    {
        $lead = $event->getLead();
        if ($lead && (null !== $lead->getDateIdentified() || !$lead->isAnonymous())) {
            // Ensure we do not duplicate this work within the same session.
            $leadKey = strtolower(
                implode(
                    '|',
                    [
                        $lead->getFirstname(),
                        ($lead->getLastActive() ? $lead->getLastActive()->format('c') : ''),
                        $lead->getEmail(),
                        $lead->getPhone(),
                        $lead->getMobile(),
                    ]
                )
            );
            if (strlen($leadKey) > 3) {
                if (isset($this->leadsEnhanced[$leadKey])) {
                    return;
                } else {
                    $this->leadsEnhanced[$leadKey] = true;
                }
            }

            /** @var \MauticPlugin\MauticEnhancerBundle\Integration\AbstractEnhancerIntegration */
            $integrations = $this->enhancerHelper->getEnhancerIntegrations();
            foreach ($integrations as $integration) {
                $settings = $integration->getIntegrationSettings();
                if ($settings->getIsPublished()) {
                    $features = $settings->getFeatureSettings();
                    if (isset($features['autorun_enabled']) && $features['autorun_enabled']) {
                        try {
                            $integration->doEnhancement($lead);
                        } catch (\Exception $exception) {
                            $e = new ApiErrorException(
                                'There was an issue using enhancer: '.$integration->getName(),
                                0,
                                $exception
                            );
                            if (!empty($lead)) {
                                $e->setContact($lead);
                            }
                            throw $e;
                        }
                    }
                }
            }
            $this->logger->info('doAutoRunEnhancements complete');
        }
    }

    /**
     * @param LeadEvent $event
     *
     * @throws ApiErrorException
     */
    public function doPostSaveEnhancements(LeadEvent $event)
    {
        /**
         * @var \MauticPlugin\MauticEnhancerBundle\Integration\AbstractEnhancerIntegration[]
         */
        $integrations = $this->enhancerHelper->getEnhancerIntegrations();
        foreach ($integrations as $integration) {
            if ($integration->isConfigured() && $integration->getIntegrationSettings()->getIsPublished()) {
                $keys = $integration->getKeys();
                if ($keys['autorun_enabled']) {
                    try {
                        $lead = $event->getLead();
                        $integration->doEnhancement($lead);
                    } catch (\Exception $exception) {
                        $e = new ApiErrorException(
                            'There was an issue using enhancer: '.$integration->getName(),
                            0,
                            $exception
                        );
                        if (!empty($lead)) {
                            $e->setContact($lead);
                        }
                        throw $e;
                    }
                }
            }
        }
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use Doctrine\DBAL\Query\QueryBuilder;
use Mautic\LeadBundle\Entity\Lead;
use MauticPlugin\MauticEnhancerBundle\Entity\PluginEnhancerCityStatePostalCode;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;

/**
 * Class CityStateFromPostalCodeIntegration.
 */
class CityStateFromPostalCodeIntegration extends AbstractEnhancerIntegration
{
    const MAX_LEV_DIST = 4;

    /** @var string */
    const COUNTRIES = 'AF,AX,AL,DZ,AS,AD,AO,AI,AQ,AG,AR,AM,AW,AU,AT,AZ,BS,BH,BD,BB,BY,BE,BZ,BJ,BM,BT,BO,BQ,BA,BW,BV,BR,IO,BN,BG,BF,BI,KH,CM,CA,CV,KY,CF,TD,CL,CN,CX,CC,CO,KM,CG,CD,CK,CR,CI,HR,CU,CW,CY,CZ,DK,DJ,DM,DO,EC,EG,SV,GQ,ER,EE,ET,FK,FO,FJ,FI,FR,GF,PF,TF,GA,GM,GE,DE,GH,GI,GR,GL,GD,GP,GU,GT,GG,GN,GW,GY,HT,HM,VA,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IM,IL,IT,JM,JP,JE,JO,KZ,KE,KI,KP,KR,KW,KG,LA,LV,LB,LS,LR,LY,LI,LT,LU,MO,MK,MG,MW,MY,MV,ML,MT,MH,MQ,MR,MU,YT,MX,FM,MD,MC,MN,ME,MS,MA,MZ,MM,NA,NR,NP,NL,NC,NZ,NI,NE,NG,NU,NF,MP,NO,OM,PK,PW,PS,PA,PG,PY,PE,PH,PN,PL,PT,PR,QA,RE,RO,RU,RW,BL,SH,KN,LC,MF,PM,VC,WS,SM,ST,SA,SN,RS,SC,SL,SG,SX,SK,SI,SB,SO,ZA,GS,SS,ES,LK,SD,SR,SJ,SZ,SE,CH,SY,TW,TJ,TZ,TH,TL,TG,TK,TO,TT,TN,TR,TM,TC,TV,UG,UA,AE,GB,US,UM,UY,UZ,VU,VE,VN,VG,VI,WF,EH,YE,ZM,ZW';

    public static $US_STATES = [
        'AL' => 'Alabama',          'AK' => 'Alaska',
        'AZ' => 'Arizona',          'AR' => 'Arkansas',
        'CA' => 'California',       'CO' => 'Colorado',
        'CT' => 'Connecticut',      'DE' => 'Delaware',
        'FL' => 'Florida',          'GA' => 'Georgia',
        'HI' => 'Hawaii',           'ID' => 'Idaho',
        'IL' => 'Illinois',         'IN' => 'Indiana',
        'IA' => 'Iowa',             'KS' => 'Kansas',
        'KY' => 'Kentucky',         'LA' => 'Louisiana',
        'ME' => 'Maine',            'MD' => 'Maryland',
        'MA' => 'Massachusetts',    'MI' => 'Michigan',
        'MN' => 'Minnesota',        'MS' => 'Mississippi',
        'MO' => 'Missouri',         'MT' => 'Montana',
        'NE' => 'Nebraska',         'NV' => 'Nevada',
        'NH' => 'New Hampshire',    'NJ' => 'New Jersey',
        'NM' => 'New Mexico',       'NY' => 'New York',
        'NC' => 'North Carolina',   'ND' => 'North Dakota',
        'OH' => 'Ohio',             'OK' => 'Oklahoma',
        'OR' => 'Oregon',           'PA' => 'Pennsylvania',
        'RI' => 'Rhode Island',     'SC' => 'South Carolina',
        'SD' => 'South Dakota',     'TN' => 'Tennessee',
        'TX' => 'Texas',            'UT' => 'Utah',
        'VT' => 'Vermont',          'VA' => 'Virginia',
        'WA' => 'Washington',       'WV' => 'West Virginia',
        'WI' => 'Wisconsin',        'WY' => 'Wyoming',
        'DC' => 'District of Columbia',
        ];

    /**
     * @var \MauticPlugin\MauticEnhancerBundle\Model\CityStatePostalCodeModel
     */
    protected $integrationModel;

    /**
     * @return string
     */
    public function getName()
    {
        return 'CityStateFromPostalCode';
    }

    /**
     * @return string
     */
    public function getDisplayName()
    {
        return 'Fill Missing City, State/Province and County From Postal Code';
    }

    /**
     * @return array
     */
    protected function getEnhancerFieldArray()
    {
        // hi-jacking build routine to create reference table
        // this will ensure the table is installed
        try {
            $this->getIntegrationModel()->verifyReferenceTable();
        } catch (\Exception $e) {
            $this->logger->error('CityStateFromPostalCode: '.$e->getMessage());
            $this->settings->setIsPublished(false);
            $this->session->getFlashBag()->add(
                'notice',
                $this->translator->trans('mautic.enhancer.integration.citystatefromzip.failure')
            );
        }

        return [
            'county' => [
                'label' => 'County',
                'type'  => 'text',
            ],
        ];
    }

    /**
     * @return \Mautic\CoreBundle\Model\AbstractCommonModel|\MauticPlugin\MauticEnhancerBundle\Model\CityStatePostalCodeModel
     */
    protected function getIntegrationModel()
    {
        if (!isset($this->integrationModel)) {
            $this->integrationModel = $this->factory->getModel('enhancer.citystatepostalcode');
        }

        return $this->integrationModel;
    }

    /**
     * @param Lead $lead
     *
     * @return bool
     */
    public function doEnhancement(Lead $lead)
    {
        $persist = false;

        $leadCity   = $lead->getCity();
        $leadState  = $lead->getState();
        $leadCounty = $lead->getFieldValue('county');

        // Get country in standard ISO3166 format.
        $leadCountry = $lead->getCountry();

        if (empty($leadCountry)) {
            /** @var mixed $ipDetails */
            $ipDetails   = $this->factory->getIpAddress()->getIpDetails();
            $leadCountry = isset($ipDetails['country']) ? strtoupper($ipDetails['country']) : 'US';
        } elseif (2 !== strlen($leadCountry)) {
            $leadCountry = $this->countryNameToISO3166($leadCountry);
        }

        // Get standardized zip without +4
        $leadZipCode = $lead->getZipcode();
        $dash        = strpos((string) $leadZipCode, '-');
        $leadZipCode = $dash ? substr((string) $leadZipCode, 0, $dash) : $leadZipCode;

        // Override User city, state if zip code given
        if (!(empty($leadCountry) || empty($leadZipCode))) {
            /** @var PluginEnhancerCityStatePostalCode $cityStatePostalCode */
            $cityStatePostalCode = $this->getIntegrationModel()->getRepository()->findOneBy(
                [
                    'postalCode' => $leadZipCode,
                    'country'    => $leadCountry,
                ]
            );

            if (null !== $cityStatePostalCode) {
                if (/*empty($leadCity) &&*/ !empty($cityStatePostalCode->getCity() && $leadCity !== $cityStatePostalCode->getCity())) {
                    $this->logger->debug('CityStateFromPostalCode: Found city for lead '.$lead->getId());
                    $lead->setCity($cityStatePostalCode->getCity());
                    $persist = true;
                }

                if (/*empty($leadState) &&*/ !empty($cityStatePostalCode->getStateProvince()) && $leadState !== $cityStatePostalCode->getStateProvince()) {
                    $this->logger->debug('CityStateFromPostalCode: Found state/province for lead '.$lead->getId());
                    $spLong  = $cityStatePostalCode->getStateProvince();
                    $spShort = in_array($spLong, self::$US_STATES)
                        ? array_search($spLong, self::$US_STATES)
                        : $spLong;
                    $lead->setState($spShort);
                    $persist = true;
                }

                if (/*empty($leadCounty) &&*/ !empty($cityStatePostalCode->getCounty()) && $leadCounty !== $cityStatePostalCode->getCounty()) {
                    $this->logger->debug('CityStateFromPostalCode: Found county for lead '.$lead->getId());
                    $lead->addUpdatedField('county', $cityStatePostalCode->getCounty(), $leadCounty);
                    $persist = true;
                }
            }
        } elseif (
            !(empty($leadCity) || empty($leadState) || empty($leadCountry)) &&
            empty($leadZipCode) &&
            empty($lead->getAddress1())
        ) {
            $stateProvince = array_key_exists($leadState, self::$US_STATES)
                ? self::$US_STATES[$leadState]
                : $leadState;
            /** @var PluginEnhancerCityStatePostalCode $cityStatePostalCode */
            $cityStatePostalCode = $this->getIntegrationModel()->getRepository()->findOneBy(
                [
                    'city'          => $leadCity,
                    'stateProvince' => $stateProvince,
                    'country'       => $leadCountry,
                ]
            );
            if (null !== $cityStatePostalCode) {
                $this->logger->debug('CityStateFromPostalCode: Found zipcode for lead '.$lead->getId());
                $lead->setZipcode($cityStatePostalCode->getPostalCode());
                $persist = true;
            } else {
                //handle non-exact city match
                /** @var QueryBuilder $qb */
                $qb = $this->em->getConnection()->createQueryBuilder();

                $qb->select(['city', 'MIN(postal_code) AS postal_code'])
                    ->from('plugin_enhancer_city_state_postal_code')
                    ->where(
                        'state_province = :state',
                        'country = :country'
                    )
                    ->groupBy('city')
                    ->setParameters([
                        'state'   => $stateProvince,
                        'country' => $leadCountry,
                    ]);

                $results = $qb->execute()->fetchAll();
                $picked  = [];
                foreach ($results as $result) {
                    if (empty($picked)) {
                        $picked['postal_code'] = $result['postal_code'];
                        $picked['distance']    = levenshtein($leadCity, $result['city']);
                    } else {
                        $distance = levenshtein($leadCity, $result['city']);
                        if ($distance < $picked['distance']) {
                            $picked['postal_code'] = $result['postal_code'];
                            $picked['distance']    = $distance;
                        }
                    }
                }
                if (isset($picked['distance']) && $picked['distance'] < self::MAX_LEV_DIST) {
                    $lead->setZipcode($picked['postal_code']);
                    $persist = true;
                }
            }
        }
        $this->em->clear('PluginEnhancerCityStatePostalCode');

        return $persist;
    }

    /**
     * @param $countryName
     *
     * @return string
     */
    private function countryNameToISO3166($countryName)
    {
        foreach (explode(',', self::COUNTRIES) as $countryCode) {
            $generatedCountryName = \Locale::getDisplayRegion('-'.$countryCode, 'EN');
            if (0 === strcasecmp($countryName, $generatedCountryName)) {
                return $countryCode;
                break;
            }
        }

        return '';
    }

    /**
     * @return string
     */
    public function getAuthenticationType()
    {
        return 'none';
    }

    /**
     * @param \Symfony\Component\Form\FormBuilderInterface $builder
     * @param array                                        $data
     * @param string                                       $formArea
     */
    public function appendToForm(&$builder, $data, $formArea)
    {
        if ('features' === $formArea) {
            $builder->add(
                'autorun_enabled',
                HiddenType::class,
                [
                    'data' => true,
                ]
            );
        }
    }

    /**
     * @param $section
     *
     * @return mixed
     */
    public function getFormNotes($section)
    {
        if ('custom' === $section) {
            return $this->translator->trans('mautic.enhancer.integration.citystatefromzip.custom_note');
        }
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use Mautic\CoreBundle\Model\AbstractCommonModel;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\UtmTag;
use MauticPlugin\MauticEnhancerBundle\Model\AnuraModel;
use Symfony\Component\Form\Extension\Core\Type\TextType;

/**
 * Class AlcazarIntegration.
 */
class AnuraIntegration extends AbstractEnhancerIntegration
{
    /* @var \MauticPlugin\MauticEnhancerBundle\Integration\NonFreeEnhancerTrait */
    use NonFreeEnhancerTrait {
        appendToForm as appendNonFreeFields;
    }

    /**
     * @var AnuraModel
     */
    protected $integrationModel;

    /**
     * @return string
     */
    public function getName()
    {
        return 'Anura';
    }

    /**
     * @return string
     */
    public function getDisplayName()
    {
        return 'Anura Suspicious Check';
    }

    /**
     * @return string
     */
    public function getAuthenticationType()
    {
        return 'keys';
    }

    /**
     * @return AnuraModel|AbstractCommonModel
     */
    public function getModel()
    {
        if (!isset($this->integrationModel)) {
            $this->integrationModel = $this->factory->getModel('enhancer.anura');
            $this->integrationModel->setup($this);
        }

        return $this->integrationModel;
    }

    /**
     * @return array
     */
    public function getRequiredKeyFields()
    {
        return [
            'endpoint' => $this->translator->trans('mautic.enhancer.integration.anura.endpoint.label'),
            'instance' => $this->translator->trans('mautic.enhancer.integration.anura.instance.label'),
        ];
    }

    /**
     * @return array
     */
    public function getSupportedFeatures()
    {
        return ['push_lead'];
    }

    /**
     * @param \Symfony\Component\Form\FormBuilderInterface $builder
     * @param array                                        $data
     * @param string                                       $formArea
     */
    public function appendToForm(&$builder, $data, $formArea)
    {
        if ('features' === $formArea) {
            $builder->add(
                'default_user_agent',
                TextType::class,
                [
                    'label'      => $this->translator->trans('mautic.enhancer.integration.anura.user_agent.label'),
                    'data'       => isset($data['default_user_agent']) ? $data['default_user_agent'] : '',
                    'required'   => true,
                    'label_attr' => ['class' => 'control-label'],
                    'attr'       => [
                        'class'   => 'form-control',
                        'tooltip' => $this->translator->trans('mautic.enhancer.integration.anura.user_agent.tooltip'),
                    ],
                ]
            );

            $this->appendNonFreeFields($builder, $data, $formArea, true);
        }
    }

    /**
     * @return array|mixed
     */
    protected function getEnhancerFieldArray()
    {
        return [
            'anura_result' => [
                'label' => $this->translator->trans('mautic.enhancer.integration.anura.anura_result.label'),
                'group' => 'enhancement',
            ],
        ];
    }

    /**
     * @param Lead $lead
     *
     * @return bool
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function doEnhancement(Lead $lead)
    {
        $didEnhnacement = false;

        if (!empty($lead)) {
            $ipAddresses = $lead->getIpAddresses()->getKeys();
            if (count($ipAddresses)) {
                $ipAddress = array_pop($ipAddresses);

                $utmTags = $lead->getUtmTags();
                if (count($utmTags)) {
                    /** @var UtmTag $utmTag */
                    $utmTag    = array_pop($utmTags);
                    $userAgent = $utmTag->getUserAgent();
                } else {
                    $settings  = $this->getIntegrationSettings()->getFeatureSettings();
                    $userAgent = $settings['default_user_agent'];
                }

                $result = $this->getModel()->getResult($ipAddress, $userAgent);

                $lead->addUpdatedField('anura_result', $result);
                $didEnhnacement = true;
            }
        }

        return $didEnhnacement;
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Model;

use Doctrine\ORM\EntityRepository;
use GuzzleHttp\Client;
use Mautic\CoreBundle\Model\AbstractCommonModel;
use MauticPlugin\MauticEnhancerBundle\Entity\PluginEnhancerBlacklist;
use MauticPlugin\MauticEnhancerBundle\Entity\PluginEnhancerBlacklistRepository;
use MauticPlugin\MauticEnhancerBundle\Integration\BlacklistIntegration;

class BlacklistModel extends AbstractCommonModel
{
    /** @var string The URL for Blacklist GET requests */
    protected $endpoint;

    /** @var string The API key provided for Blacklist */
    protected $key;

    /**
     * @param BlacklistIntegration $integration
     */
    public function setup(BlacklistIntegration $integration)
    {
        $settings       = $integration->getIntegrationSettings()->getFeatureSettings();
        $keys           = $integration->getKeys();
        $this->endpoint = $settings['endpoint'];
        $this->key      = $keys['key'];
    }

    /**
     * Gets from local cache and/or calls the Blacklist serivice.
     * See documentation at http://developer.theblacklist.click.
     *
     * @param string $phone
     * @param int    $ageMinutes
     * @param bool   $cacheOnly  only pull cache records
     *
     * @return string
     * @return PluginEnhancerBlacklist
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function getRecord($phone, $ageMinutes, $cacheOnly = false)
    {
        /** @var PluginEnhancerBlacklist $record */
        $record = $this->getRepository()->findByPhone($phone);

        if (null === $record || $record->getDateAdded()->getTimestamp() > (time() - ($ageMinutes * 60))) {
            // Do not make the API request if cacheOnly.
            if ($cacheOnly) {
                return false;
            }

            // Blacklist service does not support E164 standard.
            $uri = rtrim($this->endpoint, '/').
                '/Lookup/key/'.trim($this->key).'/response/json/phone/'.
                ltrim($phone, '+');
            $httpClient = new Client();
            $response   = $httpClient->request('GET', $uri, ['timeout' => 3, 'connect_timeout' => 2]);
            $result     = json_decode($response->getBody()->getContents(), true);

            if (null === $record) {
                $record = new PluginEnhancerBlacklist();
                $record->setPhone($phone);
            }
            if ($result['sid']) {
                $record->setSid($result['sid']);
            }
            if ($result['code']) {
                $record->setCode($result['code']);
            }
            if ($result['results']) {
                $record->setResult($result['results']);
            }
            if ($result['wireless']) {
                $record->setWireless($result['wireless']);
            }
            $record->setDateAdded(new \DateTime());
            $this->getRepository()->saveEntity($record);
        }

        return $record;
    }

    /**
     * @return PluginEnhancerBlacklistRepository|EntityRepository
     */
    public function getRepository()
    {
        return $this->em->getRepository($this->getEntityName());
    }

    /**
     * @return string
     */
    public function getEntityName()
    {
        return PluginEnhancerBlacklist::class;
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\EventListener;

use Mautic\CoreBundle\EventListener\CommonSubscriber;
use Mautic\PluginBundle\Event\PluginIntegrationEvent;
use Mautic\PluginBundle\PluginEvents;
use MauticPlugin\MauticEnhancerBundle\Helper\EnhancerHelper;

/**
 * Class PluginSubscriber.
 */
class PluginSubscriber extends CommonSubscriber
{
    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return [
            PluginEvents::PLUGIN_ON_INTEGRATION_CONFIG_SAVE => ['buildEnhancerFields', 0],
        ];
    }

    /**
     * @param PluginIntegrationEvent $event
     *
     * @throws \Doctrine\DBAL\DBALException
     */
    public function buildEnhancerFields(PluginIntegrationEvent $event)
    {
        /** @var \MauticPlugin\MauticEnhancerBundle\Integration\AbstractEnhancerIntegration $integration */
        $integration = $event->getIntegration();
        if (in_array($integration->getName(), EnhancerHelper::IntegrationNames())) {
            $integration->buildEnhancerFields();
        }
    }
}


<?php
/**
 * Created by PhpStorm.
 * User: nbush
 * Date: 5/11/18
 * Time: 11:56 AM.
 */

namespace MauticPlugin\MauticEnhancerBundle\Model;

use Mautic\CoreBundle\Model\AbstractCommonModel;
use MauticPlugin\MauticEnhancerBundle\Entity\PluginEnhancerGenderName;

/**
 * Class GenderNameModel.
 */
class GenderNameModel extends AbstractCommonModel
{
    const REFERENCE_FILENAME = 'names.zip';

    const REFERENCE_LOCAL    = '/tmp/';

    const REFERENCE_REMOTE   = 'https://www.ssa.gov/OACT/babynames/';

    /**
     * @param $name
     *
     * @return null|string
     *
     * @throws \Doctrine\ORM\ORMException
     */
    public function getGender($name)
    {
        if ($record = $this->getRepository()->findOneBy(['name' => strtoupper($name)])) {
            return $record->getGender();
        } else {
            $gender = null;
            try {
                $url         = 'https://api.genderize.io/?name='.urlencode($name);
                $ch          = curl_init($url);
                $curlOptions = [
                    CURLOPT_RETURNTRANSFER => 1,
                    CURLOPT_CONNECTTIMEOUT => 1,
                    CURLOPT_TIMEOUT        => 30,
                ];
                curl_setopt_array($ch, $curlOptions);

                $result = curl_exec($ch);

                if (false !== $result) {
                    $result = json_decode($result, true);
                    if (isset($result['gender'])) {
                        $gender = 'female' === $result['gender'] ? 'F' : 'M';

                        //add it to our reference table
                        $genderName = new PluginEnhancerGenderName();
                        $genderName
                            ->setName(strtoupper($name))
                            ->setGender($gender)
                            ->setProbability($result['probability'])
                            ->setCount($result['count']);
                        $this->getRepository()->saveEntity($genderName, true);
                    }
                }
            } catch (\Exception $e) {
                $this->logger->error(
                    sprintf(
                        '%s (%s) : %s',
                        __FILE__,
                        __LINE__,
                        $e->getMessage()
                    )
                );
            }

            return $gender;
        }
    }

    /**
     * @return \Doctrine\ORM\EntityRepository|\MauticPlugin\MauticEnhancerBundle\Entity\PluginEnhancerCityStatePostalCodeRepository
     */
    public function getRepository()
    {
        return $this->em->getRepository($this->getEntityName());
    }

    /**
     * @return string
     */
    public function getEntityName()
    {
        return '\MauticPlugin\MauticEnhancerBundle\Entity\PluginEnhancerGenderName';
    }

    /**
     * @throws \Doctrine\DBAL\DBALException
     */
    public function verifyReferenceTable()
    {
        return $this->getRepository()->verifyReferenceTable();
    }

    /**
     * @return bool
     */
    public function updateReferenceTable()
    {
        try {
            $this->getRepository()->updateReferenceTable($this);

            return true;
        } catch (\Exception $e) {
            $this->logger->error($e->getMessage());

            return false;
        }
    }

    public function prepareGenderNameData()
    {
        $dataWorking = [
            'F' => [],
            'M' => [],
        ];

        if (($workingDir = $this->cleanWorkingDir()) &&
            $this->fetchNamesZip() &&
            $this->extractNamesZip($workingDir)
        ) {
            echo 'Processing files'.PHP_EOL;
            foreach (glob($workingDir.'*.txt') as $dataFile) {
                $fp = fopen($dataFile, 'r');
                while ($dataLine = fgetcsv($fp)) {
                    list($name, $gender, $individuals) = $dataLine;
                    $name                              = strtoupper($name);
                    $gender                            = strtoupper($gender);
                    if (!isset($dataWorking[$gender][$name])) {
                        $dataWorking[$gender][$name] = 0;
                    }
                    $dataWorking[$gender][$name] += $individuals;
                }
                fclose($fp);
                unlink($dataFile);
            }

            echo 'Preparing data'.PHP_EOL;
            $dataPrepped = [];
            $unisex      = array_intersect(
                array_keys($dataWorking['F']),
                array_keys($dataWorking['M'])
            );
            foreach ($dataWorking['F'] as $fName => $count) {
                if (in_array($fName, $unisex)) {
                    $total = $count + $dataWorking['M'][$fName];
                    if ($count > $dataWorking['M'][$fName]) {
                        $dataPrepped[] = [
                            'gender'      => 'F',
                            'name'        => $fName,
                            'probability' => ($count / $total),
                            'count'       => $total,
                        ];
                    } else {
                        $dataPrepped[] = [
                            'gender'      => 'M',
                            'name'        => $fName,
                            'probability' => ($dataWorking['M'][$fName] / $total),
                            'count'       => $total,
                        ];
                    }
                    unset($dataWorking['M'][$fName]);
                } else {
                    $dataPrepped[] = ['gender' => 'F', 'name' => $fName, 'probability' => 1.00, 'count' => $count];
                }
            }
            unset($dataWorking['F']);
            foreach ($dataWorking['M'] as $mName => $count) {
                $dataPrepped[] = ['gender' => 'M', 'name' => $mName, 'probability' => 1.00, 'count' => $count];
            }
            unset($dataWorking);

            return $dataPrepped;
        }
    }

    /**
     * @return bool|string
     */
    protected function cleanWorkingDir()
    {
        echo 'Cleaning working dir'.PHP_EOL;
        $workingDir = self::REFERENCE_LOCAL.'genderNames/';
        if (!(is_dir($workingDir) || mkdir($workingDir))) {
            $this->logger->error('Unable co create working dir at '.$workingDir);

            return false;
        } else {
            $files = array_diff(scandir($workingDir), ['.', '..']);
            foreach ($files as $file) {
                try {
                    unlink($workingDir.$file);
                    $this->logger->warning('Unexpected file '.$file.' found and removed from '.$workingDir);
                } catch (\Exception $e) {
                    $this->logger->error($e->getMessage());
                }
            }
            $files = array_diff(scandir($workingDir), ['.', '..']);
            if ($files) {
                $this->logger->error('Unable to remove '.implode(', ', $files).' from '.$workingDir);

                return false;
            }
        }

        return $workingDir;
    }

    /**
     * @return bool|resource
     */
    protected function fetchNamesZip()
    {
        echo 'Downloading data file'.PHP_EOL;
        try {
            file_put_contents(
                self::REFERENCE_LOCAL.self::REFERENCE_FILENAME,
                file_get_contents(self::REFERENCE_REMOTE.self::REFERENCE_FILENAME)
            );
            $this->logger->info(self::REFERENCE_FILENAME.' downloaded');

            return true;
        } catch (\Exception $e) {
            $this->logger->error('Unable to download data file: '.$e->getMessage());

            return false;
        }
    }

    /**
     * @param string $workingDir
     */
    protected function extractNamesZip($workingDir)
    {
        echo 'Extracting files'.PHP_EOL;
        try {
            $genderNames = new \ZipArchive();
            $opened      = $genderNames->open(self::REFERENCE_LOCAL.self::REFERENCE_FILENAME);
            if (true !== $opened) {
                $this->logger->warning("[$opened] Unable to open archive");
            } elseif (!$genderNames->extractTo($workingDir)) {
                $this->logger->warning('Unable to extract archive');
            } elseif (!$genderNames->close()) {
                $this->logger->warning('Unable to close archive');
            } else {
                $this->logger->info('Archive extracted to '.$workingDir);
                unset($genderNames);

                return true;
            }
        } catch (\Exception $e) {
            $this->logger->error($e->getMessage());
        }

        return false;
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use Mautic\LeadBundle\Entity\Lead;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;

/**
 * Class CorrectAddressIntegration.
 */
class CorrectAddressIntegration extends AbstractEnhancerIntegration
{
    const CA_CORRECTA_CMD  = 'cmd';

    const CA_CORRECTA_DATA = 'data_dir';

    const CA_REMOTE_FILE   = 'file';

    const CA_REMOTE_HOST   = 'host';

    const CA_REMOTE_PATH   = 'home';

    const CA_REMOTE_PSWD   = 'password';

    const CA_REMOTE_USER   = 'username';

    /** @var array */
    protected $attempts;

    /**
     * @return string
     */
    public function getName()
    {
        return 'CorrectAddress';
    }

    /**
     * @return string
     */
    public function getDisplayName()
    {
        return 'Expirian Correct Address';
    }

    /**
     * @return string
     */
    public function getAuthenticationType()
    {
        return 'sftp';
    }

    /**
     * @return array|mixed
     */
    protected function getEnhancerFieldArray()
    {
        return [
            'address_valid' => [
                'label' => 'Address Validated',
                'type'  => 'boolean',
            ],
        ];
    }

    /**
     * @return array
     */
    public function getRequiredKeyFields()
    {
        return [
            self::CA_REMOTE_USER => 'mautic.enhancer.integration.correctaddress.username',
            self::CA_REMOTE_PSWD => 'mautic.enhancer.integration.correctaddress.password',
        ];
    }

    /**
     * @return array
     */
    public function getSecretKeys()
    {
        return [self::CA_REMOTE_PSWD];
    }

    /**
     * @param \Symfony\Component\Form\FormBuilderInterface $builder
     * @param array                                        $data
     * @param string                                       $formArea
     */
    public function appendToForm(&$builder, $data, $formArea)
    {
        /** @var \Symfony\Component\Translation\TranslatorInterface $trans */
        $translator = $this->getTranslator();

        if ('features' === $formArea) {
            $builder
                ->add(
                    self::CA_REMOTE_HOST,
                    TextType::class,
                    [
                        'required' => true,
                        'label'    => $translator->trans('mautic.enhancer.integration.correctaddress.data_server'),
                        'data'     => isset($data[self::CA_REMOTE_HOST]) ? $data[self::CA_REMOTE_HOST] : '',
                        'attr'     => [
                            'class'   => 'form-control',
                            'tooltip' => $this->translator->trans(
                                'mautic.enhancer.integration.correctaddress.data_server.tooltip'
                            ),
                        ],
                    ]
                )
                ->add(
                    self::CA_REMOTE_PATH,
                    TextType::class,
                    [
                        'required' => true,
                        'label'    => $translator->trans('mautic.enhancer.integration.correctaddress.data_path'),
                        'data'     => isset($data[self::CA_REMOTE_PATH]) ? $data[self::CA_REMOTE_PATH] : '/CorrectAddress/USA',
                        'attr'     => [
                            'class'   => 'form-control',
                            'tooltip' => $this->translator->trans(
                                'mautic.enhancer.integration.correctaddress.data_path.tooltip'
                            ),
                        ],
                    ]
                )
                ->add(
                    self::CA_REMOTE_FILE,
                    TextType::class,
                    [
                        'required' => true,
                        'label'    => $translator->trans('mautic.enhancer.integration.correctaddress.data_file'),
                        'data'     => isset($data[self::CA_REMOTE_FILE]) ? $data[self::CA_REMOTE_FILE] : 'CorrectAddressData.zip',
                        'attr'     => [
                            'class'   => 'form-control',
                            'tooltip' => $this->translator->trans(
                                'mautic.enhancer.integration.correctaddress.data_file.tooltip'
                            ),
                        ],
                    ]
                )
                ->add(
                    self::CA_CORRECTA_CMD,
                    TextType::class,
                    [
                        'required' => true,
                        'label'    => $translator->trans('mautic.enhancer.integration.correctaddress.correcta_cmd'),
                        'data'     => isset($data[self::CA_CORRECTA_CMD]) ? $data[self::CA_CORRECTA_CMD] : '/IstCorrectAddress/CallCorrectA',
                        'attr'     => [
                            'class'   => 'form-control',
                            'tooltip' => $this->translator->trans(
                                'mautic.enhancer.integration.correctaddress.correcta_cmd.tooltip'
                            ),
                        ],
                    ]
                )
                ->add(
                    self::CA_CORRECTA_DATA,
                    TextType::class,
                    [
                        'required' => true,
                        'label'    => $translator->trans('mautic.enhancer.integration.correctaddress.correcta_data'),
                        'data'     => isset($data[self::CA_CORRECTA_DATA]) ? $data[self::CA_CORRECTA_DATA] : '/IstCorrectAddress/Data',
                        'attr'     => [
                            'class'   => 'form-control',
                            'tooltip' => $this->translator->trans(
                                'mautic.enhancer.integration.correctaddress.correcta_data.tooltip'
                            ),
                        ],
                    ]
                )
                ->add(
                    'autorun_enabled',
                    HiddenType::class,
                    [
                        'data' => true,
                    ]
                );
        }
    }

    /**
     * @param Lead $lead
     *
     * @return bool
     */
    public function doEnhancement(Lead $lead)
    {
        $result        = false;
        $leadAddress1  = $lead->getAddress1();
        $leadAddress2  = $lead->getAddress2();
        $leadZipCode   = $lead->getZipcode();
        $combined      = trim($leadAddress1).'|'.trim($leadAddress2).'|'.trim($leadZipCode);
        $leadCorrected = $lead->getFieldValue('address_valid');

        if (strlen($combined) < 6) {
            $this->getLogger()->debug(
                'Correct Address: Not enough address data to enhance contact '.$lead->getId()
            );
            // ENG-871 always update the field, even with false
            $lead->addUpdatedField('address_valid', $result, $leadCorrected);

            return $result;
        }

        if (isset($this->attempts[$combined])) {
            $this->getLogger()->debug('Correct Address: Already processed address on contact '.$lead->getId());

            return $result;
        }

        // ENG-871 only try if we havent already
        if (!is_null($leadCorrected)) {
            $this->getLogger()->debug('Correct Address: Already processed or validated contact '.$lead->getId());

            return $result;
        }

        $address = implode(
            '|',
            [
                $this->sanitizeAddressData($leadAddress1),
                $this->sanitizeAddressData($leadAddress2),
                $this->sanitizeAddressData($leadZipCode),
            ]
        );

        // Run the executable to correct the address.
        $this->attempts[$combined] = true;
        $corrected                 = $this->callCorrectA($address);
        if (!$corrected) {
            // Complete failure to parse address.
            $this->getLogger()->debug('Correct Address: Failure to parse address on contact '.$lead->getId());
            // ENG-871 always update the field, even with false
            $lead->addUpdatedField('address_valid', $result, $leadCorrected);

            return $result;
        }
        $this->getLogger()->debug('Correct Address: Raw value from Experian: "'.$corrected.'"');
        list($address1, $address2, $cityStZip, $code) = explode('|', $corrected);

        if (1 <= (int) $code) {
            $cityStZip = explode(' ', trim($cityStZip));
            $zipCode   = array_pop($cityStZip);
            $state     = array_pop($cityStZip);
            $city      = implode(' ', $cityStZip);
            $address1  = trim($address1);
            $address2  = trim($address2);
            $leadCity  = $lead->getCity();
            $leadState = $lead->getState();
            if (!empty($address1) && $address1 !== $leadAddress1) {
                $lead->setAddress1($address1);
                $lead->addUpdatedField('address1', $address1, $leadAddress1);
                $this->getLogger()->debug(
                    'Correct Address: Updated address1 to '.$address1.' for contact '.$lead->getId()
                );
                $result = true;
            }
            if (!empty($address2) && $address2 !== $leadAddress2) {
                $lead->setAddress2($address2);
                $lead->addUpdatedField('address2', $address2, $leadAddress2);
                $this->getLogger()->debug(
                    'Correct Address: Updated address2 to '.$address2.' for contact '.$lead->getId()
                );
                $result = true;
            }
            if (!empty($city) && $city !== $leadCity) {
                $lead->setCity($city);
                $lead->addUpdatedField('city', $city, $leadCity);
                $this->getLogger()->debug(
                    'Correct Address: Updated city to '.$city.' for contact '.$lead->getId()
                );
                $result = true;
            }
            if (!empty($state) && $state !== $leadState) {
                $lead->setState($state);
                $lead->addUpdatedField('state', $state, $leadState);
                $this->getLogger()->debug(
                    'Correct Address: Updated state to '.$state.' for contact '.$lead->getId()
                );
                $result = true;
            }
            if (!empty($zipCode) && $zipCode !== $leadZipCode) {
                $lead->setZipcode($zipCode);
                $lead->addUpdatedField('zipcode', $zipCode, $leadZipCode);
                $this->getLogger()->debug(
                    'Correct Address: Updated zipcode to '.$zipCode.' for contact '.$lead->getId()
                );
                $result = true;
            }
        } else {
            $this->getLogger()->debug('Correct Address: Could not discern accurate address, returned code '.$code);
        }
        // ENG-871 always update the field, even with false
        $lead->addUpdatedField('address_valid', $result, $leadCorrected);

        return $result;
    }

    /**
     * @param $addressData
     *
     * @return string
     */
    protected function sanitizeAddressData($addressData)
    {
        return str_pad(
            preg_replace(
                '/[^-A-Z0-9 ]/',
                '',
                strtoupper($addressData)
            ),
            64,
            ' ',
            STR_PAD_RIGHT
        );
    }

    /**
     * @param $addressData
     *
     * @return bool|string
     */
    protected function callCorrectA($addressData)
    {
        $return   = false;
        $settings = $this->getIntegrationSettings()->getFeatureSettings();

        if (!file_exists($settings[self::CA_CORRECTA_CMD])) {
            $this->getLogger()->error(
                'Correct Address: Could not find executable '.$settings[self::CA_CORRECTA_CMD]
            );
        } else {
            $pipes   = [];
            $process = proc_open(
                $settings[self::CA_CORRECTA_CMD],
                [
                    ['pipe', 'r'], // stdin
                    ['pipe', 'w'], // stdout
                    ['pipe', 'w'], // stderr
                ],
                $pipes,
                dirname($settings[self::CA_CORRECTA_CMD]),
                ['CA_DATA' => $settings[self::CA_CORRECTA_DATA]]
            );

            if (is_resource($process)) {
                // Send input to CallCorrectA and close its stdin
                fwrite($pipes[0], $addressData) && fclose($pipes[0]);

                // Log issues and cleanup
                if ($err = stream_get_contents($pipes[2])) {
                    $this->getLogger()->error(
                        'Correct Address: Error from executable '.$err
                    );
                } else {
                    $return = fgets($pipes[1]);
                }

                fclose($pipes[1]) && fclose($pipes[2]) && proc_close($process);
            } else {
                $this->getLogger()->error(
                    'Correct Address: Could not open executable '.$settings[self::CA_CORRECTA_CMD]
                );
            }
        }

        return $return;
    }
}


<?php
/**
 * Created by PhpStorm.
 * User: nbush
 * Date: 8/8/18
 * Time: 10:08 AM.
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use GuzzleHttp\Psr7\Response;
use Mautic\LeadBundle\Entity\Lead;

/**
 * Class NeustarMpicIntegration.
 */
class NeustarMpicIntegration extends AbstractNeustarIntegration
{
    /**
     * @return string
     */
    protected function getNeustarElementId()
    {
        return '3226';
    }

    /**
     * @return string
     */
    protected function getNeustarIntegrationName()
    {
        return 'Mpic';
    }

    /**
     * @return array
     */
    protected function getNeustarServiceKeys()
    {
        return ['875'];
    }

    /**
     * @return array
     */
    public function getEnhancerFieldArray()
    {
        return [
            'neustar_mpic_address_score'    => [
                'label' => 'Neustar Address Score',
                'type'  => 'number',
            ],
            'neustar_mpic_phone_score'      => [
                'label' => 'Neustar Phone Score',
                'type'  => 'number',
            ],
            'neustar_mpic_phone_validation' => [
                'label' => 'Neustar Phone Is Valid',
                'type'  => 'boolean',
            ],
            'neustar_mpic_phone_mobile'     => [
                'label' => 'Neustar Phone Is Mobile',
                'type'  => 'boolean',
            ],
            'neustar_mpic_phone_active'     => [
                'label' => 'Neustar Phone Is Active',
                'type'  => 'boolean',
            ],
            'neustar_mpic_email_score'      => [
                'label' => 'Neustar Email Score',
                'type'  => 'number',
            ],
        ];
    }

    /**
     * @param Lead   $lead
     * @param string $serviceId
     *
     * @return string
     */
    protected function getServiceIdData(Lead $lead, $serviceId = '875')
    {
        $xmlDoc                     = new \DOMDocument('1.0');
        $xmlDoc->preserveWhiteSpace = false;

        $root = $xmlDoc->createElement('Contact');

        $name = $xmlDoc->createElement('Name');
        $name->setAttribute('type', 'C');
        $name->appendChild($xmlDoc->createElement('First', $lead->getFirstname()));
        $name->appendChild($xmlDoc->createElement('Last', $lead->getLastname()));

        $names = $xmlDoc->createElement('Names');
        $names->appendChild($name);
        $root->appendChild($names);

        if ($lead->getAddress1()) {
            $address = $xmlDoc->createElement('Address');
            $address->setAttribute('score', '1');
            $address->appendChild($xmlDoc->createElement('Street', $lead->getAddress1()));
            $address->appendChild($xmlDoc->createElement('City', $lead->getCity()));
            $address->appendChild($xmlDoc->createElement('ST', $lead->getState()));
            $address->appendChild($xmlDoc->createElement('postal', $lead->getZipcode()));

            $addresses = $xmlDoc->createElement('Addresses');
            $addresses->appendChild($address);
            $root->appendChild($addresses);
        }

        $phone = preg_replace('/\D+/', '', $lead->getPhone());
        if (empty($phone)) {
            $phone = preg_replace('/\D+/', '', $lead->getMobile());
        }
        $phone = substr($phone, -10);
        if (10 === strlen($phone)) {
            $phone = $xmlDoc->createElement('Phone', $phone);
            $phone->setAttribute('score', '1');
            $phone->setAttribute('appends', 'validation,mobile,active');

            $phones = $xmlDoc->createElement('Phones');
            $phones->appendChild($phone);
            $root->appendChild($phones);
        }

        if ($lead->getEmail()) {
            $email = $xmlDoc->createElement('eMail', $lead->getEmail());
            $email->setAttribute('score', '1');

            $emails = $xmlDoc->createElement('eMailAddresses');
            $emails->appendChild($email);
            $root->appendChild($emails);
        }

        $xmlDoc->appendChild($root);

        return $xmlDoc->saveXML();
    }

    /**
     * @param Response $response
     */
    protected function processResponse(Lead $lead, Response $response)
    {
        try {
            $data        = trim($response->getBody()->getContents());
            $xdgResponse = new \SimpleXMLElement($data);

            if ('0' === ''.$xdgResponse->errorcode) {
                $result = $xdgResponse->response->result;
                if ('0' === ''.$result->errorcode) {
                    $contact = new \DOMDocument();
                    $contact->recover;
                    $contact->loadXML($result->value);
                    $contact = $this->domDocumentArray($contact);

                    foreach ($contact['Contact'] as $section => $result) {
                        switch ($section) {
                            case 'Addresses':
                                $field      = 'address';
                                $attributes = isset($result['Address']['@attributes'])
                                    ? $result['Address']['@attributes']
                                    : [];
                                break;
                            case 'Phones':
                                $field      = 'phone';
                                $attributes = isset($result['Phone']['@attributes'])
                                    ? $result['Phone']['@attributes']
                                    : [];
                                if (!empty($attributes)) {
                                    $attributes['active']     = (bool) $attributes['active'];
                                    $attributes['validation'] = (bool) $attributes['validation'];
                                    $attributes['mobile']     = ('Y' === $attributes['mobile']);
                                }
                                break;
                            case 'eMailAddresses':
                                $field      = 'email';
                                $attributes = isset($result['eMail']['@attributes'])
                                    ? $result['eMail']['@attributes']
                                    : [];
                                break;
                            default:
                                $field      = false;
                                $attributes = [];
                        }

                        if ($field) {
                            $fieldNameBase = strtolower(
                                sprintf(
                                    '%s_%s_%s_',
                                    self::NEUSTAR_PREFIX,
                                    $this->getNeustarIntegrationName(),
                                    $field
                                )
                            );
                            foreach ($attributes as $attribute => $value) {
                                $default = $lead->getFieldValue($fieldNameBase.$attribute);
                                $lead->addUpdatedField($fieldNameBase.$attribute, $value, $default);
                            }
                        }
                    }
                }
            }
        } catch (\Exception $e) {
            $this->handleEnchancerException('NeustarMpic', $e);
            $this->logger->error(sprintf('%s (%s): %s', __FILE__, __LINE__, $e->getMessage()));

            return false;
        }

        return true;
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Entity;

use Doctrine\DBAL\DBALException;
use Mautic\CoreBundle\Entity\CommonRepository;
use MauticPlugin\MauticEnhancerBundle\Model\CityStatePostalCodeModel;

class PluginEnhancerCityStatePostalCodeRepository extends CommonRepository
{
    /**
     * @return int
     *
     * @throws DBALException
     */
    public function verifyReferenceTable()
    {
        $this->createReferenceTable();
    }

    /**
     * @throws DBALException
     */
    public function createReferenceTable()
    {
        $table = MAUTIC_TABLE_PREFIX.PluginEnhancerCityStatePostalCode::TABLE_NAME;
        $sql   = <<<EOSQL
CREATE TABLE IF NOT EXISTS `{$table}` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `postal_code` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `city` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `state_province` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `country` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `county` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `latitude` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `longitude` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `country_postal_code` (`country`,`postal_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
EOSQL;

        $this->getEntityManager()->getConnection()->exec($sql);
    }

    /**
     * @param CityStatePostalCodeModel $model
     *
     * @throws DBALException
     * @throws \Doctrine\ORM\OptimisticLockException
     */
    public function updateReferenceTable(CityStatePostalCodeModel $model)
    {
        if (false !== ($fp = $model->fetchAllCountriesZip())) {
            $this->emptyReferenceTable();
            $this->createReferenceTable();
            $em        = $this->getEntityManager();
            $batchSize = 500;
            $count     = 0;
            while (!feof($fp)) {
                $data = explode("\t", trim(fgets($fp)));
                list(
                    $country,
                    $postalCode,
                    $city,
                    $stateProvince,
                    $state,
                    $county,
                    $a, $b, $c,
                    $latitude,
                    $longitude
                    ) = array_slice(
                    $data,
                    0,
                    11
                );
                if (!$country || !$postalCode) {
                    continue;
                }
                $record = new PluginEnhancerCityStatePostalCode();
                $record
                    ->setCountry($country)
                    ->setPostalCode($postalCode)
                    ->setCity($city)
                    ->setStateProvince($stateProvince)
                    ->setCounty($county)
                    ->setLatitude($latitude)
                    ->setLongitude($longitude);
                $em->persist($record);
                ++$count;
                if (0 === ($count % $batchSize)) {
                    $em->flush();
                    $em->clear();
                }
            }
            $em->flush();
            $em->clear();
        }
    }

    /**
     * @throws DBALException
     */
    public function emptyReferenceTable()
    {
        try {
            $table = MAUTIC_TABLE_PREFIX.PluginEnhancerCityStatePostalCode::TABLE_NAME;
            $sql   = 'DROP TABLE IF EXISTS `'.$table.'`';
            $this->getEntityManager()->getConnection()->exec($sql);
        } catch (DBALException $e) {
        }
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use Mautic\LeadBundle\Entity\Lead;

/**
 * Class GenderFromNameIntegration.
 */
class GenderFromNameIntegration extends AbstractEnhancerIntegration
{
    /** @var \MauticPlugin\MauticEnhancerBundle\Model\GenderNameModel */
    protected $integrationModel;

    public function getName()
    {
        return 'GenderFromName';
    }

    public function getDisplayName()
    {
        return 'Choose Gender From Name';
    }

    /**
     * @return array
     */
    protected function getEnhancerFieldArray()
    {
        try {
            $this->getIntegrationModel()->verifyReferenceTable();
        } catch (\Exception $e) {
            $this->logger->error($e->getMessage());
            $this->settings->setIsPublished(false);
            $this->session->getFlashBag()->add(
                'notice',
                $this->translator->trans('mautic.enhancer.integration.genderfromname.failure')
            );
        }

        return [];
    }

    protected function getIntegrationModel()
    {
        if (!isset($this->integrationModel)) {
            $this->integrationModel = $this->factory->getModel('enhancer.gendername');
        }

        return $this->integrationModel;
    }

    /**
     * @param Lead $lead
     *
     * @return bool
     */
    public function doEnhancement(Lead $lead)
    {
        $gender = $lead->getFieldValue('gender');
        if (!$gender or $this->isPush) {
            try {
                $oldGender = $gender;
                $gender    = $this->getIntegrationModel()->getGender($lead->getFirstname());
            } catch (\Exception $e) {
                return false;
            }

            if ($gender) {
                $lead->addUpdatedField('gender', $gender, $oldGender);

                return true;
            }
        }

        return false;
    }

    /**
     * @return string
     */
    public function getAuthenticationType()
    {
        return 'none';
    }

    /**
     * @param \Symfony\Component\Form\FormBuilder $builder
     * @param array                               $data
     * @param string                              $formArea
     */
    public function appendToForm(&$builder, $data, $formArea)
    {
        if ('features' === $formArea) {
            $builder->add(
                'autorun_enabled',
                \Symfony\Component\Form\Extension\Core\Type\HiddenType::class,
                [
                    'data' => true,
                ]
            );
        }
    }

    /**
     * @param $section
     *
     * @return mixed
     */
    public function getFormNotes($section)
    {
        if ('custom' === $section) {
            return $this->translator->trans('mautic.enhancer.integration.genderfromname.custom_note');
        }
    }
}