Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
+308
View File
@@ -0,0 +1,308 @@
<?php
namespace Xentral\Modules\Hubspot;
use ApplicationCore;
use Xentral\Components\Database\Database;
use Xentral\Core\DependencyInjection\ContainerInterface;
use Xentral\Modules\Country\Gateway\StateGateway;
use Xentral\Modules\Hubspot\RequestQueues\HubspotRequestQueuesGateway;
use Xentral\Modules\Hubspot\RequestQueues\HubspotRequestQueuesService;
use Xentral\Modules\Hubspot\Scheduler\HubspotProcessSchedulerTask;
use Xentral\Modules\Hubspot\Scheduler\HubspotPullContactsTask;
use Xentral\Modules\Hubspot\Scheduler\HubspotPullDealsTask;
use Xentral\Modules\Hubspot\Scheduler\HubspotPullEngagementsTask;
use Xentral\Modules\Hubspot\Validators\ContactValidator;
use Xentral\Modules\Hubspot\Validators\DealValidator;
use Xentral\Modules\SubscriptionCycle\Scheduler\TaskMutexService;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices()
{
return [
'HubspotContactService' => 'onInitHubspotContactService',
'HubspotDealService' => 'onInitHubspotDealService',
'HubspotClientService' => 'onInitHubspotClientService',
'HubspotHttpClientService' => 'onInitHubspotHttpClientService',
'HubspotConfigurationService' => 'onInitHubspotConfigurationService',
'HubspotPullContactsTask' => 'onInitHubspotPullContactsTask',
'HubspotContactGateway' => 'onInitHubspotContactGateway',
'HubspotDealGateway' => 'onInitHubspotDealGateway',
'HubspotContactPropertyService' => 'onInitHubspotContactPropertyService',
'HubspotContactPropertyGateway' => 'onInitHubspotContactPropertyGateway',
'HubspotPullDealsTask' => 'onInitHubspotPullDealsTask',
'HubspotDealPropertyService' => 'onInitHubspotDealPropertyService',
'HubspotProcessSchedulerTask' => 'onInitHubspotProcessSchedulerTask',
'HubspotRequestQueuesGateway' => 'onInitRequestQueuesGateway',
'HubspotRequestQueuesService' => 'onInitRequestQueuesService',
'HubspotEventService' => 'onInitHubspotEventService',
HubspotEngagementService::class => 'onInitHubspotEngagementService',
HubspotPullEngagementsTask::class => 'onInitHubspotPullEngagementsTask',
];
}
/**
* @param ContainerInterface $container
*
* @return HubspotContactService
*/
public static function onInitHubspotContactService(ContainerInterface $container)
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
return new HubspotContactService(
$container->get('HubspotClientService'),
new HubspotMetaService($app->erp->GetTMP()),
new ContactValidator(),
$container->get('HubspotConfigurationService')
);
}
/**
* @param ContainerInterface $container
*
* @return HubspotClientService
*/
public static function onInitHubspotClientService(ContainerInterface $container)
{
return new HubspotClientService(
$container->get('HubspotHttpClientService'),
$container->get('HubspotConfigurationService')
);
}
/**
* @return HubspotHttpClientService
*/
public static function onInitHubspotHttpClientService()
{
return new HubspotHttpClientService(30);
}
/**
* @param ContainerInterface $container
*
* @return HubspotConfigurationService
*/
public static function onInitHubspotConfigurationService(ContainerInterface $container)
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
return new HubspotConfigurationService(
$app->erp,
new HubspotMetaService($app->erp->GetTMP()),
$container->get('HubspotContactPropertyGateway'),
$container->get('HubspotDealGateway'),
$container->get('CountryGateway'),
$container->get(StateGateway::class)
);
}
/**
* @param ContainerInterface $container
*
* @return HubspotContactGateway
*/
public static function onInitHubspotContactGateway(ContainerInterface $container)
{
return new HubspotContactGateway($container->get('Database'), $container->get('HubspotConfigurationService'));
}
/**
* @param ContainerInterface $container
*
* @return HubspotPullContactsTask
*/
public static function onInitHubspotPullContactsTask(ContainerInterface $container)
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
return new HubspotPullContactsTask(
$container->get('HubspotContactService'),
$container->get('Database'),
new HubspotMetaService($app->erp->GetTMP()),
$container->get('HubspotContactGateway'),
$container->get('HubspotConfigurationService'),
$container->get('HubspotEventService'),
$container->get('CountryGateway'),
new TaskMutexService($container->get('Database'))
);
}
/**
* @param ContainerInterface $container
*
* @return HubspotDealGateway
*/
public static function onInitHubspotDealGateway(ContainerInterface $container)
{
return new HubspotDealGateway($container->get('Database'));
}
/**
* @param ContainerInterface $container
*
* @return HubspotDealService
*/
public static function onInitHubspotDealService(ContainerInterface $container)
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
return new HubspotDealService(
$container->get('HubspotClientService'),
new HubspotMetaService($app->erp->GetTMP()),
new DealValidator()
);
}
/**
* @param ContainerInterface $container
*
* @return HubspotContactPropertyService
*/
public static function onInitHubspotContactPropertyService(ContainerInterface $container)
{
return new HubspotContactPropertyService(
$container->get('HubspotClientService'),
$container->get('HubspotContactPropertyGateway'),
$container->get('Database')
);
}
/**
* @param ContainerInterface $container
*
* @return HubspotContactPropertyGateway
*/
public static function onInitHubspotContactPropertyGateway(ContainerInterface $container)
{
return new HubspotContactPropertyGateway($container->get('Database'));
}
/**
* @param ContainerInterface $container
*
* @return HubspotPullDealsTask
*/
public static function onInitHubspotPullDealsTask(ContainerInterface $container)
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
return new HubspotPullDealsTask(
$container->get('HubspotDealService'),
$container->get('Database'),
new HubspotMetaService($app->erp->GetTMP()),
$container->get('HubspotDealGateway'),
$container->get('HubspotConfigurationService'),
$container->get('HubspotEventService'),
$container->get('HubspotContactGateway'),
new TaskMutexService($container->get('Database'))
);
}
/**
* @param ContainerInterface $container
*
* @return HubspotDealPropertyService
*/
public function onInitHubspotDealPropertyService(ContainerInterface $container)
{
return new HubspotDealPropertyService(
$container->get('HubspotClientService'),
$container->get('Database'),
$container->get('HubspotContactPropertyGateway')
);
}
/**
* @param ContainerInterface $container
*
* @return HubspotProcessSchedulerTask
*/
public static function onInitHubspotProcessSchedulerTask(ContainerInterface $container)
{
return new HubspotProcessSchedulerTask(
$container->get('HubspotRequestQueuesService'),
new TaskMutexService($container->get('Database'))
);
}
/**
* @param ContainerInterface $container
*
* @return HubspotRequestQueuesGateway
*/
public static function onInitRequestQueuesGateway(ContainerInterface $container)
{
return new HubspotRequestQueuesGateway($container->get('Database'));
}
/**
* @param ContainerInterface $container
*
* @return HubspotRequestQueuesService
*/
public static function onInitRequestQueuesService(ContainerInterface $container)
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
return new HubspotRequestQueuesService(
$container->get('HubspotRequestQueuesGateway'),
$container->get('Database'),
$app,
$container->get('HubspotEventService')
);
}
/**
* @param ContainerInterface $container
*
* @return HubspotEventService
*/
public static function onInitHubspotEventService(ContainerInterface $container): HubspotEventService
{
return new HubspotEventService($container->get('Database'));
}
/**
* @param ContainerInterface $container
*
* @return HubspotEngagementService
*/
public static function onInitHubspotEngagementService(ContainerInterface $container): HubspotEngagementService
{
return new HubspotEngagementService($container->get('HubspotClientService'));
}
/**
* @param ContainerInterface $container
*
* @return HubspotPullEngagementsTask
*/
public static function onInitHubspotPullEngagementsTask(ContainerInterface $container): HubspotPullEngagementsTask
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
return new HubspotPullEngagementsTask(
$container->get('Database'),
$container->get(HubspotEngagementService::class),
new HubspotMetaService($app->erp->GetTMP()),
$container->get('HubspotConfigurationService'),
$container->get('HubspotEventService'),
$container->get('HubspotContactGateway'),
new TaskMutexService($container->get('Database'))
);
}
}
@@ -0,0 +1,8 @@
<?php
namespace Xentral\Modules\Hubspot\Exception;
final class HttpClientException extends HubspotException
{
}
@@ -0,0 +1,8 @@
<?php
namespace Xentral\Modules\Hubspot\Exception;
final class HubspotConfigurationServiceException extends HubspotException
{
}
@@ -0,0 +1,8 @@
<?php
namespace Xentral\Modules\Hubspot\Exception;
class HubspotContactGatewayNotFoundException extends HubspotException
{
}
@@ -0,0 +1,8 @@
<?php
namespace Xentral\Modules\Hubspot\Exception;
final class HubspotDealGatewayNotFoundException extends HubspotException
{
}
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Hubspot\Exception;
use RuntimeException as SplRuntimeException;
final class HubspotEngagementException extends SplRuntimeException
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Modules\Hubspot\Exception;
use RuntimeException as SplRuntimeException;
class HubspotException extends SplRuntimeException implements HubspotExceptionInterface
{
}
@@ -0,0 +1,10 @@
<?php
namespace Xentral\Modules\Hubspot\Exception;
use Xentral\Core\Exception\ModuleExceptionInterface;
interface HubspotExceptionInterface extends ModuleExceptionInterface
{
}
@@ -0,0 +1,8 @@
<?php
namespace Xentral\Modules\Hubspot\Exception;
class MetaException extends HubspotException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Xentral\Modules\Hubspot\Exception;
use BadMethodCallException as SplBadMethodCallException;
class SchedulerAdapterBadMethodException extends SplBadMethodCallException implements HubspotExceptionInterface
{
}
@@ -0,0 +1,228 @@
<?php
namespace Xentral\Modules\Hubspot;
use Xentral\Modules\Hubspot\Exception\HttpClientException;
use Xentral\Modules\Hubspot\Exception\HubspotException;
final class HubspotClientService
{
private $resource;
private $endPoints = [
'allContacts' => '/contacts/v1/lists/all/contacts/all',
'recentlyAddedContacts' => '/contacts/v1/lists/all/contacts/recent',
'recentlyUpdatedContacts' => '/contacts/v1/lists/recently_updated/contacts/recent',
'deleteContact' => '/contacts/v1/contact/vid/:contact_id',
'updateContact' => '/contacts/v1/contact/vid/:vid/profile',
'createContact' => '/contacts/v1/contact',
'getContactById' => '/contacts/v1/contact/vid/:vid/profile',
'createOrUpdateContact' => '/contacts/v1/contact/createOrUpdate/email/:contact_email',
'createDeal' => '/deals/v1/deal/',
'recentlyUpdatedDeals' => '/deals/v1/deal/recent/modified',
'recentlyAddedDeals' => '/deals/v1/deal/recent/created',
'allDeals' => '/deals/v1/deal/paged',
'deleteDeal' => '/deals/v1/deal/:dealId',
'updateDeal' => '/deals/v1/deal/:dealId',
'getAllContactProperties' => '/properties/v1/contacts/properties',
'getContactProperty' => '/properties/v1/contacts/properties/named/:property_name',
'getCompanyProperty' => '/properties/v1/companies/properties/named/:property_name',
'getPipelineProperties' => '/crm-pipelines/v1/pipelines/:object_type',
'getDealById' => '/deals/v1/deal/:dealId',
'getCompanyById' => '/companies/v2/companies/:companyId',
'getRecentCompanies' => '/companies/v2/companies/recent/modified',
'getCompanies' => '/companies/v2/companies/paged?properties=name&properties=website&properties=country&properties=zip&properties=address&properties=hs_lead_status&properties=city&properties=phone',
'getCompanyContacts' => '/companies/v2/companies/:companyId/contacts',
'addContactToCompany' => '/crm-associations/v1/associations',
'deleteContactFromCompany' => '/crm-associations/v1/associations/delete',
'createCompany' => '/companies/v2/companies',
'updateCompany' => '/companies/v2/companies/:companyId',
'createEngagement' => '/engagements/v1/engagements',
'updateEngagement' => '/engagements/v1/engagements/:engagementId',
'getRecentEngagements' => '/engagements/v1/engagements/recent/modified',
'getHubspotOwner' => '/owners/v2/owners/:ownerId',
];
private $authMethod = 'key';
/** @var string|null $apiKey */
private $apiKey;
/** @var HubspotHttpClientService $client */
private $client;
/**
* @var HubspotConfigurationService
*/
private $confService;
public function __construct(HubspotHttpClientService $client, HubspotConfigurationService $confService, $apiKey = null)
{
$this->client = $client;
$this->apiKey = $apiKey;
$this->confService = $confService;
}
/** @var string $apiUrl */
private $apiUrl = 'https://api.hubapi.com%s';
/**
* @param null $suffix
* @param array $args
*
* @throws HubspotException
*
* @return string
*/
public function getEndPoint($suffix = null, $args = [])
{
$resource = null;
if (null === $suffix && !($resource = $this->getResource())) {
throw new HubspotException('Endpoint suffix cannot be set');
}
if ($resource !== null && !array_key_exists($resource, $this->endPoints)) {
throw new HubspotException('Undefined resource endpoint');
}
$suffixUrl = null === $suffix ? $this->endPoints[$resource] : $suffix;
$url = sprintf($this->apiUrl, $suffixUrl);
if ($this->authMethod === 'key') {
$apiKey = null === $this->apiKey ? $this->getConfApiKey() : $this->apiKey;
$url .= strpos($url, '?') === false? '?hapikey=' . $apiKey : '&hapikey=' . $apiKey;
}
if (!empty($args)) {
preg_match_all('/:[a-zA-Z0-9._-]+/', $url, $match);
if (!empty($match) && !empty($match[0])) {
$url = str_replace($match[0], $args, $url);
}
}
return $url;
}
/**
* @param $resource
*
* @return HubspotClientService
*/
public function setResource($resource)
{
$this->resource = $resource;
return $this;
}
/**
* @return mixed
*/
public function getResource()
{
return $this->resource;
}
/**
* @param array $data
*
* @param array $endPointArgs
*
* @return HubspotHttpResponseService
*/
public function read($data = [], $endPointArgs = [])
{
return $this->client->get($this->getEndPoint(null, $endPointArgs), $data);
}
/**
* @return false|string|null
*/
private function getConfApiKey()
{
return $this->confService->getDecryptedConfiguration(HubspotConfigurationService::HUBSPOT_SALT_CONF_NAME);
}
/**
* @param array $data
*
* @param array $endPointArgs
*
* @return HubspotHttpResponseService
*/
public function post($data = [], $endPointArgs = [])
{
return $this->client->post($this->getEndPoint(null, $endPointArgs), $data);
}
/**
* @param array $data
*
* @param array $endPointArgs
*
* @return HubspotHttpResponseService
*/
public function delete($data = [], $endPointArgs = [])
{
return $this->client->delete($this->getEndPoint(null, $endPointArgs), $data);
}
/**
* @param array $data
*
* @param array $endPointArgs
*
* @return HubspotHttpResponseService
*/
public function put($data = [], $endPointArgs = [])
{
return $this->client->put($this->getEndPoint(null, $endPointArgs), $data);
}
/**
* @param array $data
* @param array $endPointArgs
*
* @throws HubspotException
*
* @return HubspotHttpResponseService
*/
public function patch($data = [], $endPointArgs = []): HubspotHttpResponseService
{
return $this->client->patch($this->getEndPoint(null, $endPointArgs), $data);
}
/**
* @param array $data
* @param array $endPointArgs
*
* @return HubspotHttpResponseService
*/
public function get($data = [], $endPointArgs = []): HubspotHttpResponseService
{
return $this->read($data, $endPointArgs);
}
/**
* @param string $resource
* @param string $type
* @param array $data
* @param array $endPointArgs
*
* @throws HubspotException
*
* @return HubspotHttpResponseService
*/
public function apiCall(
string $resource,
string $type,
array $data = [],
array $endPointArgs = []
) : HubspotHttpResponseService
{
if (!method_exists($this, $type)) {
throw new HttpClientException(sprintf('Methode ::%s is not yet implemented !', $type));
}
$this->resource = $resource;
return $this->client->{strtolower($type)}($this->getEndPoint(null, $endPointArgs), $data);
}
}
@@ -0,0 +1,623 @@
<?php
namespace Xentral\Modules\Hubspot;
use DateInterval;
use DateTime;
use erpAPI;
use Exception;
use Xentral\Modules\Country\Gateway\CountryGateway;
use Xentral\Modules\Country\Gateway\StateGateway;
use Xentral\Modules\Hubspot\Exception\HubspotConfigurationServiceException;
use Xentral\Modules\Hubspot\Exception\HubspotException;
class HubspotConfigurationService
{
public const HUBSPOT_SALT_CONF_NAME = 'hubspot_configuration_salt';
public const HUBSPOT_SETTING_CONF_NAME = 'hubspot_settings';
private static $_defaultSettings = [
'hs_sync_deals' => true,
'hs_sync_addresses' => true,
];
/** @var erpAPI $erp */
private $erp;
/**
* @var HubspotMetaService
*/
private $meta;
/**
* @var HubspotContactPropertyGateway
*/
private $propertyGateway;
/** @var HubspotDealGateway $hubspotDealGateway */
private $hubspotDealGateway;
/** @var CountryGateway $countryGateway */
private $countryGateway;
/** @var StateGateway $stateGateway */
private $stateGateway;
/**
* @param erpAPI $erp
* @param HubspotMetaService $meta
* @param HubspotContactPropertyGateway $propertyGateway
* @param HubspotDealGateway $hubspotDealGateway
* @param CountryGateway $countryGateway
* @param StateGateway $stateGateway
*/
public function __construct(
erpAPI $erp,
HubspotMetaService $meta,
HubspotContactPropertyGateway $propertyGateway,
HubspotDealGateway $hubspotDealGateway,
CountryGateway $countryGateway,
StateGateway $stateGateway
) {
$this->erp = $erp;
$this->meta = $meta;
$this->meta->setName('conf');
$this->propertyGateway = $propertyGateway;
$this->hubspotDealGateway = $hubspotDealGateway;
$this->countryGateway = $countryGateway;
$this->stateGateway = $stateGateway;
}
/**
* @param string $name
* @param string $value
*
* @return void
*/
public function trySetConfiguration($name, $value)
{
if (empty($name) || !is_string($value)) {
throw new HubspotConfigurationServiceException('Cannot set Configuration');
}
$this->erp->SetKonfigurationValue($name, $value);
}
/**
* @param $name
*
* @return array|mixed|string|null
*/
public function tryGetConfiguration($name)
{
if (empty($name)) {
throw new HubspotConfigurationServiceException('Cannot Get Configuration');
}
return $this->erp->GetKonfiguration($name);
}
/**
* @param string $name
* @param string $value
*/
public function setEncryptedConfiguration($name, $value)
{
if (empty($name) || !is_string($value)) {
throw new HubspotConfigurationServiceException('Cannot set Configuration');
}
$encValue = $this->encrypt($value);
$this->trySetConfiguration($name, $encValue);
}
/**
* @param string $name
*
* @return false|string|null
*/
public function getDecryptedConfiguration($name)
{
if (empty($name)) {
throw new HubspotConfigurationServiceException('Cannot Get Configuration');
}
return $this->decrypt($this->tryGetConfiguration($name));
}
/**
* @param string $string
* @param string $sCipher
*
* @return string
*/
protected function encrypt($string, $sCipher = 'AES-256-CBC')
{
if (empty($string)) {
return '';
}
if (null === $this->getNonceSalt()) {
return $string;
}
$key = hash('sha256', $this->getNonceSalt());
$ivlen = openssl_cipher_iv_length($sCipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($string, $sCipher, $key, OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary = true);
return base64_encode($iv . $hmac . $ciphertext_raw);
}
/**
* @param string $string
* @param string $sCipher
*
* @return false|string|null
*/
protected function decrypt($string, $sCipher = 'AES-256-CBC')
{
if (empty($string) || !$this->isBase64Encoded($string)) {
return '';
}
if (null === $this->getNonceSalt()) {
return $this->isBase64Encoded($string) ? null : $string;
}
$enc = base64_decode($string);
$key = hash('sha256', $this->getNonceSalt());
$ivlen = openssl_cipher_iv_length($sCipher);
$iv = substr($enc, 0, $ivlen);
$hmac = substr($enc, $ivlen, $sha2len = 32);
$ciphertext_raw = substr($enc, $ivlen + $sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $sCipher, $key, OPENSSL_RAW_DATA, $iv);
$calcmac = hash_hmac('sha256', $ciphertext_raw, $key, true);
return hash_equals($hmac, $calcmac) ? $original_plaintext : '';
}
/**
* @param string $string
*
* @return bool
*/
private function isBase64Encoded($string)
{
return base64_encode(base64_decode($string)) === $string;
}
/**
* @return false|string|null
*/
private function generateSecureSalt()
{
return password_hash(uniqid(mt_rand(), true), PASSWORD_BCRYPT);
}
/**
* @param bool $force
*
* @return false|int
*/
public function createSalt($force = false)
{
if ($force === true) {
$this->meta->delete();
}
if ($this->meta->exists() && $this->meta->keyExists('nonce_salt')) {
return -1;
}
return $this->meta->save(['nonce_salt' => $this->generateSecureSalt()]);
}
/**
* @return string|null
*/
private function getNonceSalt()
{
$data = $this->meta->get();
return array_key_exists('nonce_salt', $data) ? $data['nonce_salt'] : null;
}
/**
* @param HubspotHttpResponseService $response
*
* @throws HubspotException
*
* @return array
*/
public function formatAddressByResponse(HubspotHttpResponseService $response): array
{
if ($response->getStatusCode() !== 200) {
throw new HubSpotException($response->getError());
}
$contact = $response->getJson();
$properties = $contact['properties'];
$hubspotContact = array_combine(array_keys($properties), array_column($properties, 'value'));
$hubspotOwnerId = (int)array_key_exists(
'hubspot_owner_id',
$hubspotContact
) ? $hubspotContact['hubspot_owner_id'] : 0;
$data = [
'lead' => 1,
'typ' => 'herr',
'sprache' => 'deutsch',
'name' => sprintf(
'%s %s',
empty($hubspotContact['firstname']) ? 'Hubspot - ' : $hubspotContact['firstname'],
empty($hubspotContact['lastname']) ? $hubspotContact['email'] : $hubspotContact['lastname']
),
'vorname' => empty($hubspotContact['firstname']) ? 'Hubspot - ' : $hubspotContact['firstname'],
'nachname' => empty($hubspotContact['lastname']) ? $hubspotContact['email'] : $hubspotContact['lastname'],
'ort' => empty($hubspotContact['city']) ? '' : $hubspotContact['city'],
'plz' => empty($hubspotContact['zip']) ? '' : $hubspotContact['zip'],
'telefon' => empty($hubspotContact['phone']) ? '' : $hubspotContact['phone'],
'email' => $hubspotContact['email'],
'kundenfreigabe' => 1,
'waehrung' => 'EUR',
'strasse' => empty($hubspotContact['address']) ? '' : $hubspotContact['address'],
'internetseite' => empty($hubspotContact['website']) ? '' : $hubspotContact['website'],
'hubspot_owner_id' => $hubspotOwnerId,
];
$country = empty($hubspotContact['country']) ? 'DE' : $hubspotContact['country'];
if (!empty($country)) {
$countryDb = $this->countryGateway->findByName($country);
if (!empty($countryDb)) {
$country = $countryDb['iso2_code'];
}
}
$data['land'] = $country;
$state = empty($hubspotContact['state']) ? '' : $hubspotContact['state'];
if (!empty($state) && strlen($country) === 2) {
$stateDb = $this->stateGateway->findByNameAndIso2CountryCode($state, $country);
if (!empty($stateDb)) {
$state = $stateDb['iso2_code'];
}
}
$data['bundesstaat'] = $state;
try {
$leadFields = $this->matchSelectedAddressFreeField();
$lrField = $leadFields['hubspot_lr_field'];
$lsField = $leadFields['hubspot_ls_field'];
$data[$lsField] = empty($hubspotContact['hs_lead_status']) ? '' : $hubspotContact['hs_lead_status'];
$data[$lrField] = empty($hubspotContact['lifecyclestage']) ? '' : $hubspotContact['lifecyclestage'];
} catch (HubspotException $exception) {
}
return $data;
}
/**
* @throws HubspotException
* @return array
*/
public function matchSelectedAddressFreeField()
{
$hFields = [];
$asAddressFreeFieldValues = $this->propertyGateway->getConfiguredFreeAddressFieldValues();
$hsConfFields = [
'hubspot_lr_field' => $this->tryGetConfiguration('hubspot_lr_field'),
'hubspot_ls_field' => $this->tryGetConfiguration('hubspot_ls_field'),
];
foreach ($asAddressFreeFieldValues as $fieldName) {
if (in_array('adresse' . $fieldName, $hsConfFields)) {
$hFields[array_search('adresse' . $fieldName, $hsConfFields)] = $fieldName;
}
}
if (empty($hFields)) {
throw new HubSpotException('Lead-Status/Lifecycle fields cannot be matched');
}
return $hFields;
}
/**
* @param $address
*
* @throws HubspotException
*
* @return array
*/
public function formatAddressToHubspotContact(array $address): array
{
if (empty($address)) {
throw new HubSpotException('Address is invalid');
}
$leadFields = $this->matchSelectedAddressFreeField();
$lrField = $leadFields['hubspot_lr_field'];
$lsField = $leadFields['hubspot_ls_field'];
$firstName = empty($address['vorname']) ? '' : $address['vorname'];
$lastName = empty($address['nachname']) ? '' : $address['nachname'];
if (empty($lastName) && (empty($firstName) || $firstName !== $address['name'])) {
$lastName = $address['name'];
}
$data = [
'email' => $address['email'],
'firstname' => $firstName,
'lastname' => $lastName,
'website' => $address['internetseite'],
'phone' => $address['telefon'],
'address' => $address['strasse'],
'city' => $address['ort'],
'state' => $address['bundesstaat'],
'zip' => $address['plz'],
'hs_lead_status' => $address[$lsField],
'lifecyclestage' => $address[$lrField],
];
$iso2CountryCode = $address['land'];
if (!empty($iso2CountryCode)) {
$countryDb = $this->countryGateway->findByIso2Code($iso2CountryCode);
if (!empty($countryDb)) {
$country = $countryDb['name_de'];
$data['country'] = $country;
}
}
$iso2State = $address['bundesstaat'];
if (!empty($iso2State) && strlen($iso2CountryCode) === 2) {
$stateDb = $this->stateGateway->findByIso2CodeAndIso2CountryCode($iso2State, $iso2CountryCode);
if (!empty($stateDb)) {
$iso2State = $stateDb['name_de'];
}
}
$data['state'] = $iso2State;
if (array_key_exists('typ', $address) && !empty($address['typ'])) {
$data['salutation'] = $address['typ'];
if ($address['typ'] === 'firma') {
if (array_key_exists('numberofemployees', $address) &&
!empty($address['numberofemployees'])) {
$data['numberofemployees'] = $address['numberofemployees'];
}
$settings = $this->getSettings();
$defaultCustomFields = array_key_exists('hubspot_address_free_fields', $settings) ?
$settings['hubspot_address_free_fields'] : [];
if (!empty($defaultCustomFields)) {
foreach ($defaultCustomFields as $property => $systemField) {
$data[$property] = $address[sprintf('xthubspot_%s', $property)];
}
}
}
}
return $data;
}
/**
* @param HubspotHttpResponseService $response
*
* @throws Exception
* @return array
*/
public function formatDealByResponse(HubspotHttpResponseService $response)
{
if ($response->getStatusCode() === 200) {
$deal = $response->getJson();
$properties = $deal['properties'];
$hDeal = array_combine(array_keys($properties), array_column($properties, 'value'));
$dealStage = $this->propertyGateway->getMappingByValueAndType($hDeal['dealstage'], 'deals');
return [
'bezeichnung' => $hDeal['dealname'],
'datum_angelegt' => date('Y-m-d', $hDeal['createdate'] / 1000),
'zeit_angelegt' => date('H:i:s', $hDeal['createdate'] / 1000),
'datum_erinnerung' => $this->getTimeByDays($hDeal['days_to_close'])->format('Y-m-d'),
'zeit_erinnerung' => $this->getTimeByDays($hDeal['days_to_close'])->format('H:i:s'),
'betrag' => array_key_exists('amount', $hDeal) ? (float)$hDeal['amount'] : 0.00,
'stages' => !empty($dealStage['wiedervorlage_stage_id']) ? $dealStage['wiedervorlage_stage_id'] : 0,
];
}
throw new HubSpotException($response->getError());
}
/**
* @param array $resubmission
*
* @throws Exception
* @return array
*/
public function formatResubmissionToHubspotDeal($resubmission)
{
if (is_array($resubmission) && !empty($resubmission)) {
$oCloseDate = null;
if (!empty($resubmission['datum_erinnerung']) && !empty($resubmission['zeit_erinnerung'])) {
$closeDate = $resubmission['datum_erinnerung'] . ' ' . $resubmission['zeit_erinnerung'];
$oCloseDate = new DateTime($closeDate);
}
$mapping = $this->hubspotDealGateway->getMappingStageByResubmissionStageId($resubmission['stages']);
return [
'dealname' => $resubmission['bezeichnung'],
'dealstage' => !empty($mapping) ? $mapping['value'] : null,
'amount' => empty($resubmission['betrag']) ? 0.00 : $resubmission['betrag'],
'pipeline' => 'default',
'closedate' => null !== $oCloseDate ? $oCloseDate->getTimestamp() * 1000 : 0,
];
}
throw new HubSpotException('Resubmission is invalid');
}
/**
* @param int $days
*
* @throws Exception
* @return DateTime
*/
private function getTimeByDays($days)
{
$date = new DateTime('now');
$interval = sprintf('P%dD', (int)$days);
$date->add(new DateInterval($interval));
return $date;
}
/**
* @param array $settings
*/
public function setSettings($settings = [])
{
$this->trySetConfiguration(
static::HUBSPOT_SETTING_CONF_NAME,
json_encode($settings, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT)
);
}
/**
* @throws HubspotConfigurationServiceException
* @return array
*/
public function getSettings()
{
$settingsRaw = $this->tryGetConfiguration(static::HUBSPOT_SETTING_CONF_NAME);
if (empty($settingsRaw)) {
return static::$_defaultSettings;
}
if (($settings = json_decode($settingsRaw, true)) !== null
&& (json_last_error() === JSON_ERROR_NONE)) {
if (empty($settings)) {
return static::$_defaultSettings;
}
return $settings;
}
throw new HubspotConfigurationServiceException(json_last_error_msg());
}
/**
* @param int $contactId
*
* @throws HubspotConfigurationServiceException
*
* @return void
*/
public function addContactToGroup(int $contactId = 0): void
{
$defaultSettings = $this->getSettings();
$contactGrpId = array_key_exists('hs_contact_grp', $defaultSettings) ? $defaultSettings['hs_contact_grp'] : 0;
if (!empty($contactGrpId)) {
$this->erp->AddRolleZuAdresse($contactId, 'Mitglied', 'von', 'Gruppe', $contactGrpId);
}
}
/**
* @param HubspotHttpResponseService $response
*
* @throws HubspotException
*
* @return array
*/
public function formatCompanyByResponse(HubspotHttpResponseService $response): array
{
if ($response->getStatusCode() !== 200) {
throw new HubSpotException($response->getError());
}
$contact = $response->getJson();
$properties = $contact['properties'];
$hubspotContact = array_combine(array_keys($properties), array_column($properties, 'value'));
$hubspotOwnerId = (int)array_key_exists(
'hubspot_owner_id',
$hubspotContact
) ? $hubspotContact['hubspot_owner_id'] : 0;
$data = [
'typ' => 'firma',
'sprache' => 'deutsch',
'name' => empty($hubspotContact['name']) ? 'Hubspot - Company' : $hubspotContact['name'],
'ort' => empty($hubspotContact['city']) ? '' : $hubspotContact['city'],
'plz' => empty($hubspotContact['zip']) ? '' : $hubspotContact['zip'],
'telefon' => empty($hubspotContact['phone']) ? '' : $hubspotContact['phone'],
'kundenfreigabe' => 1,
'waehrung' => 'EUR',
'strasse' => empty($hubspotContact['address']) ? '' : $hubspotContact['address'],
'internetseite' => empty($hubspotContact['website']) ? '' : $hubspotContact['website'],
'hubspot_owner_id' => $hubspotOwnerId,
];
$country = empty($hubspotContact['country']) ? 'DE' : $hubspotContact['country'];
if (!empty($country)) {
$countryDb = $this->countryGateway->findByName($country);
if (!empty($countryDb)) {
$country = $countryDb['iso2_code'];
}
}
$data['land'] = $country;
$state = empty($hubspotContact['state']) ? '' : $hubspotContact['state'];
if (!empty($state) && strlen($country) === 2) {
$stateDb = $this->stateGateway->findByNameAndIso2CountryCode($state, $country);
if (!empty($stateDb)) {
$state = $stateDb['iso2_code'];
}
}
$data['bundesstaat'] = $state;
try {
$leadFields = $this->matchSelectedAddressFreeField();
$lrField = $leadFields['hubspot_lr_field'];
$lsField = $leadFields['hubspot_ls_field'];
$data[$lsField] = empty($hubspotContact['hs_lead_status']) ? '' : $hubspotContact['hs_lead_status'];
$data[$lrField] = empty($hubspotContact['lifecyclestage']) ? '' : $hubspotContact['lifecyclestage'];
} catch (HubspotException $exception) {
}
$numberOfEmployeesField = $this->tryGetConfiguration('hubspot_numberofemployees_field');
if (!empty($numberOfEmployeesField)) {
$fieldName = str_replace('adresse', '', $numberOfEmployeesField);
$numberOfEmployees = empty($hubspotContact['numberofemployees']) ? 0 : $hubspotContact['numberofemployees'];
$data[$fieldName] = $numberOfEmployees;
}
$settings = $this->getSettings();
$defaultCustomFields = array_key_exists('hubspot_address_free_fields', $settings) ?
$settings['hubspot_address_free_fields'] : [];
if (!empty($defaultCustomFields)) {
foreach ($defaultCustomFields as $property => $systemField) {
$fieldName = str_replace('adresse', '', $systemField);
$data[$fieldName] = $hubspotContact[$property];
}
}
return $data;
}
/**
* @param string $customFreeField
* @param array $fieldConfig
*
* @return void
*/
public function setSystemFreeField(string $customFreeField, array $fieldConfig): void
{
$label = $fieldConfig['label'];
$type = $fieldConfig['fieldType'];
$value = $fieldConfig['options'];
$customFreeFieldValue = $label . '|' . implode('|', $value);
$this->erp->FirmendatenSet($customFreeField, $customFreeFieldValue);
$this->erp->FirmendatenSet($customFreeField . 'typ', $type);
$this->erp->FirmendatenSet($customFreeField . 'spalte', '1');
$this->erp->Firmendaten($customFreeField);
}
/**
* @param string $customFreeField
*
* @return void
*/
public function unsetSystemFreeField(string $customFreeField): void
{
$this->erp->FirmendatenSet($customFreeField, '');
$this->erp->FirmendatenSet($customFreeField . 'typ', '');
$this->erp->FirmendatenSet($customFreeField . 'spalte', '');
}
}
@@ -0,0 +1,223 @@
<?php
namespace Xentral\Modules\Hubspot;
use Xentral\Components\Database\Database;
final class HubspotContactGateway
{
/** @var Database $db */
private $db;
/**
* @var HubspotConfigurationService
*/
private $configurationService;
/**
* @param Database $db
* @param HubspotConfigurationService $configurationService
*/
public function __construct(Database $db, HubspotConfigurationService $configurationService)
{
$this->db = $db;
$this->configurationService = $configurationService;
}
/**
* @param int $hsContactId
* @param null|string $type
*
* @return array
*/
public function getMappingByHubspotId(int $hsContactId, ?string $type = 'address'): array
{
$sql = '
SELECT
h.id,
h.created_at,
h.data,
h.address_id,
h.type
FROM `hubspot_contacts` AS `h` WHERE h.hidden = 0 AND h.hs_contact_id = :id';
$where = ['id' => $hsContactId];
if ($type !== null) {
$sql .= ' AND h.type = :type';
$where['type'] = $type;
}
return $this->db->fetchRow($sql, $where);
}
/**
* @param int $addressId
* @param string $type
*
* @return array
*/
public function getMappingByAddressId(int $addressId, string $type = 'address'): array
{
return $this->db->fetchRow(
'SELECT
h.id,
h.created_at,
h.data,
h.hs_contact_id
FROM `hubspot_contacts` AS `h` WHERE h.hidden = 0 AND h.address_id = :id AND h.type = :type',
['id' => $addressId, 'type' => $type]
);
}
/**
* @param int $addressId
* @param bool $withStatusField
*
* @throws Exception\HubspotException
*
* @return array
*/
public function getAddressById(int $addressId, bool $withStatusField = false): array
{
$placeHolder = '';
if ($withStatusField === true) {
$leadFields = $this->configurationService->matchSelectedAddressFreeField();
$lrField = $leadFields['hubspot_lr_field'];
$lsField = $leadFields['hubspot_ls_field'];
$placeHolder = ",a.`{$lrField}`, a.`{$lsField}`";
}
$numberOfEmployeesField = $this->configurationService->tryGetConfiguration('hubspot_numberofemployees_field');
if (!empty($numberOfEmployeesField)) {
$fieldName = str_replace('adresse', '', $numberOfEmployeesField);
$placeHolder .= ",a.`{$fieldName}` AS numberofemployees";
}
$settings = $this->configurationService->getSettings();
$defaultCustomFields = array_key_exists('hubspot_address_free_fields', $settings) ?
$settings['hubspot_address_free_fields'] : [];
if (!empty($defaultCustomFields)) {
foreach ($defaultCustomFields as $defaultCustomField => $systemField) {
$fieldName = str_replace('adresse', '', $systemField);
$placeHolder .= ",a.`{$fieldName}` AS `xthubspot_{$defaultCustomField}`";
}
}
$sql = 'SELECT
a.id,
a.`lead`,
a.typ,
a.sprache,
a.name,
a.vorname,
a.nachname,
a.land,
a.ort,
a.plz,
a.bundesstaat,
a.telefon,
a.strasse,
a.vertrieb,
a.email %s FROM `adresse` AS `a` WHERE a.geloescht = 0 AND a.id = :id';
return $this->db->fetchRow(sprintf($sql, $placeHolder), ['id' => $addressId]);
}
/**
* @param int $hsContactId
* @param array $types
*
* @return bool
*/
public function hubspotContactExists(int $hsContactId, array $types = []): bool
{
$sql = '
SELECT
h.id
FROM `hubspot_contacts` AS `h` WHERE h.hidden = 0 AND h.hs_contact_id = :id';
$where = ['id' => $hsContactId];
if (!empty($types)) {
$sqlType = implode("','", $types);
$sql .= ' AND h.type IN(:type)';
$where['type'] = $sqlType;
}
return !empty($this->db->fetchValue($sql, $where));
}
/**
* @param int $contactPersonId
*
* @return array
*/
public function getContactPersonData(int $contactPersonId): array
{
$sql = 'SELECT
ap.id,
ap.adresse AS address_id,
ap.typ,
ap.sprache,
ap.name,
ap.vorname,
ap.bereich,
ap.land,
ap.ort,
ap.plz,
ap.strasse,
ap.telefon,
ap.email FROM `ansprechpartner` AS `ap` WHERE ap.geloescht = 0 AND ap.id = :id';
return $this->db->fetchRow($sql, ['id' => $contactPersonId]);
}
/**
* @param int $contactPersonId
*
* @return array
*/
public function getHubspotMappingByPersonId(int $contactPersonId): array
{
$sql = 'SELECT hc.hs_contact_id AS `company_id`,
(SELECT `hs_contact_id` FROM `hubspot_contacts` WHERE `address_id` = :cid) AS `contact_id`
FROM `ansprechpartner` AS `ap`
JOIN `hubspot_contacts` AS `hc` ON(ap.adresse = hc.address_id AND hc.type = :type)
WHERE ap.id = :cid';
return $this->db->fetchRow($sql, ['cid' => $contactPersonId, 'type' => 'company']);
}
/**
* @param int $noteId
*
* @return array
*/
public function getAddressInfoByNoteId(int $noteId): array
{
$sql = 'SELECT d.adresse_to AS `address_id`,
a.typ AS `type`,
a.name, d.betreff AS `object`,
d.content FROM `dokumente` AS `d`
JOIN `adresse` AS `a` ON(d.adresse_to = a.id)
WHERE d.`id` = :note_id AND d.typ = :type';
return $this->db->fetchRow($sql, ['note_id' => $noteId, 'type' => 'notiz']);
}
/**
* @param int $companyId
*
* @return bool
*/
public function hubspotSaleStaffExists(int $companyId): bool
{
$sql = '
SELECT
h.id
FROM `hubspot_contacts` AS `h` WHERE h.hidden = 0 AND h.data = :company AND h.type = :type';
$where = ['company' => (string)$companyId, 'type' => 'sale_staff'];
return !empty($this->db->fetchValue($sql, $where));
}
}
@@ -0,0 +1,91 @@
<?php
namespace Xentral\Modules\Hubspot;
use Xentral\Components\Database\Database;
final class HubspotContactPropertyGateway
{
/** @var Database $db */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param string $type
* @param bool $isSystem
* @param string $scope
*
* @return array
*/
public function getLeadsByType(string $type, bool $isSystem = false, string $scope = 'contact')
{
return $this->db->fetchAssoc(
'SELECT
h.id,
h.created_at,
h.label,
h.value,
h.type,
h.wiedervorlage_stage_id
FROM `hs_mapping_leads` AS `h` WHERE h.type = :type AND h.is_system = :system and h.setting_scope = :scope',
['type' => $type, 'system' => (int)$isSystem, 'scope' => $scope]
);
}
/**
* @param string $value
*
* @param string $type
*
* @return array
*/
public function getMappingByValueAndType($value, $type)
{
return $this->db->fetchRow(
'SELECT
hm.id,
hm.created_at,
hm.wiedervorlage_stage_id,
hm.label,
hm.value,
hm.type FROM hs_mapping_leads `hm` WHERE hm.value=:value AND hm.type=:type',
['value' => $value, 'type' => $type]
);
}
/**
* @param string $dbName
*
* @return array
*/
public function getAddressFreeFields($dbName)
{
return $this->db->fetchCol(
'
SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE TABLE_SCHEMA=:db AND TABLE_NAME=:table AND
COLUMN_NAME LIKE "adressefreifeld%"
',
[
'db' => $dbName,
'table' => 'firmendaten',
]
);
}
/**
* @return array
*/
public function getConfiguredFreeAddressFieldValues()
{
return $this->db->fetchCol(
'SELECT f.wert FROM firmendaten_werte `f` WHERE name LIKE "adressetabellezusatz%" AND f.wert !="" AND f.wert IS NOT NULL'
);
}
}
@@ -0,0 +1,192 @@
<?php
namespace Xentral\Modules\Hubspot;
use Xentral\Components\Database\Database;
use Xentral\Modules\Hubspot\Exception\HubspotException;
final class HubspotContactPropertyService
{
/** @var HubspotClientService $client */
private $client;
/** @var HubspotContactPropertyGateway $gateway */
private $gateway;
/** @var Database $db */
private $db;
/**
* @param HubspotClientService $client
* @param HubspotContactPropertyGateway $gateway
* @param Database $db
*/
public function __construct(HubspotClientService $client, HubspotContactPropertyGateway $gateway, Database $db)
{
$this->client = $client;
$this->gateway = $gateway;
$this->db = $db;
}
/**
* @return HubspotHttpResponseService
*/
public function getProperties()
{
return $this->client->setResource('getAllContactProperties')->read();
}
/**
* @param string $name
* @param string $type
*
* @throws HubspotException
*
* @return HubspotHttpResponseService
*/
public function getProperty(string $name, string $type = 'contact')
{
if (empty($name)) {
throw new HubspotException('Property name is missing');
}
$resource = 'getContactProperty';
if ($type === 'company') {
$resource = 'getCompanyProperty';
}
return $this->client->setResource($resource)->read([], [$name]);
}
/**
* @param string $type
* @param bool $withLabel
*
* @throws HubspotException
*
* @return array
*/
public function getHsLeadStatus(string $type = 'contact', bool $withLabel = true)
{
$response = $this->getProperty('hs_lead_status', $type);
if ($response->getStatusCode() !== 200) {
throw new HubspotException($response->getError());
}
if (($data = $response->getJson()) && array_key_exists('options', $data)) {
if ($withLabel === false) {
return array_column($data['options'], 'value');
}
$response = [];
$options = $data['options'];
foreach ($options as $option) {
$response[$option['value']] = $option['label'];
}
return $response;
}
return [];
}
public function getHsLeadRating()
{
$response = $this->getProperty('lifecyclestage');
if ($response->getStatusCode() !== 200) {
throw new HubspotException($response->getError());
}
if (($data = $response->getJson()) && array_key_exists('options', $data)) {
return array_column($data['options'], 'value');
}
return [];
}
/**
* @param string $scope
*
* @throws HubspotException
*
* @return array
*/
public function getUpdatedLeadStatuses(string $scope): array
{
$customFreeFieldValue = [];
$leadStatus = $this->gateway->getLeadsByType('status', false, $scope);
$dbStatuses = array_column($leadStatus, 'value');
$remoteStatusContact = $this->getHsLeadStatus($scope);
$remoteStatusContactKey = array_keys($remoteStatusContact);
$statusContactGone = array_diff($dbStatuses, $remoteStatusContactKey);
if (!empty($statusContactGone)) {
foreach ($statusContactGone as $valueGone) {
$delGone = "DELETE FROM hs_mapping_leads
WHERE value = :value AND type = 'status' AND setting_scope = 'contact'";
$this->db->perform($delGone, ['value' => $valueGone]);
}
}
$statusContactNew = array_diff($remoteStatusContactKey, $dbStatuses);
if (!empty($statusContactNew)) {
foreach ($remoteStatusContact as $status => $label) {
if (!in_array($status, $statusContactNew, true)) {
continue;
}
$customFreeFieldValue[] = sprintf('%s=>%s', $label, $status);
$newStatus = 'INSERT INTO hs_mapping_leads (label, value, type, setting_scope)
VALUES(:label, :value, "status", :scope)';
$this->db->perform(
$newStatus,
[
'label' => $label,
'value' => $status,
'scope' => $scope,
]
);
}
}
return $customFreeFieldValue;
}
/**
* @param string $propertyName
* @param string|null $type
*
* @return array|null
*/
public function getCustomPropertyByName(string $propertyName, ?string $type = null): ?array
{
try {
$type = $type ?? 'company';
$response = $this->getProperty(strtolower($propertyName), $type);
} catch (HubspotException $e) {
// Do nothing
}
if (!isset($response)) {
try {
$response = $this->getProperty(strtolower($propertyName));
} catch (HubspotException $e) {
return null;
}
}
if ($response->getStatusCode() !== 200) {
return null;
}
$data = $response->getJson();
$options = [];
$fieldType = $data['fieldType'];
$fieldLabel = $data['label'];
$name = $data['name'];
if (array_key_exists('options', $data)) {
$options = !empty($data['options'])? array_column($data['options'], 'value') : [];
}
return ['fieldName' => $name, 'fieldType' => $fieldType, 'label' => $fieldLabel, 'options' => $options];
}
}
@@ -0,0 +1,381 @@
<?php
namespace Xentral\Modules\Hubspot;
use JsonException;
use Xentral\Modules\Hubspot\HubspotHttpResponseService as Response;
use Xentral\Modules\Hubspot\Exception\HubspotException;
use Xentral\Modules\Hubspot\Validators\ContactValidator;
final class HubspotContactService
{
/** @var int[] $itemsCount */
private $itemsCount = ['count' => 100];
/** @var string[] $allowedSyncContactOptions */
private $allowedSyncContactOptions = [
'recently_created' => 'getRecentlyCreatedContacts',
'recently_updated' => 'getRecentlyUpdatedContacts',
'all' => 'getContacts',
'companies' => 'getCompanies',
'recent_companies' => 'getRecentCompanies',
];
/** @var HubspotClientService $client */
private $client;
/** @var HubspotMetaService $meta */
private $meta;
/** @var ContactValidator $validator */
private $validator;
/** @var HubspotConfigurationService $configurationService */
private $configurationService;
/**
* @param HubspotClientService $client
* @param HubspotMetaService $meta
* @param ContactValidator $validator
* @param HubspotConfigurationService $configurationService
*/
public function __construct(
HubspotClientService $client,
HubspotMetaService $meta,
ContactValidator $validator,
HubspotConfigurationService $configurationService
) {
$this->client = $client;
$this->meta = $meta;
$this->validator = $validator;
$this->configurationService = $configurationService;
}
/**
* @param array $options
*
* @return Response
*/
public function getContacts($options = []): Response
{
return $this->client->setResource('allContacts')->read($options);
}
/**
* @param array $options
*
* @return Response
*/
public function getRecentlyUpdatedContacts($options = []): Response
{
$options += $this->itemsCount;
return $this->client->setResource('recentlyUpdatedContacts')->read($options);
}
/**
* @param int $contactId
*
* @return Response
*/
public function deleteContact($contactId = 0): Response
{
return $this->client->setResource('deleteContact')->delete([], [$contactId]);
}
/**
* @param array $data
*
* @throws HubspotException
*
* @return HubspotHttpResponseService
*/
public function createContact($data = []): Response
{
if (!$this->validator->isValid($data)) {
throw new HubSpotException(sprintf('Invalid contact data'));
}
$contactData = $this->validator->getData();
if (array_key_exists('salutation', $data) && $data['salutation'] === 'firma') {
$contactData['name'] = $data['lastname'];
$identity = $this->formatCompanyIdentity($contactData);
return $this->client->setResource('createCompany')->post($identity);
}
$identity = $this->formatContactIdentity($contactData);
return $this->client->setResource('createContact')->post($identity);
}
/**
* @param $contactId
* @param array $data
*
* @throws HubspotException
*
* @return HubspotHttpResponseService
*/
public function updateContactById($contactId, $data = []): Response
{
if (!$this->validator->isValid($data)) {
throw new HubspotException(sprintf('Invalid contact data'));
}
$contactData = $this->validator->getData();
if (array_key_exists('salutation', $data) && $data['salutation'] === 'firma') {
$contactData['name'] = $data['lastname'];
$identity = $this->formatCompanyIdentity($contactData);
return $this->client->setResource('updateCompany')->put($identity, [$contactId]);
}
$identity = $this->formatContactIdentity($contactData);
return $this->client->setResource('updateContact')->post($identity, [$contactId]);
}
/**
* @param array $data
*
* @return mixed
*/
private function formatContactIdentity($data)
{
$identity['properties'] = [];
foreach ($data as $property => $value) {
$identity['properties'][] = [
'property' => $property,
'value' => $value,
];
}
return $identity;
}
/**
* @param array $data
*
* @return array
*/
private function formatCompanyIdentity(array $data) : array
{
$whiteList = ['name',
'phone',
'hs_lead_status',
'website',
'domain',
'lifecyclestage',
'zip',
'city',
'country',
'state',
'address',
'numberofemployees',
'description'
];
$settings = $this->configurationService->getSettings();
$defaultCustomFields = array_key_exists('hubspot_address_free_fields', $settings) ?
$settings['hubspot_address_free_fields'] : [];
if (!empty($defaultCustomFields)) {
foreach ($defaultCustomFields as $defaultCustomField => $systemField) {
$whiteList[] = "xthubspot_{$defaultCustomField}";
}
}
$identity['properties'] = [];
foreach ($data as $property => $value) {
if (!in_array($property, $whiteList, true)) {
continue;
}
if (strpos($property, 'xthubspot_') !== false) {
$property = str_replace('xthubspot_', '', $property);
}
$identity['properties'][] = [
'name' => $property,
'value' => $value,
];
}
return $identity;
}
/**
* @param array $options
*
* @return Response
*/
public function getRecentlyCreatedContacts($options = [])
{
$options += $this->itemsCount;
return $this->client->setResource('recentlyAddedContacts')->read($options);
}
/**
* @param string $type
* @param array $options
*
* @throws Exception\MetaException
* @throws HubspotException
* @throws JsonException
*
* @return HubspotHttpResponseService
*/
public function pullContacts($type = 'all', $options = []): Response
{
$options += $this->itemsCount;
if ('all' !== $type && !in_array($type, array_keys($this->allowedSyncContactOptions), true)) {
throw new HubSpotException(sprintf('Sync Type %s not allowed', $type));
}
if ($type !== 'all') {
$metaInfo = $this->meta->setName($type)->get();
if (!empty($metaInfo)) {
$options = array_merge(
$options,
[
'vidOffset' => $metaInfo['vidOffset'],
'timeOffset' => $metaInfo['timeOffset'],
]
);
}
}
/** @var Response $response */
return $this->{$this->allowedSyncContactOptions[$type]}($options);
}
/**
* @param $contactId
*
* @return HubspotHttpResponseService
*/
public function getContactById($contactId): Response
{
return $this->client->setResource('getContactById')->read([], [$contactId]);
}
/**
* @return HubspotHttpResponseService
*/
public function getCompanies(): HubspotHttpResponseService
{
return $this->client->setResource('getCompanies')->read();
}
/**
* @return HubspotHttpResponseService
*/
public function getRecentCompanies(): HubspotHttpResponseService
{
return $this->client->setResource('getRecentCompanies')->read();
}
/**
* @param int $companyId
*
* @return HubspotHttpResponseService
*/
public function getCompanyById(int $companyId): HubspotHttpResponseService
{
return $this->client->setResource('getCompanyById')->read([], [$companyId]);
}
/**
* @param string $type
* @param array $options
*
* @throws Exception\MetaException
* @throws HubspotException
* @throws JsonException
*
* @return mixed
*/
public function pullCompanies($type = 'all', $options = [])
{
$options += $this->itemsCount;
if (in_array($type, ['all', 'company'])) {
$type = 'companies';
}
if (!in_array($type, array_keys($this->allowedSyncContactOptions), true)) {
throw new HubSpotException(sprintf('Sync Type %s not allowed', $type));
}
if (($type !== 'companies') && $metaInfo = $this->meta->setName($type)->get()) {
$options = array_merge(
$options,
[
'vidOffset' => $metaInfo['vidOffset'],
'timeOffset' => $metaInfo['timeOffset'],
]
);
}
/** @var Response $response */
return $this->{$this->allowedSyncContactOptions[$type]}($options);
}
/**
* @param int $companyId
*
* @return HubspotHttpResponseService
*/
public function getCompanyContacts(int $companyId): HubspotHttpResponseService
{
return $this->client->setResource('getCompanyContacts')->read([], [$companyId]);
}
/**
* @param int $companyId
* @param int $contactId
*
* @return HubspotHttpResponseService
*/
public function addContactToCompany(int $companyId, int $contactId): HubspotHttpResponseService
{
$data = [
"fromObjectId" => $contactId,
"toObjectId" => $companyId,
"category" => 'HUBSPOT_DEFINED',
"definitionId" => 1,
];
return $this->client->setResource('getCompanyContacts')->put($data);
}
/**
* @param int $companyId
* @param int $contactId
*
* @return HubspotHttpResponseService
*/
public function removeContactFromCompany(int $companyId, int $contactId): HubspotHttpResponseService
{
$data = [
"fromObjectId" => $contactId,
"toObjectId" => $companyId,
"category" => 'HUBSPOT_DEFINED',
"definitionId" => 1,
];
return $this->client->setResource('deleteContactFromCompany')->put($data);
}
/**
* @param int $ownerId
*
* @throws HubspotException
*
* @return HubspotHttpResponseService
*/
public function getHubspotOwner(int $ownerId): HubspotHttpResponseService
{
return $this->client->apiCall('getHubspotOwner', 'get', [], [$ownerId]);
}
}
@@ -0,0 +1,136 @@
<?php
namespace Xentral\Modules\Hubspot;
use Xentral\Components\Database\Database;
use Xentral\Modules\Hubspot\Exception\HubspotDealGatewayNotFoundException;
final class HubspotDealGateway
{
/** @var Database $db */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param int $hsDealId
*
* @return array
*/
public function getByHubspotId($hsDealId)
{
if (!is_numeric($hsDealId)) {
throw new HubspotDealGatewayNotFoundException(
sprintf(
'Hubspot Deal not found: HubspotID%s',
$hsDealId
)
);
}
return $this->db->fetchRow(
'SELECT
d.id,
d.created_at,
d.data,
d.wiedervorlage_id
FROM `hubspot_deals` AS `d` WHERE d.hidden = 0 AND d.hs_deal_id = :id',
['id' => (int)$hsDealId]
);
}
/**
* @param int $resubmissionId
*
* @return array
*/
public function getByResubmissionId($resubmissionId)
{
if (!is_numeric($resubmissionId)) {
throw new HubspotDealGatewayNotFoundException(
sprintf(
'Hubspot Deal not found for : ResubmissionId%s',
$resubmissionId
)
);
}
return $this->db->fetchRow(
'SELECT
d.id,
d.created_at,
d.data,
d.wiedervorlage_id,
d.hs_deal_id
FROM `hubspot_deals` AS `d` WHERE d.hidden = 0 AND d.wiedervorlage_id = :id',
['id' => (int)$resubmissionId]
);
}
/**
* @param int $stageId
**
*
* @return array
*/
public function getMappingStageByResubmissionStageId($stageId)
{
if (!is_numeric($stageId)) {
throw new HubspotDealGatewayNotFoundException(
sprintf(
'Hubspot Deal Mapping not found for stage: ID%s',
$stageId
)
);
}
return $this->db->fetchRow(
'SELECT
hm.id,
hm.created_at,
hm.wiedervorlage_stage_id,
hm.label,
hm.value,
hm.wiedervorlage_view_id,
hm.type FROM hs_mapping_leads `hm` WHERE hm.wiedervorlage_stage_id=:resubmission_id AND hm.type=:type',
['resubmission_id' => $stageId, 'type' => 'deals']
);
}
/**
* @param string $value
**
*
* @return array
*/
public function getMappingStageByValue($value)
{
if (!is_string($value)) {
throw new HubspotDealGatewayNotFoundException(
sprintf(
'Hubspot Deal Mapping not found for value: %s',
$value
)
);
}
return $this->db->fetchRow(
'SELECT
hm.id,
hm.created_at,
hm.wiedervorlage_stage_id,
hm.label,
hm.value,
hm.wiedervorlage_view_id,
hm.type FROM hs_mapping_leads `hm` WHERE hm.value=:value AND hm.type=:type',
['value' => $value, 'type' => 'deals']
);
}
}
@@ -0,0 +1,172 @@
<?php
namespace Xentral\Modules\Hubspot;
use Xentral\Components\Database\Database;
use Xentral\Modules\Hubspot\Exception\HubspotException;
final class HubspotDealPropertyService
{
/**
* @var HubspotClientService
*/
private $client;
/**
* @var Database
*/
private $db;
/** @var HubspotContactPropertyGateway $propertyGateway */
private $propertyGateway;
/**
* HubspotDealPropertyService constructor.
*
* @param HubspotClientService $client
* @param Database $db
* @param HubspotContactPropertyGateway $propertyGateway
*/
public function __construct(
HubspotClientService $client,
Database $db,
HubspotContactPropertyGateway $propertyGateway
) {
$this->client = $client;
$this->db = $db;
$this->propertyGateway = $propertyGateway;
}
/**
* @throws HubspotException
* @return array
*/
public function getDealStages()
{
$response = $this->client->setResource('getPipelineProperties')->read([], ['deals']);
if ($response->getStatusCode() !== 200) {
throw new HubspotException($response->getError());
}
if (($data = $response->getJson()) && array_key_exists('results', $data)) {
$stages = array_column($data['results'], 'stages');
return reset($stages);
}
return [];
}
public function installDealStages()
{
// Get Deal Stages
$firstStage = 0;
$viewId = 0;
if ($dealStages = $this->propertyGateway->getLeadsByType('deals')) {
$stage = array_unique(array_column($dealStages, 'wiedervorlage_stage_id'));
$firstStage = $stage[0];
}
if ($firstStage > 0) {
$viewId = $this->db->fetchValue(
'SELECT ws.view FROM wiedervorlage_stages `ws` WHERE ws.id=:id',
[
'id' => $firstStage,
]
);
}
if (empty($firstStage) || empty($viewId)) {
// CHECK the default view
if ($this->db->fetchValue(
'SELECT wv.id FROM wiedervorlage_view `wv`
WHERE wv.name=:name AND wv.shortname=:desc AND wv.active=:active AND wv.project=0',
[
'name' => 'Hubspot',
'desc' => 'Hubspot',
'active' => 1,
]
)) {
return;
}
$this->db->perform(
'INSERT INTO wiedervorlage_view(name,shortname,active) VALUES (:name,:desc_short,1)',
[
'name' => 'Hubspot',
'desc_short' => 'Hubspot',
]
);
$viewId = $this->db->lastInsertId();
}
if ($viewId > 0 && ($ahDealStages = $this->getDealStages())) {
$position = (int)$this->getMaxSortByViewId($viewId) + 1;
foreach ($ahDealStages as $hDealStage) {
if ($this->db->fetchValue(
'SELECT hm.id FROM hs_mapping_leads `hm` WHERE hm.value=:value AND hm.type=:type',
[
'value' => $hDealStage['stageId'],
'type' => 'deals',
]
)) {
continue;
}
$this->db->perform(
'INSERT INTO wiedervorlage_stages (kurzbezeichnung,name,stageausblenden,sort,view)
VALUES(:desc, :name,:enabled, :position,:wiedervorlage_view_id)',
[
'desc' => $hDealStage['label'],
'name' => $hDealStage['label'],
'position' => $position,
'wiedervorlage_view_id' => $viewId,
'enabled' => 1,
]
);
if ($stageId = $this->db->lastInsertId()) {
$this->db->perform(
'INSERT INTO hs_mapping_leads (`label`, `value`, `type`, `wiedervorlage_stage_id`, `is_system`,
`wiedervorlage_view_id`)
VALUES(:label, :value,:type, :wstage_id, :is_system, :view_id)',
[
'label' => $hDealStage['label'],
'value' => $hDealStage['stageId'],
'type' => 'deals',
'wstage_id' => $stageId,
'is_system' => 1,
'view_id' => $viewId,
]
);
}
$position++;
}
}
}
/**
* @param int $viewId
*
* @return false|float|int|string
*/
public function getMaxSortByViewId($viewId)
{
return $this->db->fetchValue(
'SELECT MAX(ws.sort) FROM wiedervorlage_stages `ws` WHERE ws.`view`=:id',
['id' => $viewId]
);
}
/**
* @param int $viewId
*
* @return false|float|int|string
*/
public function getMinStageByViewId($viewId)
{
return $this->db->fetchValue(
'SELECT MIN(ws.id) FROM wiedervorlage_stages `ws` WHERE ws.`view`=:id',
['id' => $viewId]
);
}
}
@@ -0,0 +1,174 @@
<?php
namespace Xentral\Modules\Hubspot;
use Xentral\Modules\Hubspot\Exception\HubspotException;
use Xentral\Modules\Hubspot\HubspotHttpResponseService as Response;
use Xentral\Modules\Hubspot\Validators\DealValidator;
final class HubspotDealService
{
/** @var array $allowedSyncDealsOptions */
private $allowedSyncDealsOptions = [
'recently_created_deals' => 'getRecentlyCreatedDeals',
'recently_updated_deals' => 'getRecentlyUpdatedDeals',
'all_deals' => 'getDeals',
];
/** @var array $asDealPhases */
private $asDealPhases = [
'appointmentscheduled',
'qualifiedtobuy',
'presentationscheduled',
'decisionmakerboughtin',
'contractsent',
'closedwon',
'closedlost',
];
private $limit = ['limit' => 200];
/** @var HubspotClientService $client */
private $client;
/** @var HubspotMetaService $meta */
private $meta;
/** @var DealValidator $validator */
private $validator;
public function __construct(
HubspotClientService $client,
HubspotMetaService $meta,
DealValidator $validator
) {
$this->client = $client;
$this->meta = $meta;
$this->validator = $validator;
}
public function createDeal($data = [])
{
if (!$this->validator->isValid($data)) {
throw new HubSpotException(sprintf('Invalid Deal data'));
}
$default = ['pipeline' => 'default', 'dealstage' => 'appointmentscheduled'];
$data += $default;
if (!$this->validator->isValid($data)) {
throw new HubSpotException(sprintf('Invalid Deal data'));
}
$deal = $this->formatDealData($this->validator->getData());
return $this->client->setResource('createDeal')->post($deal);
}
public function getDealById($dealId)
{
return $this->client->setResource('getDealById')->read([], [$dealId]);
}
/**
* @param array $options
*
* @return Response
*/
public function getDeals($options = [])
{
$options += $this->limit;
return $this->client->setResource('allDeals')->read($options);
}
/**
* @param array $options
*
* @return Response
*/
public function getRecentlyUpdatedDeals($options = [])
{
$options += $this->limit;
return $this->client->setResource('recentlyUpdatedDeals')->read($options);
}
/**
* @param int $dealId
*
* @return Response
*/
public function deleteDeal($dealId)
{
return $this->client->setResource('deleteDeal')->delete([], [$dealId]);
}
/**
* @param int $dealId
* @param array $data
*
* @return HubspotHttpResponseService
*/
public function updateDealById($dealId, $data = [])
{
if (!$this->validator->isValid($data)) {
throw new HubSpotException(sprintf('Invalid Deal data'));
}
$deal = $this->formatDealData($this->validator->getData());
return $this->client->setResource('updateDeal')->put($deal, [$dealId]);
}
/**
* @param array $options
*
* @return Response
*/
public function getRecentlyCreatedDeals($options = [])
{
$options += $this->limit;
return $this->client->setResource('recentlyAddedDeals')->read($options);
}
/**
* @param string $type
* @param array $options
*
* @return mixed
*/
public function pullDeals($type = 'all', $options = [])
{
if ('all' !== $type && !in_array($type, array_keys($this->allowedSyncDealsOptions), true)) {
throw new HubSpotException(sprintf('Sync Deal with Type %s not allowed', $type));
}
if (($type !== 'all') && ($metaInfo = $this->meta->setName($type)->get())) {
$since = !array_key_exists('since', $metaInfo) ? time() * 1000 : $metaInfo['since'];
$options += ['offset' => $metaInfo['offset'], 'since' => $since];
}
/** @var Response $response */
return $this->{$this->allowedSyncDealsOptions[$type]}($options);
}
/**
* @param array $data
*
* @return mixed
*/
private function formatDealData($data)
{
$identity['properties'] = [];
foreach ($data as $property => $value) {
$identity['properties'][] = [
'name' => $property,
'value' => $value,
];
}
return $identity;
}
}
@@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Hubspot;
use Xentral\Modules\Hubspot\Exception\HubspotEngagementException;
final class HubspotEngagementService
{
/** @var HubspotClientService $client */
private $client;
/** @var string[] */
private const ASSOCIATIONS_VALIDATOR = [
'contact' => 'contactIds',
'company' => 'companyIds',
'deal' => 'dealIds',
'owner' => 'ownerIds',
'ticket' => 'ticketIds',
];
/** @var string[] */
private const ALLOWED_TYPES = ['EMAIL', 'CALL', 'MEETING', 'TASK', 'NOTE'];
/**
* @param HubspotClientService $client
*/
public function __construct(HubspotClientService $client)
{
$this->client = $client;
}
/**
* @param string $body
* @param array $associationIds
* @param string $type
*
* @return HubspotHttpResponseService|null
*/
public function createCompanyEngagement(
string $body,
array $associationIds = [],
string $type = 'NOTE'
): ?HubspotHttpResponseService {
try {
$data = $this->getCreateData($type, $body, 'company', $associationIds);
$response = $this->client->apiCall('createEngagement', 'post', $data);
} catch (Exception\HubspotException | HubspotEngagementException $e) {
return null;
}
return $response;
}
/**
* @param string $body
* @param array $associationIds
* @param string $type
*
* @return HubspotHttpResponseService|null
*/
public function createContactEngagement(
string $body,
array $associationIds = [],
string $type = 'NOTE'
): ?HubspotHttpResponseService {
try {
$data = $this->getCreateData($type, $body, 'contact', $associationIds);
$response = $this->client->apiCall('createEngagement', 'post', $data);
} catch (Exception\HubspotException | HubspotEngagementException $e) {
return null;
}
return $response;
}
/**
* @param string $body
* @param array $associationIds
* @param string $type
*
* @return HubspotHttpResponseService|null
*/
public function createDealEngagement(
string $body,
array $associationIds = [],
string $type = 'NOTE'
): ?HubspotHttpResponseService {
try {
$data = $this->getCreateData($type, $body, 'deal', $associationIds);
$response = $this->client->apiCall('createEngagement', 'post', $data);
} catch (Exception\HubspotException | HubspotEngagementException $e) {
return null;
}
return $response;
}
/**
* @param int $engagementId
* @param string $body
* @param string $intendedTo
* @param string $type
*
* @return bool
*/
public function updateEngagement(int $engagementId, string $body, string $intendedTo, string $type = 'NOTE'): bool
{
try {
$data = $this->getCreateData($type, $body, $intendedTo);
$response = $this->client->apiCall('updateEngagement', 'patch', $data, [$engagementId]);
} catch (Exception\HubspotException | HubspotEngagementException $e) {
return false;
}
return $response->getStatusCode() === 200;
}
/**
* @param array $options
*
* @throws Exception\HubspotException
*
* @return HubspotHttpResponseService
*/
public function getRecentEngagements(array $options = []): HubspotHttpResponseService
{
if (array_key_exists('offset', $options) && array_key_exists('since', $options)) {
unset($options['since'], $options['offset']);
}
return $this->client->apiCall('getRecentEngagements', 'get', $options);
}
/**
* @param string $type
* @param string $body
* @param string $intendedTo
* @param array $associationIds
*
* @throws HubspotEngagementException
*
* @return array
*/
private function getCreateData(string $type, string $body, string $intendedTo, array $associationIds = []): array
{
if (!in_array($type, self::ALLOWED_TYPES)) {
throw new HubspotEngagementException(sprintf('Type %s is not allowed !', $type));
}
if (!array_key_exists($intendedTo, self::ASSOCIATIONS_VALIDATOR)) {
throw new HubspotEngagementException(sprintf('Intended Association %s is not allowed !', $intendedTo));
}
$data = [
'engagement' => [
'active' => true,
'type' => $type,
'timestamp' => time() * 1000,
],
];
if (!empty($body)) {
$data['metadata'] = ['body' => $body];
}
if (!empty($associationIds)) {
$data['associations'] = [
self::ASSOCIATIONS_VALIDATOR[$intendedTo] => $associationIds,
];
}
return $data;
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Hubspot;
use Xentral\Components\Database\Database;
use Xentral\Components\Database\Exception\DatabaseExceptionInterface;
use Xentral\Modules\Hubspot\Exception\HubspotException;
final class HubspotEventService
{
/** @var Database $db */
private $db;
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param string $event
*
* @return int
*/
public function add(string $event): int
{
$add = 'INSERT INTO `hubspot_events` (`event`, `created_at` )
VALUES (:event, NOW())';
try {
$this->db->perform($add, ['event' => $event]);
} catch (DatabaseExceptionInterface $exception) {
throw new HubspotException($exception->getMessage());
}
return $this->db->lastInsertId();
}
/**
* @param int $id
*
* @return void
*/
public function deleteById(int $id): void
{
$this->db->perform('DELETE FROM `hubspot_events` WHERE id = :id', ['id' => $id]);
}
/**
* @return void
*/
public function deleteAll(): void
{
$this->db->perform('DELETE FROM `hubspot_events`');
}
/**
* @param int $days
*
* @return void
*/
public function deleteByInterval(int $days = 30): void
{
$sql = 'DELETE FROM `hubspot_events` WHERE `created_at` < DATE_SUB(NOW(), INTERVAL :days DAY)';
$this->db->perform($sql, ['days' => $days]);
}
}
@@ -0,0 +1,198 @@
<?php
namespace Xentral\Modules\Hubspot;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Client;
use Xentral\Modules\Hubspot\HubspotHttpResponseService as Response;
use Xentral\Modules\Hubspot\Exception\HttpClientException;
use Xentral\Modules\Hubspot\Interfaces\HubspotHttpClientInterface;
final class HubspotHttpClientService implements HubspotHttpClientInterface
{
/**
* @var string
*/
protected $endpoint = null;
/**
* @var array
*/
protected $userAgent = [];
/**
* @var array
*/
protected $hRequestVerbs = [
self::GET_REQUEST => null,
self::POST_REQUEST => 'json',
self::PUT_REQUEST => 'json',
self::PATCH_REQUEST => 'json',
self::DELETE_REQUEST => null,
];
/**
* @var int
*/
private $timeout;
/**
* @var array
*/
private $_headers = [];
/**
* @param int $timeout > 0
*
*/
public function __construct($timeout = 0)
{
if (!is_int($timeout) || $timeout < 0) {
throw new HttpClientException(
sprintf(
'Connection timeout must be an int >= 0, got "%s".',
is_object($timeout) ? get_class($timeout) : gettype($timeout) . ' ' . var_export($timeout, true)
)
);
}
if (!empty($timeout)) {
$this->timeout = $timeout;
}
}
/**
* @param string $url
* @param string $method
* @param array $data
*
* @param array $headers
*
* @return HubspotHttpResponseService
* @throws HttpClientException
*/
public function performRequest($url, $method, array $data = [], $headers = [])
{
$this->setHeader($headers);
$hHeaders = $this->getHeaders();
try {
$client = $this->getClient();
$keyParam = $this->hRequestVerbs[$method];
$paramData = ['headers' => $hHeaders];
if ($keyParam !== null) {
$paramData[$keyParam] = $data;
}
/** @var Response */
$response = $client->request($method, $url, $paramData);
return new HubspotHttpResponseService($response);
} catch (RequestException $exception) {
throw new HttpClientException($exception->getMessage());
} catch (GuzzleException $exception) {
throw new HttpClientException($exception->getMessage());
}
}
/**
* @return Client
*/
protected function getClient()
{
return new Client(['timeout' => $this->timeout]);
}
/**
* @param string $url
* @param array $data
* @param array $header
*
* @return Response
*/
public function get($url, $data = [], $header = [])
{
if (!empty($data)) {
$query = parse_url($url, PHP_URL_QUERY);
$newQuery = http_build_query($data);
$url = $query ? $url . '&' . $newQuery : $url . '?' . $newQuery;
}
return $this->performRequest($url, static::GET_REQUEST, [], $header);
}
/**
* @param string $url
* @param array $data
* @param array $header
*
* @return Response
*/
public function post($url, $data = [], $header = [])
{
$defHeader = ['Content-Type' => 'application/json', 'Accept' => 'application/json'];
$header += $defHeader;
return $this->performRequest($url, static::POST_REQUEST, $data, $header);
}
/**
* @param string $url
* @param array $data
* @param array $header
*
* @return Response
*/
public function patch($url, $data = [], $header = [])
{
return $this->performRequest($url, static::PATCH_REQUEST, $data, $header);
}
/**
* @param string $url
* @param array $data
* @param array $header
*
* @return Response
*/
public function delete($url, $data = [], $header = [])
{
return $this->performRequest($url, static::DELETE_REQUEST, $data, $header);
}
/**
* @param array $option
*/
protected function setHeader($option = [])
{
$this->_headers += $option;
}
/**
* @return array
*/
protected function getHeaders()
{
$default = ['User-Agent' => 'Xentral-ERP-CRM'];
return $this->_headers += $default;
}
/**
* @param string $url
* @param array $data
* @param array $header
*
* @return Response
*/
public function put($url, $data = [], $header = [])
{
$defHeader = ['Content-Type' => 'application/json', 'Accept' => 'application/json'];
$header += $defHeader;
return $this->performRequest($url, static::PUT_REQUEST, $data, $header);
}
}
@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Hubspot;
use \Psr\Http\Message\ResponseInterface;
final class HubspotHttpResponseService
{
/** @var ResponseInterface $response */
private $response;
/**
* @param ResponseInterface $response
*/
public function __construct(ResponseInterface $response)
{
$this->response = $response;
}
/**
* Returns the json response body
*
* @return array
*/
public function getJson(): array
{
$content = (string)$this->response->getBody();
$jsonResponse = json_decode($content, true);
if ($jsonResponse === null || (json_last_error() !== JSON_ERROR_NONE)) {
return [];
}
return $jsonResponse;
}
/**
* @return int
*/
public function getStatusCode() : int
{
return $this->response->getStatusCode();
}
/**
* Returns the error message
*
* @return string
*/
public function getError(): string
{
if (!in_array($this->getStatusCode(), [200, 201, 204])) {
if (($resp = $this->getJson()) && array_key_exists('error', $resp)) {
return $resp['error'];
}
return 'Unknown Error';
}
return '';
}
}
@@ -0,0 +1,157 @@
<?php
namespace Xentral\Modules\Hubspot;
use Xentral\Modules\Hubspot\Exception\MetaException;
class HubspotMetaService
{
private $name;
private $content;
private $extension;
private $tmpDir;
/**
* @param string $tmpDir
* @param string $ext
*/
public function __construct(string $tmpDir, $ext = 'json')
{
$this->extension = $ext;
$this->tmpDir = $tmpDir;
}
/**
* @param string $name
*
* @throws MetaException
* @return HubspotMetaService
*/
public function setName(string $name): HubspotMetaService
{
if (empty($name)) {
throw new MetaException('Name cannot be empty');
}
$this->name = preg_replace('/[^a-zA-Z]+/', '', $name);
return $this;
}
/**
* @throws MetaException
*
* @return string
*/
private function getFullFileName(): string
{
$metaTmpDir = $this->tmpDir . 'meta';
if (!is_dir($metaTmpDir) && !mkdir($metaTmpDir, 0777, true) && !is_dir($metaTmpDir)) {
throw new MetaException(sprintf('Directory "%s" was not created', $metaTmpDir));
}
$metaTmpDir .= DIRECTORY_SEPARATOR . $this->name;
if (!empty($this->extension)) {
$metaTmpDir .= '.' . $this->extension;
}
return $metaTmpDir;
}
/**
* @throws MetaException
*
* @return array
*/
public function get(): array
{
if (!empty($this->content)) {
return $this->content;
}
$fullFileName = $this->getFullFileName();
$metaContent = @file_get_contents($fullFileName);
$meta = json_decode($metaContent, true);
if ($meta === null || (json_last_error() !== JSON_ERROR_NONE)
) {
return [];
}
return $meta;
}
/**
* @param array $data
*
* @throws MetaException
*
* @return bool
*/
public function update($data = []): bool
{
if (!$this->save($data)) {
return false;
}
$this->content = $data;
return true;
}
/**
* @param array $data
*
* @throws MetaException
*
* @return false|int
*/
public function save($data = [])
{
return file_put_contents($this->getFullFileName(), json_encode($data));
}
/**
* @throws MetaException
* @return bool
*/
public function exists(): bool
{
if (empty($this->name)) {
throw new MetaException('Meta file name is not set');
}
return is_file($this->getFullFileName());
}
/**
* @param $key
*
* @return bool
*/
public function keyExists($key): bool
{
$meta = null;
try {
if ($this->exists()) {
$meta = $this->get();
}
} catch (MetaException $exception) {
return false;
}
return null !== $meta && array_key_exists($key, $meta);
}
/**
* @throws MetaException
*
* @return bool
*/
public function delete(): bool
{
if (!$this->exists()) {
return false;
}
return @unlink($this->getFullFileName());
}
}
@@ -0,0 +1,17 @@
<?php
namespace Xentral\Modules\Hubspot\Interfaces;
interface HubspotHttpClientInterface
{
const GET_REQUEST = 'GET';
const POST_REQUEST = 'POST';
const DELETE_REQUEST = 'DELETE';
const PATCH_REQUEST = 'PATCH';
const PUT_REQUEST = 'PUT';
//public function get($url, $data = [], $header = []);
}
@@ -0,0 +1,11 @@
<?php
namespace Xentral\Modules\Hubspot\RequestQueues\Exception;
use RuntimeException as SplRuntimeException;
class RequestQueuesException extends SplRuntimeException
{
}
@@ -0,0 +1,44 @@
<?php
namespace Xentral\Modules\Hubspot\RequestQueues;
use Xentral\Components\Database\Database;
use Xentral\Modules\Hubspot\RequestQueues\Exception\RequestQueuesException;
final class HubspotRequestQueuesGateway
{
/** @var Database $db */
private $db;
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param string $type
*
* @throws RequestQueuesException
* @return array
*/
public function getNewRequestsByCallType($type)
{
if (!is_string($type)) {
throw new RequestQueuesException(
'Call Type should be a string'
);
}
$sql = 'SELECT
rq.id,
rq.command,
rq.not_before,
rq.try,
rq.runner,
rq.check_sum,
rq.on_after_done
FROM hubspot_request_queues AS `rq` WHERE rq.call_type=:type AND rq.completed=0 AND rq.deleted=0
ORDER BY rq.created_at';
return $this->db->fetchAll($sql, ['type' => $type]);
}
}
@@ -0,0 +1,270 @@
<?php
namespace Xentral\Modules\Hubspot\RequestQueues;
use ApplicationCore;
use Xentral\Components\Database\Database;
use Xentral\Components\Database\Exception\DatabaseExceptionInterface;
use Xentral\Modules\Hubspot\Exception\HubspotException;
use Xentral\Modules\Hubspot\HubspotEventService;
use Xentral\Modules\Hubspot\HubspotHttpResponseService;
use Xentral\Modules\Hubspot\RequestQueues\Exception\RequestQueuesException;
use RuntimeException;
final class HubspotRequestQueuesService
{
/** @var int */
const LOOP_WAITING_TIME = 10000000;
/** @var int */
const LOOP_BATCH = 1;
/** @var HubspotRequestQueuesGateway $gateway */
private $gateway;
/** @var ApplicationCore $app */
private $app;
/** @var Database $db */
private $db;
/** @var array $completedIds */
private $completedIds = [];
/** @var HubspotEventService $eventService */
private $eventService;
/**
* @param HubspotRequestQueuesGateway $gateway
* @param Database $database
* @param ApplicationCore $app
* @param HubspotEventService $eventService
*/
public function __construct(
HubspotRequestQueuesGateway $gateway,
Database $database,
ApplicationCore $app,
HubspotEventService $eventService
) {
$this->gateway = $gateway;
$this->app = $app;
$this->db = $database;
$this->eventService = $eventService;
}
/**
* @param array $option
*
* @throws RequestQueuesException
* @return int
*/
public function addRequest($option)
{
$default = ['check_sum' => '', 'command' => '', 'on_after_done' => '', 'not_before' => 0, 'call_type' => ''];
$hCommand = [];
if (array_key_exists('method', $option)) {
$hCommand['method'] = $option['method'];
unset($option['method']);
}
if (array_key_exists('args', $option)) {
$hCommand['args'] = $option['args'];
unset($option['args']);
}
if (!empty($hCommand)) {
$option['command'] = json_encode($hCommand, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
}
if (!empty($option['check_sum'])) {
$default['check_sum'] = $option['check_sum'];
} else {
$default['check_sum'] = array_key_exists('command', $option) ? md5($option['command']) : '';
}
$option = array_merge($default, $option);
if (!empty($option['on_after_done']) && is_array($option['on_after_done'])) {
$option['on_after_done'] = json_encode(
$option['on_after_done'],
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
);
}
$check = 'SELECT EXISTS(
SELECT id FROM hubspot_request_queues
WHERE deleted=0 AND completed=0 AND check_sum=:check_sum AND runner=:runner
)';
if (empty(
$this->db->fetchValue(
$check,
['runner' => $option['runner'], 'check_sum' => $option['check_sum']]
)
)) {
$add = 'INSERT INTO hubspot_request_queues (command, on_after_done, runner, not_before, check_sum, call_type )
VALUES (:command, :on_after_done, :runner, :not_before, :check_sum, :call_type)';
try {
$this->db->perform($add, $option);
} catch (DatabaseExceptionInterface $exception) {
throw new RequestQueuesException(json_encode(['too' => $option, $exception->getMessage()]));
}
return $this->db->lastInsertId();
}
return 0;
}
/**
* @param string $callType
*
* @throws HubspotException
* @throws RequestQueuesException
*
* @return void
*/
public function execute(string $callType): void
{
$jobs = $this->gateway->getNewRequestsByCallType($callType);
if (empty($jobs)) {
return;
}
$batch_loop = self::LOOP_BATCH;
$iCount = 0;
$bSkippWait = count($jobs) <= 1;
foreach ($jobs as $job) {
$this->db->perform('UPDATE `hubspot_request_queues` SET try = try+1 WHERE id =:id', ['id' => $job['id']]);
$oClass = $this->app->Container->get($job['runner']);
$hCommand = json_decode($job['command'], true);
if (!is_array($hCommand)) {
continue;
}
$xArg = $hCommand['args'];
$sMethod = $hCommand['method'];
try {
$response = call_user_func_array([$oClass, $sMethod], $xArg);
if (empty($response)) {
continue;
}
} catch (RuntimeException $exception) {
$this->db->perform(
'UPDATE hubspot_request_queues SET completed = 1 WHERE id = :id',
['id' => $job['id']]
);
$this->completedIds[] = $job['id'];
$this->eventService->add($exception->getMessage());
continue;
}
$this->onAfterDone($job['id'], $response, $job['on_after_done']);
if ($bSkippWait === false) {
if ($iCount === $batch_loop) {
$batch_loop += self::LOOP_BATCH;
@usleep(self::LOOP_WAITING_TIME);
}
echo 'Script always alive... ';
}
}
}
/**
* @param int $id
* @param HubspotHttpResponseService|null $response
* @param string $onAfter
*
* @throws HubspotException
*
* @return void
*/
private function onAfterDone(int $id, ?HubspotHttpResponseService $response, string $onAfter = ''): void
{
$onAfterData = !empty($onAfter) ? json_decode($onAfter, true) : [];
if (!empty($onAfterData) && array_key_exists('runner', $onAfterData) && array_key_exists(
'method',
$onAfterData
) && array_key_exists('args', $onAfterData) && $this->app->Container->has($onAfterData['runner'])) {
$oClass = $this->app->Container->get($onAfterData['runner']);
if (!empty($onAfterData['replace_in_args']) && in_array($response->getStatusCode(), [200, 204], true)) {
$hasDataFetcher = !empty($onAfterData['data_fetcher']) && method_exists(
$this,
$onAfterData['data_fetcher']
);
$jsonData = $hasDataFetcher === true ? $this->{$onAfterData['data_fetcher']}(
$response
) : $response->getJson();
if (empty($jsonData)) {
return;
}
foreach ($onAfterData['args'] as &$xArg) {
if (is_string($xArg)) {
foreach ($onAfterData['replace_in_args'] as $replace_with) {
if (!empty($jsonData[$replace_with])) {
$xArg = sprintf($xArg, $jsonData[$replace_with]);
}
}
}
}
}
call_user_func_array(
[$oClass, $onAfterData['method']],
$onAfterData['args']
);
}
if (array_key_exists('event', $onAfterData) &&
!empty($onAfterData['event']) &&
is_string($onAfterData['event']) &&
in_array($response->getStatusCode(), [200, 204], true)
) {
$this->eventService->add($onAfterData['event']);
}
if (array_key_exists('other_event', $onAfterData)) {
$other = $onAfterData['other_event'];
if (!empty($other) && $this->app->Container->has($other['runner'])) {
$otherEventClass = $this->app->Container->get($other['runner']);
call_user_func_array(
[$otherEventClass, $other['method']],
$other['args']
);
}
}
if (is_numeric($id)) {
$this->db->perform('UPDATE hubspot_request_queues SET completed = 1 WHERE id = :id', ['id' => $id]);
$this->completedIds[] = $id;
}
}
/**
* @return void
*/
public function cleanup()
{
foreach ($this->completedIds as $completedId) {
echo $completedId;
$this->db->perform(
'DELETE FROM hubspot_request_queues WHERE id=:id AND completed=1',
['id' => $completedId]
);
}
$this->eventService->deleteByInterval();
unset($this->completedIds);
}
/**
* @param HubspotHttpResponseService $response
*
* @return array
*/
private function getEngagement(HubspotHttpResponseService $response): array
{
if ($response->getStatusCode() !== 200) {
return [];
}
$result = $response->getJson();
if (!array_key_exists('engagement', $result)) {
return [];
}
return $result['engagement'];
}
}
@@ -0,0 +1,65 @@
<?php
namespace Xentral\Modules\Hubspot\Scheduler\Adapter;
use ArrayObject;
use Xentral\Modules\Hubspot\Exception\SchedulerAdapterBadMethodException;
use Xentral\Modules\Hubspot\Scheduler\HubspotSchedulerTaskInterface;
final class SchedulerAdapter
{
/** @var HubspotSchedulerTaskInterface $schedulerTask */
private $schedulerTask;
/** @var bool $debugMode */
public $debugMode = false;
public function __construct(HubspotSchedulerTaskInterface $schedulerTask)
{
$this->schedulerTask = $schedulerTask;
}
public function __call($method, $args)
{
if (!method_exists($this->schedulerTask, $method)) {
$class = get_class($this->schedulerTask);
throw new SchedulerAdapterBadMethodException(sprintf('Method %s at %s class is missing', $method, $class));
}
if (is_callable([$this->schedulerTask, $method])) {
if ($method === 'execute' && empty($args) === true) {
$this->schedulerTask->beforeScheduleAction(new ArrayObject($args));
}
if ($this->debugMode === true) {
$this->debug(json_encode(new ArrayObject($args)));
$message = 'Call ' . get_class($this->schedulerTask) . '::' . $method . ' with args ' . json_encode(
$args
);
$this->debug($message);
}
call_user_func([$this->schedulerTask, $method], $args);
if ($method === 'execute' && empty($args) === true) {
$this->schedulerTask->afterScheduleAction(new ArrayObject($args));
}
} else {
$class = get_class($this->schedulerTask);
throw new SchedulerAdapterBadMethodException(sprintf('No callable method %s at %s class', $method, $class));
}
}
/**
* @param string $message
* @param null|string $debuggerFile
*
* @return null|void
*/
public function debug($message, $debuggerFile = null)
{
if ($this->debugMode === false) {
return null;
}
$logFile = null === $debuggerFile ? sys_get_temp_dir() . '/pull.log' : $debuggerFile;
file_put_contents($logFile, date('Y-m-d H:i:s') . '- ' . $message . "\n", FILE_APPEND | LOCK_EX);
}
}
@@ -0,0 +1,69 @@
<?php
namespace Xentral\Modules\Hubspot\Scheduler;
use ArrayObject;
use Xentral\Modules\Hubspot\RequestQueues\HubspotRequestQueuesService;
use Xentral\Modules\SubscriptionCycle\Scheduler\TaskMutexServiceInterface;
final class HubspotProcessSchedulerTask implements HubspotSchedulerTaskInterface
{
const CALL_TYPE = 'hubspot';
/** @var HubspotRequestQueuesService $gateway */
private $service;
/** @var TaskMutexServiceInterface $taskMutexService */
private $taskMutexService;
/**
* @param HubspotRequestQueuesService $service
* @param TaskMutexServiceInterface $taskMutexService
*/
public function __construct(HubspotRequestQueuesService $service, TaskMutexServiceInterface $taskMutexService)
{
$this->service = $service;
$this->taskMutexService = $taskMutexService;
}
/**
* @return void
*/
public function execute()
{
if ($this->taskMutexService->isTaskInstanceRunning('hubspot_process')) {
return;
}
$this->taskMutexService->setMutex('hubspot_process');
$this->service->execute(static::CALL_TYPE);
}
/**
* @return void
*/
public function cleanup()
{
$this->taskMutexService->setMutex('hubspot_process', false);
$this->service->cleanup();
}
/**
* @param ArrayObject $args
*
* @return void
*/
public function beforeScheduleAction(ArrayObject $args)
{
// TODO: Implement beforeScheduleAction() method.
}
/**
* @param ArrayObject $args
*
* @return void
*/
public function afterScheduleAction(ArrayObject $args)
{
// TODO: Implement afterScheduleAction() method.
}
}
@@ -0,0 +1,866 @@
<?php
namespace Xentral\Modules\Hubspot\Scheduler;
use JsonException;
use Xentral\Components\Database\Database;
use Xentral\Components\Database\Exception\EscapingException;
use Xentral\Modules\Country\Gateway\CountryGateway;
use Xentral\Modules\Hubspot\Exception\HubspotConfigurationServiceException;
use Xentral\Modules\Hubspot\Exception\HubspotException;
use Xentral\Modules\Hubspot\Exception\MetaException;
use Xentral\Modules\Hubspot\HubspotConfigurationService;
use Xentral\Modules\Hubspot\HubspotContactService;
use Xentral\Modules\Hubspot\HubspotEventService;
use Xentral\Modules\Hubspot\HubspotContactGateway;
use Xentral\Modules\Hubspot\HubspotMetaService;
use ArrayObject;
use Xentral\Modules\SubscriptionCycle\Scheduler\TaskMutexServiceInterface;
final class HubspotPullContactsTask implements HubspotSchedulerTaskInterface
{
/** @var HubspotContactService $contactService */
private $contactService;
/** @var Database $db */
private $db;
/** @var HubspotMetaService $meta */
private $meta;
/** @var HubspotContactGateway $gateway */
private $gateway;
/** @var HubspotConfigurationService $configuration */
private $configuration;
/** @var HubspotEventService $eventService */
private $eventService;
/** @var CountryGateway $countryGateway */
private $countryGateway;
/** @var TaskMutexServiceInterface $taskMutexService */
private $taskMutexService;
/** @var bool $mutexOn */
private $mutexOn = false;
/**
* @param HubspotContactService $contactService
* @param Database $db
* @param HubspotMetaService $metaService
* @param HubspotContactGateway $gateway
* @param HubspotConfigurationService $configuration
* @param HubspotEventService $eventService
* @param CountryGateway $countryGateway
* @param TaskMutexServiceInterface $taskMutexService
*/
public function __construct(
HubspotContactService $contactService,
Database $db,
HubspotMetaService $metaService,
HubspotContactGateway $gateway,
HubspotConfigurationService $configuration,
HubspotEventService $eventService,
CountryGateway $countryGateway,
TaskMutexServiceInterface $taskMutexService
) {
$this->db = $db;
$this->contactService = $contactService;
$this->meta = $metaService;
$this->gateway = $gateway;
$this->configuration = $configuration;
$this->eventService = $eventService;
$this->countryGateway = $countryGateway;
$this->taskMutexService = $taskMutexService;
}
/**
* @param array $option
* @param null $type
* @param false $recursiveMode
*
* @throws EscapingException
* @throws HubspotConfigurationServiceException
* @throws HubspotException
* @throws JsonException
* @throws MetaException
*
* @return void
*/
public function execute($option = [], $type = null, $recursiveMode = false): void
{
if ($recursiveMode === false && $this->mutexOn === false) {
if ($this->taskMutexService->isTaskInstanceRunning('hubspot_pull_contacts')) {
return;
}
$this->taskMutexService->setMutex('hubspot_pull_contacts');
$this->mutexOn = true;
}
$settings = $this->configuration->getSettings();
if ($settings['hs_sync_addresses'] !== true) {
return;
}
$contactType = $type ?? 'all';
if (empty($recursiveMode) && $this->hasHsContacts() === true) {
$contactType = $contactType === 'all' ? 'recently_updated' : $contactType;
}
if ($contactType === 'company') {
$contactType = 'companies';
}
$ret = $this->pull($contactType, $option);
if (!empty($ret['has_more'])) {
$option = ['vidOffset' => $ret['vidOffset'], 'timeOffset' => $ret['timeOffset']];
usleep(5000000);
$this->execute($option, $contactType, true);
}
}
/**
* @param array $option
* @param null $type
* @param false $recursiveMode
*
* @throws EscapingException
* @throws HubspotConfigurationServiceException
* @throws HubspotException
* @throws JsonException
* @throws MetaException
*
* @return void
*/
protected function executeForCompany($option = [], $type = null, $recursiveMode = false): void
{
if ($recursiveMode === false && $this->mutexOn === false) {
if ($this->taskMutexService->isTaskInstanceRunning('hubspot_pull_contacts')) {
return;
}
$this->taskMutexService->setMutex('hubspot_pull_contacts');
$this->mutexOn = true;
}
$settings = $this->configuration->getSettings();
if ($settings['hs_sync_addresses'] !== true) {
return;
}
$contactType = $type ?? 'company';
if (empty($recursiveMode) && $this->hasHsContacts() === true) {
$contactType = $contactType === 'company' ? 'recent_companies' : $contactType;
}
$ret = $this->pull($contactType, $option);
if (!empty($ret['has_more'])) {
$option = ['vidOffset' => $ret['vidOffset'], 'timeOffset' => $ret['timeOffset']];
usleep(5000000);
$this->executeForCompany($option, $contactType, true);
}
}
/**
* @param string $type
*
* @return bool
*/
private function hasHsContacts(string $type = 'address'): bool
{
return $this->db->fetchValue(
'SELECT COUNT(`id`) FROM `hubspot_contacts` WHERE `hidden` = 0 AND `type` = :type',
['type' => $type]
) > 0;
}
/**
* @param string $email
* @param int $addressId
*
* @return bool
*/
private function contactPersonExists(string $email, int $addressId): bool
{
return $this->db->fetchValue(
'SELECT `id` FROM `ansprechpartner` WHERE `email` = :email AND `adresse` = :addressId',
['email' => $email, 'addressId' => $addressId]
) > 0;
}
/**
* @param string $name
*
* @return int
*/
private function getAddressIdByCompanyName(string $name): int
{
return $this->db->fetchValue(
"SELECT ad.id FROM `adresse` AS `ad` JOIN `hubspot_contacts` AS `hs`
ON(ad.id = hs.address_id)
WHERE ad.firma = 1 AND
ad.typ = 'firma' AND
hs.type = 'company' AND
ad.name = :name LIMIT 1",
['name' => $name]
);
}
/**
* @return void
*/
public function cleanup(): void
{
$this->taskMutexService->setMutex('hubspot_pull_contacts', false);
}
/**
* @param array $companies
*
* @throws HubspotException
* @throws EscapingException
*
* @return void
*/
private function importCompany(array $companies): void
{
$settings = $this->configuration->getSettings();
foreach ($companies as $company) {
$companyId = $company['companyId'];
if ($this->gateway->getMappingByHubspotId($companyId, 'company')) {
$this->updateXTContact($companyId, 'company');
continue;
}
$properties = $company['properties'];
$companyData = array_combine(array_keys($properties), array_column($properties, 'value'));
$companyNameTag = $properties['name'];
$contactSourceIds = array_combine(array_keys($properties), array_column($properties, 'sourceId'));
$hsLeadStatus = array_key_exists('hs_lead_status', $companyData) ? $companyData['hs_lead_status'] : '';
$createdAt = $companyNameTag['timestamp'];
$sourceEmail = $companyNameTag['sourceId'];
if (empty($sourceEmail)) {
$sourceEmail = $contactSourceIds['name'];
}
$country = empty($companyData['country']) ? 'DE' : $companyData['country'];
if ($country !== 'DE') {
$countryDb = $this->countryGateway->findByName($country);
if (!empty($countryDb)) {
$country = $countryDb['iso2_code'];
}
}
$address = [
'typ' => 'firma',
'sprache' => 'deutsch',
'name' => $companyData['name'],
'land' => $country,
'email' => $sourceEmail,
'kundenfreigabe' => 1,
'firma' => 1,
'waehrung' => 'EUR',
'internetseite' => empty($companyData['website']) ? '' : $companyData['website'],
'ort' => empty($companyData['city']) ? '' : $companyData['city'],
'plz' => empty($companyData['zip']) ? '' : $companyData['zip'],
'strasse' => empty($companyData['address']) ? '' : $companyData['address'],
];
try {
$leadFields = $this->configuration->matchSelectedAddressFreeField();
$lrField = $leadFields['hubspot_lr_field'];
$lsField = $leadFields['hubspot_ls_field'];
$address[$lsField] = $hsLeadStatus;
$address[$lrField] = empty($companyData['lifecyclestage']) ? '' : $companyData['lifecyclestage'];
} catch (HubspotException $exception) {
$this->eventService->add($exception->getMessage());
}
$numberOfEmployeesField = $this->configuration->tryGetConfiguration('hubspot_numberofemployees_field');
if (!empty($numberOfEmployeesField)) {
$fieldName = str_replace('adresse', '', $numberOfEmployeesField);
$numberOfEmployees = empty($companyData['numberofemployees']) ? 0 : $companyData['numberofemployees'];
$address[$fieldName] = $numberOfEmployees;
}
$defaultCustomFields = array_key_exists('hubspot_address_free_fields', $settings) ?
$settings['hubspot_address_free_fields'] : [];
if (!empty($defaultCustomFields)) {
foreach ($defaultCustomFields as $defaultCustomField => $systemField) {
$fieldName = str_replace('adresse', '', $systemField);
$fieldValue = empty($companyData[$defaultCustomField]) ? '' : $companyData[$defaultCustomField];
$address[$fieldName] = $fieldValue;
}
}
if (!empty($createdAt) &&
array_key_exists('hs_sync_addresses_from', $settings) &&
!empty($settings['hs_sync_addresses_from'])
) {
$addedTime = $createdAt / 1000;
if ($addedTime < $settings['hs_sync_addresses_from']) {
continue;
}
}
// Status
if (array_key_exists('hs_sync_address_status', $settings) &&
!empty($settings['hs_sync_address_status']) &&
$hsLeadStatus !== $settings['hs_sync_address_status']
) {
continue;
}
$hubspotOwnerId = (int)array_key_exists(
'hubspot_owner_id',
$companyData
) ? $companyData['hubspot_owner_id'] : 0;
if ($hubspotOwnerId !== 0) {
$staffId = $this->manageSaleStaffPerson($companyId, $hubspotOwnerId);
if ($staffId !== 0) {
$address['vertrieb'] = $staffId;
}
}
$this->addXTContact($companyId, 'company', $address);
}
}
/**
* @param string $type
* @param array $options
*
* @throws EscapingException
* @throws HubspotConfigurationServiceException
* @throws HubspotException
* @throws MetaException
* @throws JsonException
*
* @return array
*/
protected function pull($type = 'recently_updated', $options = []): array
{
$response = in_array($type, ['company', 'recent_companies']) ?
$this->contactService->pullCompanies($type, $options) :
$this->contactService->pullContacts($type, $options);
if ($response->getStatusCode() !== 200) {
return [];
}
$data = $response->getJson();
$offSet = array_key_exists('vid-offset', $data) ? $data['vid-offset'] : 0;
$singleOffset = array_key_exists('offset', $data) ? $data['offset'] : -1;
$hasMore = array_key_exists('has-more', $data) ? $data['has-more'] : false;
$timeOffset = array_key_exists('time-offset', $data) ? $data['time-offset'] : 0;
if (array_key_exists('companies', $data) || $type === 'recent_companies') {
$companyResponse = $type !== 'recent_companies' ? $data['companies'] : $data['results'];
$this->importCompany($companyResponse);
}
if (array_key_exists('contacts', $data)) {
$settings = $this->configuration->getSettings();
$contacts = $data['contacts'];
if (count($contacts) > 0) {
foreach ($contacts as $contact) {
$contactId = $contact['vid'];
$properties = $contact['properties'];
$createdAt = $contact['addedAt'];
$contactData = array_combine(array_keys($properties), array_column($properties, 'value'));
$hsLeadStatus = array_key_exists('hs_lead_status', $contactData) ?
$contactData['hs_lead_status'] : '';
$email = '';
$identityProfile = $contact['identity-profiles'];
if (!empty($identityProfile)) {
$identities = array_column($identityProfile, 'identities');
foreach ($identities as $identity) {
foreach ($identity as $item) {
if ($item['type'] === 'EMAIL') {
$email = $item['value'];
break;
}
}
}
}
$contactCompany = array_key_exists('company', $contactData) ? $contactData['company'] : '';
if (!empty($contactCompany) && ($addressId = $this->getAddressIdByCompanyName(
$contactCompany
))) {
$contactPerson = [
'type' => 'herr',
'name' => sprintf(
'%s %s',
empty($contactData['firstname']) ? 'Hubspot - ' : $contactData['firstname'],
empty($contactData['lastname']) ? '' : $contactData['lastname']
),
'adresse' => $addressId,
'email' => $email,
'land' => 'DE',
'phone' => $contactData['phone'],
];
if ($this->contactPersonExists($email, $addressId) === true) {
continue;
}
$this->addContactPerson($contactPerson, $contactId);
continue;
}
if ($this->gateway->getMappingByHubspotId($contactId)) {
// UPDATE
if ($type === 'recently_updated') {
$this->updateXTContact($contactId);
}
continue;
}
if (!empty($createdAt) &&
array_key_exists('hs_sync_addresses_from', $settings) &&
!empty($settings['hs_sync_addresses_from'])
) {
$addedTime = $createdAt / 1000;
if ($addedTime < $settings['hs_sync_addresses_from']) {
continue;
}
}
// Status
if (array_key_exists('hs_sync_address_status', $settings) &&
!empty($settings['hs_sync_address_status']) &&
$hsLeadStatus !== $settings['hs_sync_address_status']
) {
continue;
}
$this->addXTContact($contactId);
}
}
}
if ($timeOffset === 0) {
$timeOffset = time() * 1000;
}
$remainingData = ['vidOffset' => $offSet, 'timeOffset' => $timeOffset];
if ($singleOffset !== -1) {
$remainingData['offset'] = $singleOffset;
}
$this->meta->setName($type)->save($remainingData);
$remainingData['has_more'] = $hasMore;
return $remainingData;
}
/**
* @param int $contactId
* @param string $type
* @param array $contact
*
* @throws HubspotException
* @throws EscapingException
*
* @return void
*/
private function addXTContact(int $contactId = 0, string $type = 'address', array $contact = []): void
{
$addressContact = $contact;
if (empty($contact)) {
$addressContact = $this->configuration->formatAddressByResponse(
$this->contactService->getContactById($contactId)
);
}
if (empty($addressContact)) {
return;
}
$hubspotOwnerId = (int)$addressContact['hubspot_owner_id'];
unset($addressContact['hubspot_owner_id']);
if ($hubspotOwnerId !== 0) {
$staffId = $this->manageSaleStaffPerson($contactId, $hubspotOwnerId);
if ($staffId !== 0) {
$addressContact['vertrieb'] = $staffId;
}
}
$paramValues = array_map(
function ($value) {
if (empty($value)) {
return '\'\'';
}
return is_string($value) ? $this->db->escapeString($value) : $value;
},
array_values($addressContact)
);
$placeHolders = implode(',', array_fill(0, count($paramValues), '%s'));
$sql = 'INSERT INTO adresse(' . implode(',', array_keys($addressContact)) . ')
VALUES(' . vsprintf($placeHolders, $paramValues) . ')';
$this->db->perform($sql);
if ($addressId = $this->db->lastInsertId()) {
$this->db->perform(
'INSERT INTO `hubspot_contacts` (`hs_contact_id`, `created_at`, `address_id`, `type`)
VALUES (:id, NOW(), :aid, :type)',
['id' => $contactId, 'aid' => $addressId, 'type' => $type]
);
$eventItem = !empty($addressContact['email']) ? $addressContact['email'] : $addressContact['name'];
$eventMsg = sprintf(
'Neuen Kontakt (<a href="/index.php?module=adresse&action=edit&id=%d">%s</a>) vom Hubspot hinzugef&uuml;gt ins Xentral importiert.',
$addressId,
$eventItem
);
$this->eventService->add($eventMsg);
// ADD to group
$this->configuration->addContactToGroup($addressId);
// get companies contacts
if ($type === 'company') {
$this->importCompanyContactPersons($contactId, $addressId);
}
}
}
/**
* @param int $hubspotContactId
* @param string|null $type
*
* @throws HubspotException
*
* @return void
*/
private function updateXTContact(int $hubspotContactId = 0, ?string $type = 'address'): void
{
$remoteResponse = $type === 'company' ? $this->contactService->getCompanyById($hubspotContactId) :
$this->contactService->getContactById($hubspotContactId);
if ($remoteResponse->getStatusCode() !== 200) {
return;
}
try {
$xtContact = $type === 'company' ? $this->configuration->formatCompanyByResponse($remoteResponse) :
$this->configuration->formatAddressByResponse($remoteResponse);
} catch (HubspotException $exception) {
$this->eventService->add($exception->getMessage());
return;
}
$excludeVars = ['lead', 'typ', 'sprache', 'waehrung', 'kundenfreigabe'];
foreach ($excludeVars as $excludeVar) {
if (array_key_exists($excludeVar, $xtContact)) {
unset($xtContact[$excludeVar]);
}
}
$hubspotOwnerId = (int)$xtContact['hubspot_owner_id'];
unset($xtContact['hubspot_owner_id']);
if ($hubspotOwnerId !== 0) {
$staffId = $this->manageSaleStaffPerson($hubspotContactId, $hubspotOwnerId);
if ($staffId !== 0) {
$xtContact['vertrieb'] = $staffId;
}
}
$hHSContact = $this->gateway->getMappingByHubspotId($hubspotContactId, $type);
if (empty($hHSContact)) {
return;
}
$asPlaceHolders = array_map(
static function ($val) {
return vsprintf('%s=:%s', [$val, $val]);
},
array_keys($xtContact)
);
$placeHolders = implode(',', $asPlaceHolders);
$affected = $this->db->fetchAffected(
'UPDATE adresse SET ' . $placeHolders . ' WHERE id=' . $hHSContact['address_id'],
$xtContact
);
$eventItem = !empty($xtContact['email']) ? $xtContact['email'] : $xtContact['name'];
if ($affected > 0) {
$eventMsg = sprintf(
'Kontakt (<a href="/index.php?module=adresse&action=edit&id=%d">%s</a>) vom Hubspot ge&auml;ndert und ins Xentral importiert',
$hHSContact['address_id'],
$eventItem
);
$this->eventService->add($eventMsg);
}
if ($type === 'company') {
$this->importCompanyContactPersons($hubspotContactId, $hHSContact['address_id']);
}
}
/**
* @param ArrayObject $args
*
* @throws EscapingException
* @throws HubspotConfigurationServiceException
* @throws HubspotException
* @throws JsonException
* @throws MetaException
*
* @return void
*/
public function beforeScheduleAction(ArrayObject $args): void
{
if (empty($this->configuration->tryGetConfiguration(HubspotConfigurationService::HUBSPOT_SALT_CONF_NAME))) {
return;
}
try {
$leadsFields = $this->configuration->matchSelectedAddressFreeField();
} catch (HubspotException $exception) {
return;
}
if (empty($leadsFields)) {
return;
}
$this->executeForCompany();
}
/**
* @param array $contactPerson
* @param int $hubspotContactPersonId
*
* @throws HubspotConfigurationServiceException
*
* @return void
*/
private function addContactPerson(array $contactPerson, int $hubspotContactPersonId = 0): void
{
$this->db->perform(
'INSERT INTO `ansprechpartner` (
`typ`,
`name`,
`adresse`,
`email`,
`land`,
`logdatei`,
`telefon`
)
VALUES (:type, :name, :adresse, :email, :land, NOW(), :phone)',
$contactPerson
);
$companyContactPersonId = $this->db->lastInsertId();
if (empty($companyContactPersonId) || empty($hubspotContactPersonId)) {
return;
}
$contactExists = $this->gateway->hubspotContactExists($hubspotContactPersonId, ['address', 'person']);
if ($contactExists === true) {
$sql = 'UPDATE `hubspot_contacts` SET `type` = :type, `address_id` = :aid WHERE `hs_contact_id` = :id';
$this->db->perform(
$sql,
[
'id' => $hubspotContactPersonId,
'aid' => $companyContactPersonId,
'type' => 'person',
]
);
} else {
$this->db->perform(
'INSERT INTO `hubspot_contacts` (`hs_contact_id`, `created_at`, `address_id`, `type`)
VALUES (:id, NOW(), :aid, :type)',
['id' => $hubspotContactPersonId, 'aid' => $companyContactPersonId, 'type' => 'person']
);
}
if ($this->isContactPersonInHubspotGroup($companyContactPersonId) === false) {
$this->addContactPersonToHubspotGroup($companyContactPersonId);
}
}
/**
* @param int $contactPersonId
*
* @throws HubspotConfigurationServiceException
*
* @return void
*/
private function addContactPersonToHubspotGroup(int $contactPersonId): void
{
$defaultSettings = $this->configuration->getSettings();
$contactGrpId = array_key_exists('hs_contact_grp', $defaultSettings) ? $defaultSettings['hs_contact_grp'] : 0;
if (empty($contactGrpId)) {
return;
}
$sql = 'INSERT INTO `ansprechpartner_gruppen` (`ansprechpartner`, `gruppe`, `aktiv`) VALUES (:id, :group, 1)';
$this->db->perform($sql, ['id' => $contactPersonId, 'group' => $contactGrpId]);
}
/**
* @param int $contactPersonId
*
* @throws HubspotConfigurationServiceException
*
* @return bool
*/
private function isContactPersonInHubspotGroup(int $contactPersonId): bool
{
$defaultSettings = $this->configuration->getSettings();
$contactGrpId = array_key_exists('hs_contact_grp', $defaultSettings) ? $defaultSettings['hs_contact_grp'] : 0;
if (empty($contactGrpId)) {
return true;
}
$sql = 'SELECT `id` FROM `ansprechpartner_gruppen` WHERE `ansprechpartner` = :id AND `gruppe` = :group';
$result = $this->db->fetchValue($sql, ['id' => $contactPersonId, 'group' => $contactGrpId]);
return !empty($result);
}
/**
* @param int $internalContactPersonId
*
* @return void
*/
public function mapPersonToCompany(int $internalContactPersonId): void
{
$mappingData = $this->gateway->getHubspotMappingByPersonId($internalContactPersonId);
if (empty($mappingData)) {
return;
}
if (empty($mappingData['company_id']) || empty($mappingData['contact_id'])) {
return;
}
$this->contactService->addContactToCompany($mappingData['company_id'], $mappingData['contact_id']);
}
/**
* @param int $companyId
* @param int $addressId
*
* @throws HubspotConfigurationServiceException
*
* @return void
*/
private function importCompanyContactPersons(int $companyId, int $addressId): void
{
$contactPersonsResponse = $this->contactService->getCompanyContacts($companyId);
if ($contactPersonsResponse->getStatusCode() !== 200) {
return;
}
$contactPersonsData = $contactPersonsResponse->getJson();
if (empty($contactPersonsData['contacts'])) {
return;
}
$contactPersons = $contactPersonsData['contacts'];
foreach ($contactPersons as $contactPerson) {
$email = '';
$identities = $contactPerson['identities'];
$contactPersonVid = array_column($identities, 'vid');
$rawContact = [];
$contactPersonId = 0;
if (!empty($contactPersonVid)) {
$contactPersonId = $contactPersonVid[0];
$hubspotContactResponse = $this->contactService->getContactById($contactPersonId);
if ($hubspotContactResponse->getStatusCode() !== 200) {
continue;
}
$contactJs = $hubspotContactResponse->getJson();
$properties = $contactJs['properties'];
$rawContact = array_combine(
array_keys($properties),
array_column($properties, 'value')
);
$email = array_key_exists('email', $rawContact) ? $rawContact['email'] : '';
}
if (empty($rawContact)) {
$contactPersonIdentity = array_column($identities, 'identity');
foreach ($contactPersonIdentity as $identity) {
foreach ($identity as $item) {
if ($item['type'] === 'EMAIL') {
$email = $item['value'];
break;
}
}
}
$properties = $contactPerson['properties'];
$rawContact = array_combine(
array_column($properties, 'name'),
array_column($properties, 'value')
);
}
$contactPersonForDB = [
'type' => 'herr',
'name' => sprintf(
'%s %s',
empty($rawContact['firstname']) ? 'Hubspot - ' : $rawContact['firstname'],
empty($rawContact['lastname']) ? '' : $rawContact['lastname']
),
'adresse' => $addressId,
'email' => $email,
'land' => 'DE',
'phone' => empty($rawContact['phone']) ? '' : $rawContact['phone'],
];
if ($this->contactPersonExists($email, $addressId) === true) {
continue;
}
$this->addContactPerson($contactPersonForDB, $contactPersonId);
}
}
public function afterScheduleAction(ArrayObject $args): void
{
// TODO: Implement afterSchedule() method.
}
/**
* @param int $companyId
* @param int $hubspotOwnerId
*
* @throws HubspotException
*
* @return int
*/
private function manageSaleStaffPerson(int $companyId, int $hubspotOwnerId): int
{
if ($companyId === 0 || $hubspotOwnerId === 0) {
return 0;
}
$addressResponse = $this->contactService->getHubspotOwner($hubspotOwnerId);
$address = $addressResponse->getJson();
if (empty($address)) {
return 0;
}
$email = $address['email'];
$sql = 'SELECT id FROM `adresse` WHERE `typ` != :type AND `email` = :email LIMIT 1';
$addressId = $this->db->fetchValue($sql, ['type' => 'firma', 'email' => $email]);
$staffExists = $this->gateway->hubspotSaleStaffExists($companyId);
if ($staffExists === true) {
$sql = 'UPDATE `hubspot_contacts` SET `address_id` = :aid WHERE `hs_contact_id` = :id AND `data` = :company';
$this->db->perform(
$sql,
[
'id' => $hubspotOwnerId,
'aid' => $addressId,
'company' => (string)$companyId,
]
);
} elseif ($addressId !== 0) {
$this->db->perform(
'INSERT INTO `hubspot_contacts` (`hs_contact_id`, `created_at`, `address_id`, `type`, `data`)
VALUES (:id, NOW(), :aid, :type, :company)',
['id' => $hubspotOwnerId, 'aid' => $addressId, 'type' => 'sale_staff', 'company' => (string)$companyId]
);
}
return $addressId;
}
}
@@ -0,0 +1,317 @@
<?php
namespace Xentral\Modules\Hubspot\Scheduler;
use ArrayObject;
use Exception;
use Xentral\Components\Database\Database;
use Xentral\Modules\Hubspot\Exception\HubspotDealGatewayNotFoundException;
use Xentral\Modules\Hubspot\Exception\HubspotException;
use Xentral\Modules\Hubspot\HubspotEventService;
use Xentral\Modules\Hubspot\HubspotHttpResponseService as Response;
use Xentral\Modules\Hubspot\HubspotConfigurationService;
use Xentral\Modules\Hubspot\HubspotDealGateway;
use Xentral\Modules\Hubspot\HubspotDealService;
use Xentral\Modules\Hubspot\HubspotMetaService;
use Xentral\Modules\Hubspot\HubspotContactGateway;
use Xentral\Modules\SubscriptionCycle\Scheduler\TaskMutexServiceInterface;
final class HubspotPullDealsTask implements HubspotSchedulerTaskInterface
{
/** @var Database $db */
private $db;
/** @var HubspotMetaService $meta */
private $meta;
/** @var HubspotDealGateway $gateway */
private $gateway;
/** @var HubspotDealService $dealService */
private $dealService;
/** @var HubspotConfigurationService $configuration */
private $configuration;
/** @var HubspotEventService $eventService */
private $eventService;
/** @var HubspotContactGateway $contactGateway */
private $contactGateway;
/** @var TaskMutexServiceInterface $taskMutexService */
private $taskMutexService;
/**
* @param HubspotDealService $dealService
* @param Database $db
* @param HubspotMetaService $metaService
* @param HubspotDealGateway $gateway
* @param HubspotConfigurationService $configuration
* @param HubspotEventService $eventService
* @param HubspotContactGateway $contactGateway
* @param TaskMutexServiceInterface $taskMutexService
*/
public function __construct(
HubspotDealService $dealService,
Database $db,
HubspotMetaService $metaService,
HubspotDealGateway $gateway,
HubspotConfigurationService $configuration,
HubspotEventService $eventService,
HubspotContactGateway $contactGateway,
TaskMutexServiceInterface $taskMutexService
) {
$this->db = $db;
$this->dealService = $dealService;
$this->meta = $metaService;
$this->gateway = $gateway;
$this->configuration = $configuration;
$this->eventService = $eventService;
$this->contactGateway = $contactGateway;
$this->taskMutexService = $taskMutexService;
}
/**
* @param array $option
*
* @param null|string $type
*
* @param bool $recursiveMode
*
* @throws Exception
* @return void
*/
public function execute($option = [], $type = null, $recursiveMode = false): void
{
if ($recursiveMode === false) {
if ($this->taskMutexService->isTaskInstanceRunning('hubspot_pull_deals')) {
return;
}
$this->taskMutexService->setMutex('hubspot_pull_deals');
}
$settings = $this->configuration->getSettings();
if ($settings['hs_sync_deals'] !== true) {
return;
}
$type = $type ?? 'all_deals';
if ($type !== 'recently_updated_deals' && empty($recursiveMode) &&
$this->db->fetchValue('SELECT COUNT(id) FROM hubspot_deals WHERE hidden=0') > 0) {
$type = 'recently_created_deals';
}
$ret = $this->pull($type, $option);
if (array_key_exists('has_more', $ret) && $ret['has_more'] === true) {
$option = ['offset' => $ret['deal_offset']];
$this->execute($option, $type, true);
}
if ($ret['has_more'] === false && !in_array($type, ['all_deals', 'recently_updated_deals'])) {
$this->execute([], 'recently_updated_deals');
}
}
/**
* @return void
*/
public function cleanup()
{
$this->taskMutexService->setMutex('hubspot_pull_deals', false);
}
/**
* @param string $type
* @param array $options
*
* @throws Exception
* @return array
*/
protected function pull($type = 'recently_created_deals', $options = [])
{
/** @var Response $response */
$response = $this->dealService->pullDeals($type, $options);
if ($response->getStatusCode() === 200) {
$data = $response->getJson();
$offSet = array_key_exists('offset', $data) ? $data['offset'] : 0;
$since = array_key_exists('since', $data) ? $data['since'] : 0;
$hasMore = array_key_exists('hasMore', $data) ? $data['hasMore'] : false;
if (array_key_exists('deals', $data) || array_key_exists('results', $data)) {
$deals = array_key_exists('deals', $data) ? $data['deals'] : $data['results'];
if (count($deals) > 0) {
foreach ($deals as $deal) {
$dealId = $deal['dealId'];
$addressId = 0;
$associations = array_key_exists('associations', $deal) ? $deal['associations'] : [];
if (!empty($associations)) {
$dealContactIds = $associations['associatedVids'];
$companyIds = $associations['associatedCompanyIds'];
if (!empty($companyIds)) {
$dealContactIds = $companyIds;
}
if (!empty($dealContactIds)) {
$dealContactId = $dealContactIds[0];
$hsContact = $this->contactGateway->getMappingByHubspotId($dealContactId, null);
if (!empty($hsContact)) {
$addressId = $hsContact['address_id'];
}
}
}
if ($deal['isDeleted'] === true) {
// @todo delete from xentral
continue;
}
if ($mapping = $this->gateway->getByHubspotId($dealId)) {
// UPDATE
if (($type === 'recently_updated_deals')) {
$this->updateXTDeal($dealId, $mapping['wiedervorlage_id'], $addressId);
//$updated++;
}
continue;
}
if ($pipelineId = $this->addXTDeal($dealId, $addressId)) {
$this->db->perform(
'INSERT INTO `hubspot_deals` (`hs_deal_id`, `created_at`, `wiedervorlage_id`)
VALUES (:id,NOW(), :pipelineId)',
['id' => (int)$dealId, 'pipelineId' => $pipelineId]
);
}
}
}
}
if ($since === 0) {
$since = time() * 1000;
}
$this->meta->setName($type)->save(['offset' => $offSet, 'since' => $since]);
return ['deal_offset' => $offSet, 'has_more' => $hasMore];
}
return [];
}
public function beforeScheduleAction(ArrayObject $data)
{
if (empty($this->configuration->tryGetConfiguration(HubspotConfigurationService::HUBSPOT_SALT_CONF_NAME))) {
return;
}
try {
$leadsFields = $this->configuration->matchSelectedAddressFreeField();
} catch (HubspotException $exception) {
return;
}
if (empty($leadsFields)) {
return;
}
}
/**
* @param ArrayObject $data
*/
public function afterScheduleAction(ArrayObject $data)
{
// TODO: Implement afterSchedule() method.
}
/**
* @param $dealId
* @param int $addressId
*
* @throws HubspotException
* @throws HubspotDealGatewayNotFoundException
*
* @throws Exception
* @return int
*/
private function addXTDeal($dealId, $addressId = 0): int
{
if ($xtDeal = $this->configuration->formatDealByResponse($this->dealService->getDealById($dealId))) {
$xtDeal['adr'] = $addressId;
$this->db->perform(
'INSERT INTO `wiedervorlage` (
`bezeichnung`,
`datum_angelegt`,
`zeit_angelegt`,
`datum_erinnerung`,
`zeit_erinnerung`,
`stages`,
`adresse`
)
VALUES(:bezeichnung, :datum_angelegt, :zeit_angelegt, :datum_erinnerung, :zeit_erinnerung, :stages, :adr)',
$xtDeal
);
$latestId = $this->db->lastInsertId();
if ($data = $this->gateway->getMappingStageByResubmissionStageId($xtDeal['stages'])) {
$viewId = $data['wiedervorlage_view_id'];
// update
$eventMsg = sprintf(
'Neues Deal(<a href="/index.php?module=wiedervorlage&action=list&view=%d">%s</a>) vom Hubspot importiert.',
$viewId,
$xtDeal['bezeichnung']
);
$this->eventService->add($eventMsg);
}
return $latestId;
}
return 0;
}
/**
* @param int $dealId
* @param int $wvId
* @param int $addressId
*
* @throws Exception
*/
private function updateXTDeal($dealId, $wvId, $addressId = 0)
{
if ($xtDeal = $this->configuration->formatDealByResponse($this->dealService->getDealById($dealId))) {
$xtDeal['adr'] = $addressId;
$affected = $this->db->fetchAffected(
'UPDATE `wiedervorlage`
SET `bezeichnung` = :bezeichnung,
`datum_angelegt` = :datum_angelegt,
`zeit_angelegt` = :zeit_angelegt,
`datum_erinnerung` = :datum_erinnerung,
`zeit_erinnerung` = :zeit_erinnerung,
`stages` = :stages,
`adresse` = :adr
WHERE `id` =' . $wvId,
$xtDeal
);
if ($affected > 0) {
$data = $this->gateway->getMappingStageByResubmissionStageId($xtDeal['stages']);
if (empty($data)) {
return;
}
$viewId = $data['wiedervorlage_view_id'];
// update
$eventMsg = sprintf(
'Ge&auml;ndertes Deal(<a href="/index.php?module=wiedervorlage&action=list&view=%d">%s</a>) vom Hubspot importiert.',
$viewId,
$xtDeal['bezeichnung']
);
$this->eventService->add($eventMsg);
}
}
}
}
@@ -0,0 +1,289 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Hubspot\Scheduler;
use ArrayObject;
use Xentral\Components\Database\Database;
use Xentral\Modules\Hubspot\Exception\HubspotConfigurationServiceException;
use Xentral\Modules\Hubspot\Exception\HubspotException;
use Xentral\Modules\Hubspot\Exception\MetaException;
use Xentral\Modules\Hubspot\HubspotConfigurationService;
use Xentral\Modules\Hubspot\HubspotContactGateway;
use Xentral\Modules\Hubspot\HubspotEngagementService;
use Xentral\Modules\Hubspot\HubspotEventService;
use Xentral\Modules\Hubspot\HubspotMetaService;
use Xentral\Modules\SubscriptionCycle\Scheduler\TaskMutexServiceInterface;
final class HubspotPullEngagementsTask implements HubspotSchedulerTaskInterface
{
/** @var int[] $itemsCount */
private $itemsCount = ['count' => 100];
/** @var Database $db */
private $db;
/** @var HubspotEngagementService $engagementsService */
private $engagementsService;
/** @var HubspotMetaService $meta */
private $meta;
/** @var HubspotConfigurationService $configuration */
private $configuration;
/** @var HubspotEventService $event */
private $event;
/** @var HubspotContactGateway $contactGateway */
private $contactGateway;
/** @var TaskMutexServiceInterface $taskMutexService */
private $taskMutexService;
/**
* @param Database $db
* @param HubspotEngagementService $engagementService
* @param HubspotMetaService $metaService
* @param HubspotConfigurationService $configuration
* @param HubspotEventService $eventService
* @param HubspotContactGateway $contactGateway
* @param TaskMutexServiceInterface $taskMutexService
*/
public function __construct(
Database $db,
HubspotEngagementService $engagementService,
HubspotMetaService $metaService,
HubspotConfigurationService $configuration,
HubspotEventService $eventService,
HubspotContactGateway $contactGateway,
TaskMutexServiceInterface $taskMutexService
) {
$this->db = $db;
$this->engagementsService = $engagementService;
$this->configuration = $configuration;
$this->meta = $metaService;
$this->event = $eventService;
$this->contactGateway = $contactGateway;
$this->taskMutexService = $taskMutexService;
}
/**
* @throws HubspotException
*
* @return void
*/
public function execute(): void
{
if ($this->taskMutexService->isTaskInstanceRunning('hubspot_pull_engagements')) {
return;
}
$this->taskMutexService->setMutex('hubspot_pull_engagements');
try {
$settings = $this->configuration->getSettings();
} catch (HubspotConfigurationServiceException $e) {
return;
}
if (empty($settings['hs_sync_engagements'])) {
return;
}
$this->recursiveExecute();
}
/**
* @param array $option
*
* @throws HubspotException
*
* @return void
*/
private function recursiveExecute(array $option = []): void
{
$remainingData = $this->pull($option);
if (!empty($remainingData['has_more'])) {
unset($remainingData['has_more']);
$this->recursiveExecute($remainingData);
}
}
/**
* @param array $options
*
* @throws HubspotException
*
* @return array|null
*/
private function pull(array $options = []): ?array
{
$options = array_merge($options, $this->itemsCount);
$metaInfo = $this->meta->setName('recent_engagements')->get();
$requestData = [];
if (array_key_exists('since', $metaInfo)) {
$requestData['since'] = $metaInfo['since'];
}
if (array_key_exists('offset', $metaInfo)) {
$requestData['offset'] = $metaInfo['offset'];
}
$options = array_merge($options, $requestData);
$response = $this->engagementsService->getRecentEngagements($options);
if ($response->getStatusCode() !== 200) {
return null;
}
$data = $response->getJson();
$offSet = array_key_exists('offset', $data) ? $data['offset'] : 0;
$hasMore = array_key_exists('hasMore', $data) ? $data['hasMore'] : false;
$total = array_key_exists('total', $data) ? $data['total'] : 0;
$since = array_key_exists('since', $data) ? $data['since'] : time() * 1000;
if ($total === 0 || !array_key_exists('results', $data)) {
return null;
}
$engagements = $data['results'];
foreach ($engagements as $engagement) {
$engagementData = $engagement['engagement'];
// ONLY TYPE NOTE IS CURRENTLY ALLOWED
if ($engagementData['type'] !== 'NOTE') {
continue;
}
$engagementId = $engagementData['id'];
$engagementAssociations = $engagement['associations'];
$metadata = $engagement['metadata'];
$body = $metadata['body'];
$companyIds = null;
$contactIds = null;
if ($this->engagementExists($engagementId)) {
continue;
}
foreach ($engagementAssociations as $intended => $values) {
if (!is_string($intended)) {
continue;
}
if ($intended === 'companyIds') {
$companyIds = $values;
}
if ($intended === 'contactIds') {
$contactIds = $values;
}
}
if (empty($companyIds) && empty($contactIds)) {
continue;
}
$intendedIds = array_merge($companyIds, $contactIds);
foreach ($intendedIds as $intendedId) {
$noteId = $this->addNote($intendedId, $body);
if (empty($noteId)) {
continue;
}
$mapping = $this->contactGateway->getMappingByHubspotId($intendedId, null);
if (empty($mapping)) {
continue;
}
$addressId = $mapping['address_id'];
$this->db->perform(
'INSERT INTO `hubspot_contacts` (`hs_contact_id`, `created_at`, `address_id`, `type`)
VALUES (:id, NOW(), :aid, :type)',
['id' => $engagementId, 'aid' => $noteId, 'type' => 'note']
);
$eventItem = 'Adresse';
$eventMsg = sprintf(
'Neue Notiz vom Hubspot hinzugef&uuml;gt ins Xentral f&uuml;r (<a href="/index.php?module=adresse&action=edit&id=%d">%s</a>) importiert.',
$addressId,
$eventItem
);
$this->event->add($eventMsg);
}
}
$remainingData = ['offset' => $offSet, 'since' => $since];
try {
$this->meta->setName('recent_engagements')->save($remainingData);
} catch (MetaException $e) {
$this->event->add($e->getMessage());
}
$remainingData['has_more'] = $hasMore;
return $remainingData;
}
public function cleanup()
{
$this->taskMutexService->setMutex('hubspot_pull_engagements', false);
}
public function beforeScheduleAction(ArrayObject $args)
{
}
public function afterScheduleAction(ArrayObject $args)
{
}
/**
* @param int $hubspotContactId
* @param string $body
*
* @return int
*/
private function addNote(int $hubspotContactId, string $body): int
{
$mapping = $this->contactGateway->getMappingByHubspotId($hubspotContactId, null);
if (empty($mapping)) {
return 0;
}
$sql = 'INSERT INTO `dokumente` (
`adresse_to`,
`adresse_from`,
`typ`,
`betreff`,
`content`,
`datum`,
`uhrzeit`,
`created`,
`bearbeiter`)
VALUES (:address, 1, :type, :object, :body, :date, :time, NOW(), :editor)';
$this->db->perform(
$sql,
[
'address' => $mapping['address_id'],
'type' => 'notiz',
'object' => 'Hubspot note',
'body' => $body,
'date' => date('Y-m-d'),
'time' => date('H:i:s'),
'editor' => 'HubspotModule',
]
);
return $this->db->lastInsertId();
}
/**
* @param int $engagementId
*
* @return bool
*/
private function engagementExists(int $engagementId) : bool
{
return $this->contactGateway->hubspotContactExists($engagementId, ['note']);
}
}
@@ -0,0 +1,16 @@
<?php
namespace Xentral\Modules\Hubspot\Scheduler;
use ArrayObject;
interface HubspotSchedulerTaskInterface
{
public function execute();
public function cleanup();
public function beforeScheduleAction(ArrayObject $args);
public function afterScheduleAction(ArrayObject $args);
}
@@ -0,0 +1,25 @@
<?php
namespace Xentral\Modules\Hubspot\Validators;
final class ContactPropertyValidator implements ValidatorInterface
{
/** @var string */
private $rules;
public function __construct($rules = 'default')
{
$this->rules = $rules;
}
public function isValid($data = [])
{
// TODO: Implement isValid() method.
}
public function validatorRuleDefault()
{
// TODO: Implement validatorRuleDefault() method.
}
}
@@ -0,0 +1,137 @@
<?php
namespace Xentral\Modules\Hubspot\Validators;
use Xentral\Modules\Hubspot\Exception\HubspotException;
final class ContactValidator implements ValidatorInterface
{
/** @var string */
private $rules;
private $data;
public function __construct($rules = 'default')
{
$this->rules = $rules;
}
/**
* @return array
*/
public function validatorRuleDefault()
{
return [
'email' => [
'rule' => static function ($data) {
return !empty($data['email']) && is_string($data['email']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'Email'),
],
'firstname' => [
'rule' => static function ($data) {
return !empty($data['firstname']) && is_string($data['firstname']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'First name'),
],
'lastname' => [
'rule' => static function ($data) {
return !empty($data['lastname']) && is_string($data['lastname']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'Last name'),
],
'website' => [
'rule' => static function ($data) {
return !empty($data['website']) && is_string($data['website']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'Website'),
],
'company' => [
'rule' => static function ($data) {
return !empty($data['company']) && is_string($data['company']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'company'),
],
'phone' => [
'rule' => static function ($data) {
return !empty($data['phone']) && is_string($data['phone']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'phone'),
],
'address' => [
'rule' => static function ($data) {
return !empty($data['address']) && is_string($data['address']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'Address'),
],
'city' => [
'rule' => static function ($data) {
return !empty($data['city']) && is_string($data['city']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'city'),
],
'state' => [
'rule' => static function ($data) {
return !empty($data['state']) && is_string($data['state']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'state'),
],
'zip' => [
'rule' => static function ($data) {
return !empty($data['zip']) && is_string($data['zip']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'zip'),
],
];
}
/**
* @param array $data
*
* @return bool
*/
public function isValid($data = [])
{
$this->data = $data;
$validatorMethod = 'validatorRule' . ucfirst($this->rules);
if (!method_exists($this, $validatorMethod)) {
throw new HubspotException(sprintf('Validator method %s is missing', $validatorMethod));
}
$rules = $this->{$validatorMethod}();
foreach ($rules as $field => $rule) {
if (array_key_exists('rule', $rule)) {
$validation = call_user_func($rule['rule'], $this->data);
if (!$validation && (!empty($this->data[$field]) || !empty($rule['required']))) {
return false;
}
}
}
return true;
}
public function getData()
{
return array_filter($this->data, static function ($value) {
return $value !== null && trim($value) !== '';
});
}
}
@@ -0,0 +1,108 @@
<?php
namespace Xentral\Modules\Hubspot\Validators;
use Xentral\Modules\Hubspot\Exception\HubspotException;
final class DealValidator implements ValidatorInterface
{
/** @var string */
private $rules;
private $data;
public function __construct($rules = 'default')
{
$this->rules = $rules;
}
public function isValid($data = [])
{
$validatorMethod = 'validatorRule' . ucfirst($this->rules);
if (!method_exists($this, $validatorMethod)) {
throw new HubspotException(sprintf('Validator method %s is missing', $validatorMethod));
}
$this->data = $data;
$rules = $this->{$validatorMethod}();
foreach ($rules as $field => $rule) {
if (array_key_exists('rule', $rule)) {
$validation = call_user_func($rule['rule'], $data);
if (!$validation && (!empty($this->data[$field]) || !empty($rule['required']))) {
return false;
}
}
}
return true;
}
/**
* @return array
*/
public function validatorRuleDefault()
{
return [
'dealname' => [
'rule' => static function ($data) {
return !empty($data['dealname']) && is_string($data['dealname']);
},
'required' => true,
'message' => sprintf('%s should be a non empty String', 'name'),
],
'dealstage' => [
'rule' => static function ($data) {
return !empty($data['dealstage']) && is_string($data['dealstage']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'Deal stage'),
],
'pipeline' => [
'rule' => static function ($data) {
return !empty($data['pipeline']) && is_string($data['pipeline']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'Pipeline'),
],
'hubspot_owner_id' => [
'rule' => static function ($data) {
return !empty($data['hubspot_owner_id']) && is_numeric($data['hubspot_owner_id']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'hubspot_owner_id'),
],
'closedate' => [
'rule' => static function ($data) {
return !empty($data['closedate']) && is_numeric($data['closedate']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'closedate'),
],
'dealtype' => [
'rule' => static function ($data) {
return !empty($data['dealtype']) && is_string($data['dealtype']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'dealtype'),
],
'amount' => [
'rule' => static function ($data) {
return !empty($data['amount']) && is_numeric($data['amount']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'amount'),
],
];
}
public function getData()
{
return array_filter($this->data, static function ($value) {
return $value !== null && trim($value) !== '';
});
}
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Modules\Hubspot\Validators;
interface ValidatorInterface
{
public function isValid($data=[]);
public function validatorRuleDefault();
}
@@ -0,0 +1,12 @@
.hs-visible{
/*display:block;*/
visibility: visible;
}
.hs-invisible{
visibility: hidden;
display: none;
}
#hs-add-more-field {
color: #367FA9;
cursor: pointer;
}
+97
View File
@@ -0,0 +1,97 @@
var HubSpotModule = function ($) {
'use strict';
var me = {
isInitialized: false,
storage: {},
/**
* @return void
*/
init: function () {
if (me.isInitialized === true) {
return;
}
me.registerEvents();
me.isInitialized = true;
},
registerEvents: function () {
$('#sync_hs_xt').on('click', function (event) {
me.syncAccount();
event.preventDefault();
});
$('#hs-add-more-field ').on('click', function () {
me.addMoreCustomField();;
});
$('#no-matching').on('click', function (event) {
$(this).prop('checked', true);
$('#do-matching').prop('checked', false);
me.hideMatchingSetting();
});
$('#do-matching').on('click', function (event) {
$(this).prop('checked', true);
$('#no-matching').prop('checked', false);
me.showMatchingSetting();
});
},
showMatchingSetting: function () {
$('.deal-system').removeClass('hs-invisible').find('select').prop('disabled', false);
},
hideMatchingSetting: function () {
$('.deal-system').addClass('hs-invisible').find('select').prop('disabled', true);
},
/**
* @return {void}
*/
addApiKey: function () {
if (me.isInitialized === false) {
me.init();
}
me.resetAdd();
me.storage.$createItemDialog.dialog('open');
},
/**
* @return {void}
*/
syncAccount: function () {
var $form = $('#hs-configurator-form');
$form.action = 'index.php?module=hubspot&action=apikey';
$form.submit();
},
addMoreCustomField: function () {
var $tableTr = $('tr[id^="field"]:last');
var num = parseInt($tableTr.prop('id').match(/\d+/g), 10) + 1;
var $clone = $tableTr.clone().prop('id', 'field' + num);
$tableTr.after($clone.html(
'<td width="150">Eigenschaft ' + num + ' :</td>' +
'<td><input type="text" name="custom_field[]" class="fd_custom_field" placeholder=\"Feld name\" id="field' + num + '" value=""></td>'));
}
};
return {
init: me.init,
addApiKey: me.addApiKey
};
}(jQuery);
$(function () {
if ($('#sync_hs_xt').length > 0) {
HubSpotModule.init();
}
});