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
+283
View File
@@ -0,0 +1,283 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive;
use ApplicationCore;
use Xentral\Core\DependencyInjection\ContainerInterface;
use Xentral\Modules\Pipedrive\Gateway\PipedriveContactGateway;
use Xentral\Modules\Pipedrive\Gateway\PipedriveDealGateway;
use Xentral\Modules\Pipedrive\Gateway\PipedrivePersonPropertyGateway;
use Xentral\Modules\Pipedrive\RequestQueues\PipedriveRequestQueuesGateway;
use Xentral\Modules\Pipedrive\RequestQueues\PipedriveRequestQueuesService;
use Xentral\Modules\Pipedrive\Scheduler\PipedriveProcessSchedulerTask;
use Xentral\Modules\Pipedrive\Scheduler\PipedrivePullDealsTask;
use Xentral\Modules\Pipedrive\Scheduler\PipedrivePullPersonsTask;
use Xentral\Modules\Pipedrive\Service\PipedriveClientService;
use Xentral\Modules\Pipedrive\Service\PipedriveConfigurationService;
use Xentral\Modules\Pipedrive\Service\PipedriveDealPropertyService;
use Xentral\Modules\Pipedrive\Service\PipedriveDealService;
use Xentral\Modules\Pipedrive\Service\PipedriveEventService;
use Xentral\Modules\Pipedrive\Service\PipedriveHttpClientService;
use Xentral\Modules\Pipedrive\Service\PipedriveMetaReaderService;
use Xentral\Modules\Pipedrive\Service\PipedriveMetaWriterService;
use Xentral\Modules\Pipedrive\Service\PipedrivePersonPropertyService;
use Xentral\Modules\Pipedrive\Service\PipedrivePersonService;
use Xentral\Modules\Pipedrive\Validator\PipedriveDealValidator;
use Xentral\Modules\Pipedrive\Validator\PipedrivePersonValidator;
use Xentral\Modules\Pipedrive\Wrapper\PipedriveAddAddressRoleWrapper;
use Xentral\Modules\Pipedrive\Wrapper\PipedriveResubmissionWrapper;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices(): array
{
return [
'PipedriveConfigurationService' => 'onInitPipedriveConfigurationService',
'PipedrivePersonService' => 'onInitPipedrivePersonService',
'PipedriveClientService' => 'onInitPipedriveClientService',
'PipedriveRequestQueuesGateway' => 'onInitPipedriveRequestQueuesGateway',
'PipedriveRequestQueuesService' => 'onInitPipedriveRequestQueuesService',
'PipedriveContactGateway' => 'onInitPipedriveContactGateway',
'PipedriveDealGateway' => 'onInitPipedriveDealGateway',
'PipedrivePersonPropertyService' => 'onInitPipedrivePersonPropertyService',
'PipedrivePersonPropertyGateway' => 'onInitPipedrivePersonPropertyGateway',
'PipedriveDealPropertyService' => 'onInitPipedriveDealPropertyService',
'PipedriveProcessSchedulerTask' => 'onInitPipedriveProcessSchedulerTask',
'PipedrivePullPersonsTask' => 'onInitPipedrivePullPersonsTask',
'PipedrivePullDealsTask' => 'onInitPipedrivePullDealsTask',
'PipedriveDealService' => 'onInitPipedriveDealService',
'PipedriveEventService' => 'onInitPipedriveEventService',
];
}
/**
* @param ContainerInterface $container
*
* @return PipedriveConfigurationService
*/
public static function onInitPipedriveConfigurationService(
ContainerInterface $container
): PipedriveConfigurationService {
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
$metaTmp = $app->erp->GetTMP() . 'meta' . DIRECTORY_SEPARATOR . 'pipedrive';
return new PipedriveConfigurationService(
$container->get('SystemConfigModule'),
new PipedriveMetaWriterService($metaTmp),
$container->get('PipedrivePersonPropertyGateway'),
$container->get('PipedriveDealGateway'),
new PipedriveMetaReaderService($metaTmp),
new PipedriveAddAddressRoleWrapper($app->erp)
);
}
/**
* @param ContainerInterface $container
*
* @throws Exception\PipedriveHttpClientException
*
* @return PipedriveClientService
*/
public static function onInitPipedriveClientService(ContainerInterface $container): PipedriveClientService
{
return new PipedriveClientService(
new PipedriveHttpClientService($container->get('HttpClientFactory'), 30),
$container->get('PipedriveConfigurationService')
);
}
/**
* @param ContainerInterface $container
*
* @return PipedrivePersonService
*/
public static function onInitPipedrivePersonService(ContainerInterface $container): PipedrivePersonService
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
$metaTmp = $app->erp->GetTMP() . 'meta' . DIRECTORY_SEPARATOR . 'pipedrive';
return new PipedrivePersonService(
$container->get('PipedriveClientService'),
new PipedrivePersonValidator(),
new PipedriveMetaReaderService($metaTmp)
);
}
/**
* @param ContainerInterface $container
*
* @return PipedriveRequestQueuesGateway
*/
public static function onInitPipedriveRequestQueuesGateway(ContainerInterface $container
): PipedriveRequestQueuesGateway {
return new PipedriveRequestQueuesGateway($container->get('Database'));
}
/**
* @param ContainerInterface $container
*
* @return PipedriveRequestQueuesService
*/
public static function onInitPipedriveRequestQueuesService(ContainerInterface $container
): PipedriveRequestQueuesService {
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
return new PipedriveRequestQueuesService(
$container->get('PipedriveRequestQueuesGateway'),
$container->get('Database'),
$app,
$container->get('PipedriveConfigurationService'),
$container->get('PipedriveEventService')
);
}
/**
* @param ContainerInterface $container
*
* @return PipedriveContactGateway
*/
public static function onInitPipedriveContactGateway(ContainerInterface $container): PipedriveContactGateway
{
return new PipedriveContactGateway(
$container->get('Database'), $container->get('PipedriveConfigurationService')
);
}
/**
* @param ContainerInterface $container
*
* @return PipedriveDealGateway
*/
public static function onInitPipedriveDealGateway(ContainerInterface $container): PipedriveDealGateway
{
return new PipedriveDealGateway($container->get('Database'));
}
/**
* @param ContainerInterface $container
*
* @return PipedrivePersonPropertyGateway
*/
public static function onInitPipedrivePersonPropertyGateway(ContainerInterface $container
): PipedrivePersonPropertyGateway {
return new PipedrivePersonPropertyGateway($container->get('Database'));
}
/**
* @param ContainerInterface $container
*
* @return PipedrivePersonPropertyService
*/
public static function onInitPipedrivePersonPropertyService(ContainerInterface $container
): PipedrivePersonPropertyService {
return new PipedrivePersonPropertyService($container->get('PipedriveClientService'));
}
/**
* @param ContainerInterface $container
*
* @return PipedrivePullPersonsTask
*/
public static function onInitPipedrivePullPersonsTask(ContainerInterface $container): PipedrivePullPersonsTask
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
$metaTmp = $app->erp->GetTMP() . 'meta' . DIRECTORY_SEPARATOR . 'pipedrive';
return new PipedrivePullPersonsTask(
$container->get('PipedrivePersonService'),
$container->get('Database'),
new PipedriveMetaWriterService($metaTmp),
$container->get('PipedriveContactGateway'),
$container->get('PipedriveConfigurationService'),
$container->get('PipedriveEventService'),
new PipedriveMetaReaderService($metaTmp)
);
}
/**
* @param ContainerInterface $container
*
* @return PipedriveProcessSchedulerTask
*/
public static function onInitPipedriveProcessSchedulerTask(ContainerInterface $container
): PipedriveProcessSchedulerTask {
return new PipedriveProcessSchedulerTask($container->get('PipedriveRequestQueuesService'));
}
/**
* @param ContainerInterface $container
*
* @return PipedriveDealPropertyService
*/
public static function onInitPipedriveDealPropertyService(ContainerInterface $container
): PipedriveDealPropertyService {
return new PipedriveDealPropertyService(
$container->get('PipedriveClientService'),
$container->get('Database'),
$container->get('PipedrivePersonPropertyGateway'),
$container->get('ResubmissionGateway'),
new PipedriveResubmissionWrapper($container->get('Database'))
);
}
/**
* @param ContainerInterface $container
*
* @return PipedrivePullDealsTask
*/
public static function onInitPipedrivePullDealsTask(ContainerInterface $container): PipedrivePullDealsTask
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
$metaTmp = $app->erp->GetTMP() . 'meta' . DIRECTORY_SEPARATOR . 'pipedrive';
return new PipedrivePullDealsTask(
$container->get('PipedriveDealService'),
$container->get('Database'),
new PipedriveMetaWriterService($metaTmp),
$container->get('PipedriveDealGateway'),
$container->get('PipedriveConfigurationService'),
$container->get('PipedriveEventService'),
new PipedriveMetaReaderService($metaTmp),
new PipedriveResubmissionWrapper($container->get('Database'))
);
}
/**
* @param ContainerInterface $container
*
* @return PipedriveDealService
*/
public static function onInitPipedriveDealService(ContainerInterface $container): PipedriveDealService
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
$metaTmp = $app->erp->GetTMP() . 'meta' . DIRECTORY_SEPARATOR . 'pipedrive';
return new PipedriveDealService(
$container->get('PipedriveClientService'),
new PipedriveDealValidator(),
new PipedriveMetaReaderService($metaTmp)
);
}
/**
* @param ContainerInterface $container
*
* @return PipedriveEventService
*/
public static function onInitPipedriveEventService(ContainerInterface $container): PipedriveEventService
{
return new PipedriveEventService($container->get('Database'));
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
final class PipedriveClientException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
final class PipedriveConfigurationException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
class PipedriveContactGatewayNotFoundException extends RuntimeException
implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
class PipedriveDealGatewayNotFoundException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
class PipedriveDealPropertyServiceException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
class PipedriveDealServiceException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
class PipedriveEventException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use Xentral\Core\Exception\ModuleExceptionInterface;
interface PipedriveExceptionInterface extends ModuleExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
final class PipedriveHttpClientException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
class PipedriveMetaException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
class PipedrivePersonPropertyGatewayException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
class PipedrivePersonPropertyServiceException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
class PipedrivePersonServiceException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
class PipedrivePullPersonsTaskException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
final class PipedriveRequestQueuesException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
final class PipedriveSchedulerAdapterBadMethodException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Exception;
use RuntimeException;
class PipedriveValidatorException extends RuntimeException implements PipedriveExceptionInterface
{
}
@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Gateway;
use Xentral\Components\Database\Database;
use Xentral\Modules\Pipedrive\Exception\PipedriveConfigurationException;
use Xentral\Modules\Pipedrive\Service\PipedriveConfigurationService;
final class PipedriveContactGateway
{
/** @var Database $db */
private $db;
/** @var PipedriveConfigurationService $configurationService */
private $configurationService;
/**
* @param Database $db
* @param PipedriveConfigurationService $configurationService
*/
public function __construct(Database $db, PipedriveConfigurationService $configurationService)
{
$this->db = $db;
$this->configurationService = $configurationService;
}
/**
* @param int $pdContactId Pipedrive contact ID
*
* @return array
*/
public function getMappingByPipedriveId(int $pdContactId): array
{
return $this->db->fetchRow(
'SELECT
p.id,
p.created_at,
p.data,
p.address_id
FROM `pipedrive_contacts` AS `p`
WHERE p.hidden = 0 AND p.pd_contact_id = :id',
['id' => $pdContactId]
);
}
/**
* @param int $addressId Xentral Address ID
*
* @return array
*/
public function getMappingByAddressId(int $addressId): array
{
return $this->db->fetchRow(
'SELECT
p.id,
p.created_at,
p.data,
p.pd_contact_id
FROM `pipedrive_contacts` AS `p`
WHERE p.hidden = 0 AND p.address_id = :id',
['id' => $addressId]
);
}
/**
* @param int $addressId
* @param bool $withStatusField
*
* @throws PipedriveConfigurationException
*
* @return array
*/
public function getInternalAddressById(int $addressId, bool $withStatusField = false): array
{
$placeHolder = '';
if ($withStatusField === true) {
// @codeCoverageIgnoreStart
$leadFields = $this->configurationService->matchSelectedAddressFreeField();
$lsField = $leadFields['pipedrive_ls_field'];
$placeHolder = ", a.{$lsField}";
// @codeCoverageIgnoreEnd
}
$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.email %s
FROM `adresse` AS `a`
WHERE a.geloescht = 0 AND a.id = :id';
return $this->db->fetchRow(sprintf($sql, $placeHolder), ['id' => $addressId]);
}
}
@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Gateway;
use Xentral\Components\Database\Database;
final class PipedriveDealGateway
{
/** @var Database $db */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param int $pdDealId
*
* @return array
*/
public function getDealByPipedriveId(int $pdDealId): array
{
return $this->db->fetchRow(
'SELECT
d.id,
d.created_at,
d.data,
d.wiedervorlage_id
FROM `pipedrive_deals` AS `d`
WHERE d.hidden = 0 AND d.pd_deal_id = :id',
['id' => $pdDealId]
);
}
/**
* @param int $resubmissionId
*
* @return array
*/
public function getDealByResubmissionId(int $resubmissionId): array
{
return $this->db->fetchRow(
'SELECT
d.id,
d.created_at,
d.data,
d.wiedervorlage_id,
d.pd_deal_id
FROM `pipedrive_deals` AS `d`
WHERE d.hidden = 0 AND d.wiedervorlage_id = :id',
['id' => $resubmissionId]
);
}
/**
* @param int $stageId
*
* @return array
*/
public function getMappingStageByResubmissionStageId(int $stageId): array
{
return $this->db->fetchRow(
'SELECT
pm.id,
pm.created_at,
pm.wiedervorlage_stage_id,
pm.label,
pm.value,
pm.wiedervorlage_view_id,
pm.type
FROM `pipedrive_mappings` AS `pm`
WHERE pm.wiedervorlage_stage_id=:resubmission_id AND pm.type =:type',
['resubmission_id' => $stageId, 'type' => 'deals']
);
}
}
@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Gateway;
use Xentral\Components\Database\Database;
final class PipedrivePersonPropertyGateway
{
/** @var Database $db */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param string $type
*
* @param bool $isSystem
*
* @return array
*/
public function getLeadsByType(string $type, bool $isSystem = false): array
{
return $this->db->fetchAssoc(
'SELECT
p.id,
p.created_at,
p.label,
p.value,
p.type,
p.wiedervorlage_stage_id
FROM `pipedrive_mappings` AS `p` WHERE p.type = :type AND p.is_system=:system',
['type' => $type, 'system' => (int)$isSystem]
);
}
/**
* @param int $value
* @param string $type
*
* @return array
*/
public function getMappingByValueAndType(int $value, string $type): array
{
return $this->db->fetchRow(
'SELECT
hm.id,
hm.created_at,
hm.wiedervorlage_stage_id,
hm.label,
hm.value,
hm.type
FROM `pipedrive_mappings` AS `hm`
WHERE hm.value =:value AND hm.type = :type',
['value' => $value, 'type' => $type]
);
}
/**
* @param string $dbName
*
* @return array
*/
public function getAddressFreeFields(string $dbName): array
{
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(): array
{
return $this->db->fetchCol(
'SELECT f.wert FROM `firmendaten_werte` AS `f`
WHERE `name` LIKE "adressetabellezusatz%" AND f.wert !="" AND f.wert IS NOT NULL'
);
}
}
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\RequestQueues;
use Xentral\Components\Database\Database;
final class PipedriveRequestQueuesGateway
{
/** @var Database $db */
private $db;
/**
* PipedriveRequestQueuesGateway constructor.
*
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param string $type
*
* @return array
*/
public function getNewRequestsByCallType(string $type = 'pipedrive'): array
{
$sql = 'SELECT
rq.id,
rq.command,
rq.not_before,
rq.amount_attempts,
rq.runner,
rq.check_sum,
rq.on_after_done,
rq.is_looped,
rq.setting_name,
rq.created_at,
rq.modified_at
FROM `pipedrive_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,328 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\RequestQueues;
use ApplicationCore;
use Exception;
use Xentral\Components\Database\Database;
use Xentral\Components\Database\Exception\DatabaseExceptionInterface;
use Xentral\Core\DependencyInjection\Exception\ServiceNotFoundException;
use Xentral\Modules\Pipedrive\Exception\PipedriveConfigurationException;
use Xentral\Modules\Pipedrive\Exception\PipedriveEventException;
use Xentral\Modules\Pipedrive\Exception\PipedriveMetaException;
use Xentral\Modules\Pipedrive\Exception\PipedriveRequestQueuesException;
use Xentral\Modules\Pipedrive\Service\PipedriveConfigurationService;
use Xentral\Modules\Pipedrive\Service\PipedriveEventService;
use Xentral\Modules\Pipedrive\Service\PipedriveServerResponseInterface;
use DateTime;
use RuntimeException;
final class PipedriveRequestQueuesService
{
/** @var int */
private const _WAITING_TIME = 10000000;
/** @var int */
private const _BATCH = 1;
/** @var PipedriveRequestQueuesGateway $gateway */
private $gateway;
/** @var ApplicationCore $app */
private $app;
/** @var Database $db */
private $db;
/** @var array $completedIds */
private $completedIds = [];
/** @var PipedriveConfigurationService $confService */
private $confService;
/** @var PipedriveEventService $eventService */
private $eventService;
/**
* @param PipedriveRequestQueuesGateway $gateway
* @param Database $database
* @param ApplicationCore $app
* @param PipedriveConfigurationService $confService
* @param PipedriveEventService $eventService
*/
public function __construct(
PipedriveRequestQueuesGateway $gateway,
Database $database,
ApplicationCore $app,
PipedriveConfigurationService $confService,
PipedriveEventService $eventService
) {
$this->gateway = $gateway;
$this->app = $app;
$this->db = $database;
$this->confService = $confService;
$this->eventService = $eventService;
}
/**
* @param array $option
*
* @throws PipedriveRequestQueuesException
*
* @return int
*/
public function addRequest(array $option): int
{
$default = [
'check_sum' => '',
'command' => '',
'on_after_done' => '',
'not_before' => 0,
'call_type' => 'pipedrive',
'is_looped' => 0,
'setting_name' => '',
];
$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 (!array_key_exists('runner', $option)) {
throw new PipedriveRequestQueuesException('Runner is missing!');
}
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 prq.id FROM `pipedrive_request_queues` AS `prq`
WHERE prq.deleted=0 AND prq.completed=0 AND prq.check_sum=:check_sum AND prq.runner=:runner
)';
if (empty(
$this->db->fetchValue(
$check,
['runner' => $option['runner'], 'check_sum' => $option['check_sum']]
)
)) {
$add = 'INSERT INTO `pipedrive_request_queues` (`command`, `on_after_done`, `runner`, `not_before`, `check_sum`, `call_type`, `is_looped`, `setting_name`, `created_at` )
VALUES (:command, :on_after_done, :runner, :not_before, :check_sum, :call_type, :is_looped, :setting_name, NOW())';
try {
$this->db->perform($add, $option);
} catch (DatabaseExceptionInterface $exception) {
throw new PipedriveRequestQueuesException($exception->getMessage());
}
return $this->db->lastInsertId();
}
return 0;
}
/**
* @param string|null $callType
*
* @throws PipedriveConfigurationException
* @throws PipedriveEventException
* @throws PipedriveRequestQueuesException
* @throws PipedriveMetaException
* @throws Exception
*
* @return void
*/
public function execute(?string $callType = null): void
{
$callType = $callType ?? 'pipedrive';
$jobs = $this->gateway->getNewRequestsByCallType($callType);
if (!empty($jobs)) {
$batch_loop = self::_BATCH;
$iCount = 0;
$bSkipWait = count($jobs) <= 1;
foreach ($jobs as $job) {
$settings = $this->confService->getSettings();
// HANDLE CAN EXECUTE
if (!empty($job['setting_name']) && array_key_exists(
$job['setting_name'],
$settings
) && $settings[$job['setting_name']] === false) {
// SKIP
continue;
}
// HANDLE is_looped
if (!empty($job['is_looped']) && !empty($job['setting_name'])) {
$settingInterval = sprintf('%s_interval', $job['setting_name']);
if (!array_key_exists($settingInterval, $settings)) {
// SKIP or ERROR?
continue;
}
$interval = $settings[$settingInterval];
$modifiedAt = new DateTime($job['modified_at']);
$iModifiedAt = $modifiedAt->getTimestamp();
if (time() < $iModifiedAt + $interval) {
// SKIP, waiting for next running time
continue;
}
}
if (!empty($job['is_looped'])) {
$this->db->perform(
'UPDATE `pipedrive_request_queues` SET `modified_at` = NOW() WHERE `id` = :id',
['id' => $job['id']]
);
} else {
$this->db->perform(
'UPDATE `pipedrive_request_queues`
SET `amount_attempts` = `amount_attempts` +1 WHERE `id` = :id',
['id' => $job['id']]
);
}
try {
$oClass = $this->app->Container->get($job['runner']);
} catch (ServiceNotFoundException $exception) {
throw new PipedriveRequestQueuesException($exception->getMessage());
}
$hCommand = json_decode($job['command'], true);
if (is_array($hCommand)) {
$xArg = $hCommand['args'];
$sMethod = $hCommand['method'];
try {
/** @var PipedriveServerResponseInterface $xReturn */
$xReturn = call_user_func_array([$oClass, $sMethod], $xArg);
} catch (RuntimeException $exception) {
$this->eventService->add($exception->getMessage());
continue;
}
$isLooped = !empty($job['is_looped']);
if (empty($job['on_after_done'])) {
$this->markJobAsComplete($job['id'], $isLooped);
} else {
$this->onAfterDone($job['id'], $xReturn, $job['on_after_done'], $isLooped);
}
//BATCH PROCESS CHECK - take a nap
if ($bSkipWait === false) {
if ($iCount === $batch_loop) {
$batch_loop += self::_BATCH;
@usleep(self::_WAITING_TIME);
}
echo 'Script always alive... ';
}
} else {
continue;
}
}
}
}
/**
* @param int $id
* @param PipedriveServerResponseInterface|null $response
* @param string|null $onAfter
* @param bool $looped
*
* @throws PipedriveEventException
*
* @return void
*/
private function onAfterDone(
int $id,
?PipedriveServerResponseInterface $response,
?string $onAfter = null,
bool $looped = false
): void {
$hOnAfter = !empty($onAfter) ? json_decode($onAfter, true) : [];
if (!empty($hOnAfter) && array_key_exists('runner', $hOnAfter) && array_key_exists(
'method',
$hOnAfter
) && array_key_exists('args', $hOnAfter)) {
$oClass = $this->app->Container->get($hOnAfter['runner']);
if (!empty($hOnAfter['replace_in_args']) && in_array($response->getStatusCode(), [200, 201], true)) {
$jsonData = $response->getData();
foreach ($hOnAfter['args'] as &$xArg) {
if (is_string($xArg)) {
foreach ($hOnAfter['replace_in_args'] as $replace_with) {
if (!empty($jsonData[$replace_with])) {
$xArg = sprintf($xArg, $jsonData[$replace_with]);
}
}
}
}
}
unset($xArg);
try {
call_user_func_array(
[$oClass, $hOnAfter['method']],
$hOnAfter['args']
);
// ADD EVENT
if (array_key_exists('event', $hOnAfter) && !empty($hOnAfter['event']) && is_string(
$hOnAfter['event']
) &&
in_array($response->getStatusCode(), [200, 201], true)) {
$this->eventService->add($hOnAfter['event']);
}
} catch (RuntimeException $exception) {
$this->eventService->add($exception->getMessage());
}
}
$this->markJobAsComplete($id, $looped);
}
/**
* @param int $id
* @param bool $looped
*
* @return void
*/
private function markJobAsComplete(int $id, bool $looped): void
{
if (is_numeric($id) && $looped === false) {
$this->db->perform('UPDATE `pipedrive_request_queues` SET `completed` = 1 WHERE `id` = :id', ['id' => $id]);
$this->completedIds[] = $id;
}
}
/**
* @return void
*/
public function cleanup(): void
{
$this->db->perform('DELETE FROM `pipedrive_request_queues` WHERE `completed` = 1');
$this->eventService->deleteByInterval();
unset($this->completedIds);
}
}
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Scheduler\Adapter;
use ArrayObject;
use Xentral\Components\Logger\LoggerAwareTrait;
use Xentral\Modules\Pipedrive\Exception\PipedriveSchedulerAdapterBadMethodException;
use Xentral\Modules\Pipedrive\Scheduler\PipedriveSchedulerTaskInterface;
final class PipedriveSchedulerAdapter
{
use LoggerAwareTrait;
/** @var PipedriveSchedulerTaskInterface $schedulerTask */
private $schedulerTask;
/** @var bool $debugMode */
public $debugMode = false;
/**
* PipedriveSchedulerAdapter constructor.
*
* @param PipedriveSchedulerTaskInterface $schedulerTask
*/
public function __construct(PipedriveSchedulerTaskInterface $schedulerTask)
{
$this->schedulerTask = $schedulerTask;
}
/**
* @param $method
* @param $args
*
* @throws PipedriveSchedulerAdapterBadMethodException
*
* @return void
*/
public function __call($method, $args)
{
if (!method_exists($this->schedulerTask, $method)) {
$class = get_class($this->schedulerTask);
throw new PipedriveSchedulerAdapterBadMethodException(
sprintf('Method %s at %s class is missing', $method, $class)
);
}
if (!is_callable([$this->schedulerTask, $method])) {
$class = get_class($this->schedulerTask);
throw new PipedriveSchedulerAdapterBadMethodException(
sprintf('No callable method %s at %s class', $method, $class)
);
}
$this->debug(json_encode(new ArrayObject($args)));
if ($method === 'execute' && empty($args) === true) {
$this->schedulerTask->beforeScheduleAction(new ArrayObject($args));
}
if ($this->debugMode === true) {
$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));
}
}
/**
* @param string $message
*
* @return void
*/
public function debug(string $message): void
{
if ($this->debugMode === false) {
return;
}
$this->logger->debug(date('Y-m-d H:i:s') . '- ' . $message);
}
}
@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Scheduler;
use ArrayObject;
use Xentral\Modules\Pipedrive\Exception\PipedriveConfigurationException;
use Xentral\Modules\Pipedrive\Exception\PipedriveEventException;
use Xentral\Modules\Pipedrive\Exception\PipedriveMetaException;
use Xentral\Modules\Pipedrive\Exception\PipedriveRequestQueuesException;
use Xentral\Modules\Pipedrive\RequestQueues\PipedriveRequestQueuesService;
final class PipedriveProcessSchedulerTask implements PipedriveSchedulerTaskInterface
{
/** @var string */
public const CALL_TYPE = 'pipedrive';
/** @var PipedriveRequestQueuesService $service */
private $service;
/**
* @param PipedriveRequestQueuesService $service
*/
public function __construct(PipedriveRequestQueuesService $service)
{
$this->service = $service;
}
/**
* @throws PipedriveConfigurationException
* @throws PipedriveEventException
* @throws PipedriveMetaException
* @throws PipedriveRequestQueuesException
*
* @return void
*/
public function execute(): void
{
$this->service->execute(self::CALL_TYPE);
}
/**
* @return void
*/
public function cleanup(): void
{
$this->service->cleanup();
}
// @codeCoverageIgnoreStart
/**
* @inheritDoc
*/
public function beforeScheduleAction(ArrayObject $data)
{
// TODO: Implement beforeScheduleAction() method.
}
/**
* @inheritDoc
*/
public function afterScheduleAction(ArrayObject $data)
{
// TODO: Implement afterScheduleAction() method.
}
// @codeCoverageIgnoreEnd
}
@@ -0,0 +1,284 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Scheduler;
use ArrayObject;
use Exception;
use Xentral\Components\Database\Database;
use Xentral\Modules\Pipedrive\Exception\PipedriveConfigurationException;
use Xentral\Modules\Pipedrive\Exception\PipedriveDealServiceException;
use Xentral\Modules\Pipedrive\Exception\PipedriveMetaException;
use Xentral\Modules\Pipedrive\Gateway\PipedriveDealGateway;
use Xentral\Modules\Pipedrive\Service\PipedriveConfigurationService;
use Xentral\Modules\Pipedrive\Service\PipedriveDealService;
use Xentral\Modules\Pipedrive\Service\PipedriveEventService;
use Xentral\Modules\Pipedrive\Service\PipedriveMetaReaderService;
use Xentral\Modules\Pipedrive\Service\PipedriveMetaWriterService;
use Xentral\Modules\Pipedrive\Wrapper\PipedriveResubmissionWrapper;
final class PipedrivePullDealsTask implements PipedriveSchedulerTaskInterface
{
/** @var Database $db */
private $db;
/** @var PipedriveMetaWriterService $metaWrite */
private $metaWrite;
/** @var PipedriveDealGateway $gateway */
private $gateway;
/** @var PipedriveDealService $dealService */
private $dealService;
/** @var PipedriveConfigurationService $configuration */
private $configuration;
/** @var PipedriveEventService $eventService */
private $eventService;
/** @var PipedriveMetaReaderService $metaReaderService */
private $metaReaderService;
/** @var PipedriveResubmissionWrapper $resubmissionWrapper */
private $resubmissionWrapper;
/**
* @param PipedriveDealService $dealService
* @param Database $db
* @param PipedriveMetaWriterService $metaWriterService
* @param PipedriveDealGateway $gateway
* @param PipedriveConfigurationService $configuration
* @param PipedriveEventService $eventService
* @param PipedriveMetaReaderService $metaReaderService
* @param PipedriveResubmissionWrapper $resubmissionWrapper
*/
public function __construct(
PipedriveDealService $dealService,
Database $db,
PipedriveMetaWriterService $metaWriterService,
PipedriveDealGateway $gateway,
PipedriveConfigurationService $configuration,
PipedriveEventService $eventService,
PipedriveMetaReaderService $metaReaderService,
PipedriveResubmissionWrapper $resubmissionWrapper
) {
$this->db = $db;
$this->dealService = $dealService;
$this->metaWrite = $metaWriterService;
$this->gateway = $gateway;
$this->configuration = $configuration;
$this->eventService = $eventService;
$this->metaReaderService = $metaReaderService;
$this->resubmissionWrapper = $resubmissionWrapper;
}
/**
* @param array $option
* @param string|null $type
* @param bool $recursiveMode
*
* @throws PipedriveConfigurationException
* @throws PipedriveDealServiceException
* @throws PipedriveMetaException
*
* @return void
*/
public function execute(array $option = [], ?string $type = null, bool $recursiveMode = false): void
{
$settings = $this->configuration->getSettings();
if ($settings['pd_sync_deals'] !== true) {
return;
}
$type = $type ?? 'pipedrive_recently_updated_deals';
if ($type !== 'pipedrive_recently_updated_deals' && empty($recursiveMode) &&
$this->db->fetchValue('SELECT COUNT(id) FROM `pipedrive_deals` WHERE `hidden` = 0') > 0) {
$type = 'pipedrive_recently_updated_deals';
}
$ret = $this->pull($type, $option);
if (array_key_exists('has_more', $ret) && $ret['has_more'] === true) {
$this->execute($option, $type, true);
}
}
/**
* @return void
*/
public function cleanup(): void
{
// TODO: Implement cleanup() method.
}
/**
* @param string $type
* @param array $options
*
* @throws PipedriveDealServiceException
* @throws PipedriveMetaException
* @throws Exception
*
* @return array
*/
protected function pull(string $type = 'pipedrive_recently_updated_deals', array $options = []): array
{
$response = $this->dealService->pullDeals($type, $options);
if ($response->getStatusCode() !== 200) {
return [];
}
$deals = $response->getData();
$pagination = $response->getPagination();
$metaFile = sprintf('%s.json', $type);
$metaOption = $this->metaReaderService->readFromFile($metaFile);
$hasMore = is_array($pagination) && array_key_exists(
'more_items_in_collection',
$pagination
) ? $pagination['more_items_in_collection'] : false;
$startOffset = 0;
if (empty($metaOption)) {
$timeOffset = '1970-01-01 23:59:59';
$this->metaWrite->save($metaFile, ['timeOffset' => $timeOffset]);
} elseif (array_key_exists('has_more', $options) && $options['has_more'] === true) {
$timeOffset = $options['previous_timeOffset'] ?? '1970-01-01 23:59:59';
$startOffset += 100;
} else {
$timeOffset = $metaOption['timeOffset'];
}
if (is_array($deals) && count($deals) > 0) {
foreach ($deals as $deal) {
$dealId = $deal['id'];
$hDeal = $deal['data'];
if ($hDeal['deleted'] === true) {
// DELETE IT HERE
if ($mapping = $this->gateway->getDealByPipedriveId($dealId)) {
$this->db->perform(
'DELETE FROM `pipedrive_deals` WHERE `pd_deal_id` = :id',
['id' => $dealId]
);
$this->db->perform(
'DELETE FROM `wiedervorlage` WHERE id = :id',
['id' => $mapping['wiedervorlage_id']]
);
}
continue;
}
if ($mapping = $this->gateway->getDealByPipedriveId($dealId)) {
$this->updateXTDeal($hDeal, $mapping['wiedervorlage_id']);
} elseif ($pipelineId = $this->addXTDeal($hDeal)) {
$this->db->perform(
'INSERT INTO `pipedrive_deals` (`pd_deal_id`, `created_at`, `wiedervorlage_id`)
VALUES (:id,NOW(), :pipelineId)',
['id' => (int)$dealId, 'pipelineId' => $pipelineId]
);
}
}
}
if (!empty($metaOption) && !array_key_exists('previous_timeOffset', $metaOption)) {
$this->metaWrite->save($metaFile, ['timeOffset' => gmdate('Y-m-d H:i:s')]);
}
return [
'has_more' => $hasMore,
'previous_timeOffset' => $timeOffset,
'startOffset' => $startOffset,
];
}
/**
* @param ArrayObject $data
*
* @throws PipedriveConfigurationException
*
* @return mixed|void
*/
public function beforeScheduleAction(ArrayObject $data)
{
if (empty($this->configuration->tryGetConfiguration('pipedrive_settings'))) {
return;
}
try {
$leadsFields = $this->configuration->matchSelectedAddressFreeField();
} catch (PipedriveConfigurationException $exception) {
return;
}
if (empty($leadsFields)) {
return;
}
}
/**
* @param ArrayObject $data
*/
public function afterScheduleAction(ArrayObject $data)
{
}
/**
* @param array $deal
*
* @throws Exception
*
* @return int
*/
private function addXTDeal(array $deal): int
{
$internalDeal = $this->configuration->formatDealToInternal($deal);
if (!$internalDeal) {
return 0;
}
$latestId = $this->resubmissionWrapper->addResubmission($internalDeal);
if ($data = $this->gateway->getMappingStageByResubmissionStageId($internalDeal['stages'])) {
$viewId = $data['wiedervorlage_view_id'];
$eventMsg = sprintf(
'Neues Deal (<a href="/index.php?module=wiedervorlage&action=list&view=%d">%s</a>) vom Pipedrive hinzugef&uuml;gt ins Xentral importiert',
$viewId,
$internalDeal['bezeichnung']
);
$this->eventService->add($eventMsg);
}
return $latestId;
}
/**
* @param array $deal
* @param int $wvId
*
* @throws Exception
*
* @return void
*/
private function updateXTDeal(array $deal, int $wvId): void
{
$internalDeal = $this->configuration->formatDealToInternal($deal);
if (!$internalDeal) {
return;
}
$this->resubmissionWrapper->updateResubmission($wvId, $internalDeal);
if ($data = $this->gateway->getMappingStageByResubmissionStageId($internalDeal['stages'])) {
$viewId = $data['wiedervorlage_view_id'];
$eventMsg = sprintf(
'Deal (<a href="/index.php?module=wiedervorlage&action=list&view=%d">%s</a>)
vom Pipedrive ge&auml;ndert und ins Xentral importiert',
$viewId,
$internalDeal['bezeichnung']
);
$this->eventService->add($eventMsg);
}
}
}
@@ -0,0 +1,305 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Scheduler;
use Xentral\Components\Database\Database;
use ArrayObject;
use Xentral\Components\Database\Exception\EscapingException;
use Xentral\Modules\Pipedrive\Exception\PipedriveConfigurationException;
use Xentral\Modules\Pipedrive\Exception\PipedriveEventException;
use Xentral\Modules\Pipedrive\Exception\PipedriveMetaException;
use Xentral\Modules\Pipedrive\Exception\PipedrivePersonServiceException;
use Xentral\Modules\Pipedrive\Gateway\PipedriveContactGateway;
use Xentral\Modules\Pipedrive\Service\PipedriveConfigurationService;
use Xentral\Modules\Pipedrive\Service\PipedriveEventService;
use Xentral\Modules\Pipedrive\Service\PipedriveMetaReaderService;
use Xentral\Modules\Pipedrive\Service\PipedriveMetaWriterService;
use Xentral\Modules\Pipedrive\Service\PipedrivePersonService;
final class PipedrivePullPersonsTask implements PipedriveSchedulerTaskInterface
{
/** @var PipedrivePersonService $contactService */
private $contactService;
/** @var Database $db */
private $db;
/** @var PipedriveMetaWriterService $metaWriterService */
private $metaWriterService;
/** @var PipedriveContactGateway $gateway */
private $gateway;
/** @var PipedriveConfigurationService $configuration */
private $configuration;
/** @var PipedriveEventService $eventService */
private $eventService;
/** @var PipedriveMetaReaderService $metaReaderService */
private $metaReaderService;
/**
* @param PipedrivePersonService $contactService
* @param Database $db
* @param PipedriveMetaWriterService $metaService
* @param PipedriveContactGateway $gateway
* @param PipedriveConfigurationService $configuration
* @param PipedriveEventService $eventService
* @param PipedriveMetaReaderService $metaReaderService
*/
public function __construct(
PipedrivePersonService $contactService,
Database $db,
PipedriveMetaWriterService $metaService,
PipedriveContactGateway $gateway,
PipedriveConfigurationService $configuration,
PipedriveEventService $eventService,
PipedriveMetaReaderService $metaReaderService
) {
$this->db = $db;
$this->contactService = $contactService;
$this->metaWriterService = $metaService;
$this->gateway = $gateway;
$this->configuration = $configuration;
$this->eventService = $eventService;
$this->metaReaderService = $metaReaderService;
}
/**
* @param array $option
* @param string|null $type
* @param bool $recursiveMode
*
* @throws EscapingException
* @throws PipedriveConfigurationException
* @throws PipedriveEventException
* @throws PipedriveMetaException
* @throws PipedrivePersonServiceException
*
* @return void
*/
public function execute(array $option = [], ?string $type = null, bool $recursiveMode = false): void
{
$settings = $this->configuration->getSettings();
if ($settings['pd_sync_addresses'] !== true) {
return;
}
$type = $type ?? 'pipedrive_recently_updated';
if (empty($recursiveMode) && $this->db->fetchValue(
'SELECT COUNT(id) FROM `pipedrive_contacts` WHERE `hidden` = 0'
) > 0) {
$type = $type === 'all' ? 'pipedrive_recently_updated' : $type;
}
$ret = $this->pull($type, $option);
if (!empty($ret['has_more'])) {
$this->execute($option, $type, true);
}
}
/**
* @return void
*/
public function cleanup(): void
{
}
/**
* @param string $type
* @param array $options
*
* @throws EscapingException
* @throws PipedriveConfigurationException
* @throws PipedriveEventException
* @throws PipedriveMetaException
* @throws PipedrivePersonServiceException
*
* @return array
*/
protected function pull(string $type = 'pipedrive_recently_updated', array $options = []): array
{
$response = $this->contactService->pullPersons($type, $options);
if ($response->getStatusCode() !== 200) {
return [];
}
$persons = $response->getData();
$pagination = $response->getPagination();
$metaFile = sprintf('%s.json', $type);
$metaOption = $this->metaReaderService->readFromFile($metaFile);
$hasMore = is_array($pagination) && array_key_exists(
'more_items_in_collection',
$pagination
) ? $pagination['more_items_in_collection'] : false;
$startOffset = 0;
if (empty($metaOption)) {
$timeOffset = '1970-01-01 23:59:59';
$this->metaWriterService->save($metaFile, ['timeOffset' => date('Y-m-d H:i:s')]);
} elseif (array_key_exists('has_more', $options) && $options['has_more'] === true) {
$timeOffset = $options['previous_timeOffset'] ?? '1970-01-01 23:59:59';
$startOffset += 100;
} else {
$timeOffset = $metaOption['timeOffset'];
}
if (is_array($persons) && count($persons) > 0) {
foreach ($persons as $contact) {
$contactData = $contact['data'];
$contactId = $contact['id'];
if ($this->gateway->getMappingByPipedriveId($contactId)) {
// UPDATE
$this->updateXTContact($contactData);
} else {
$this->addXTContact($contactData);
}
}
}
if (!empty($metaOption) && !array_key_exists('previous_timeOffset', $metaOption)) {
$this->metaWriterService->save($metaFile, ['timeOffset' => date('Y-m-d H:i:s')]);
}
return [
'has_more' => $hasMore,
'previous_timeOffset' => $timeOffset,
'startOffset' => $startOffset,
];
}
/**
* @param array $contact
*
* @throws PipedriveConfigurationException
* @throws PipedriveMetaException
* @throws EscapingException
* @throws PipedriveEventException
*
* @return void
*/
private function addXTContact(array $contact): void
{
$internalContact = $this->configuration->formatAddressByResponse($contact);
if (!$internalContact) {
return;
}
$paramValues = array_map(
function ($value) {
if (empty($value)) {
return "''";
}
return is_string($value) ? $this->db->escapeString($value) : $value;
},
array_values($internalContact)
);
$placeHolders = implode(',', array_fill(0, count($paramValues), '%s'));
$insertSql = sprintf(
'INSERT INTO `adresse` (%s) VALUES(%s)',
implode(',', array_keys($internalContact)),
vsprintf($placeHolders, $paramValues)
);
$this->db->perform($insertSql);
if ($addressId = $this->db->lastInsertId()) {
$this->db->perform(
'INSERT INTO `pipedrive_contacts`
(`pd_contact_id`, `created_at`, `address_id`) VALUES (:id, NOW(), :aid)',
['id' => (int)$contact['id'], 'aid' => $addressId]
);
$personName = !empty($internalContact['email'])? $internalContact['email'] : $internalContact['name'];
$eventMsg = sprintf(
'Neuen Kontakt (<a href="/index.php?module=adresse&action=edit&id=%d">%s</a>) hinzugef&uuml;gt.',
$addressId,
$personName
);
$this->eventService->add($eventMsg);
// ADD to group
$this->configuration->addContactToGroup($addressId);
}
}
/**
* @param array $contact
*
* @throws PipedriveConfigurationException
* @throws PipedriveEventException
*
* @return void
*/
private function updateXTContact(array $contact): void
{
$internalContact = $this->configuration->formatAddressByResponse($contact);
if (!$internalContact) {
return;
}
$excludeVars = ['lead', 'typ', 'sprache', 'waehrung', 'kundenfreigabe'];
foreach ($excludeVars as $excludeVar) {
unset($internalContact[$excludeVar]);
}
if ($hPdContact = $this->gateway->getMappingByPipedriveId($contact['id'])) {
$asPlaceHolders = array_map(
static function ($val) {
return vsprintf('%s=:%s', [$val, $val]);
},
array_keys($internalContact)
);
$placeHolders = implode(',', $asPlaceHolders);
$affected = $this->db->fetchAffected(
'UPDATE `adresse` SET ' . $placeHolders . ' WHERE `id` = ' . $hPdContact['address_id'],
$internalContact
);
if ($affected > 0) {
$personName = !empty($internalContact['email'])? $internalContact['email'] : $internalContact['name'];
$eventMsg = sprintf(
'Kontakt (<a href="/index.php?module=adresse&action=edit&id=%d">%s</a>) ge&auml;ndert.',
$hPdContact['address_id'],
$personName
);
$this->eventService->add($eventMsg);
}
}
}
/**
* @param ArrayObject $data
*
* @throws PipedriveConfigurationException
*
* @return void
*/
public function beforeScheduleAction(ArrayObject $data): void
{
if (empty($this->configuration->tryGetConfiguration('pipedrive_settings'))) {
return;
}
try {
$leadsFields = $this->configuration->matchSelectedAddressFreeField();
} catch (PipedriveConfigurationException $exception) {
return;
}
if (empty($leadsFields)) {
return;
}
}
/**
* @param ArrayObject $data
*
* @return void
*/
public function afterScheduleAction(ArrayObject $data): void
{
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Scheduler;
use ArrayObject;
interface PipedriveSchedulerTaskInterface
{
/**
* @return void
*/
public function execute(): void;
/**
* @return void
*/
public function cleanup(): void;
/**
* @param ArrayObject $data
*
* @return mixed
*/
public function beforeScheduleAction(ArrayObject $data);
/**
* @param ArrayObject $data
*
* @return mixed
*/
public function afterScheduleAction(ArrayObject $data);
}
@@ -0,0 +1,179 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Service;
use Xentral\Modules\Pipedrive\Exception\PipedriveClientException;
use Xentral\Modules\Pipedrive\Exception\PipedriveConfigurationException;
use Xentral\Modules\Pipedrive\Exception\PipedriveHttpClientException;
use Xentral\Modules\Pipedrive\Exception\PipedriveMetaException;
final class PipedriveClientService
{
/** @var string[] $endPoints */
private $endPoints = [
'allPersons' => '/v1/persons',
'recentlyUpdatedPersons' => '/v1/recents',
'deleteContact' => '/v1/persons/:id',
'updateContact' => '/v1/persons/:id',
'createContact' => '/v1/persons',
'getContactById' => '/v1/persons/:id',
'recentlyUpdatedDeals' => '/v1/recents',
'createDeal' => '/v1/deals',
'allDeals' => '/v1/deals',
'deleteDeal' => '/v1/deals/:id',
'updateDeal' => '/v1/deals/:id',
'getDealById' => '/v1/deals/:id',
'getPersonFields' => '/v1/personFields',
'getOnePersonField' => '/v1/personFields/:id',
'getStages' => '/v1/stages',
'getPipelines' => '/v1/pipelines',
];
/** @var string $authMethod */
private $authMethod = 'key';
/** @var string|null $apiKey */
private $apiKey;
/** @var PipedriveHttpClientService $client */
private $client;
/** @var PipedriveConfigurationService $confService */
private $confService;
/**
* @param PipedriveHttpClientService $client
* @param PipedriveConfigurationService $confService
* @param string|null $apiKey
*/
public function __construct(
PipedriveHttpClientService $client,
PipedriveConfigurationService $confService,
?string $apiKey = null
) {
$this->client = $client;
$this->apiKey = $apiKey;
$this->confService = $confService;
}
/** @var string $apiUrl */
private $apiUrl = 'https://api.pipedrive.com%s';
/**
* @param string $resource
* @param array $args
* @param string|null $suffix
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveMetaException
*
* @return string
*/
protected function getEndPoint(string $resource, array $args = [], ?string $suffix = null): string
{
if (!array_key_exists($resource, $this->endPoints)) {
throw new PipedriveClientException('Undefined resource endpoint');
}
$suffixUrl = $suffix ?? $this->endPoints[$resource];
$url = sprintf($this->apiUrl, $suffixUrl);
if ($this->authMethod === 'key') {
$apiKey = $this->apiKey ?? $this->getConfApiKey();
$url .= sprintf('?api_token=%s', $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 string $ressource
* @param array $data
* @param array $endPointArgs
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function read(string $ressource, array $data = [], array $endPointArgs = []): PipedriveServerResponseInterface
{
return $this->client->get($this->getEndPoint($ressource, $endPointArgs), $data);
}
/**
* @throws PipedriveConfigurationException
* @throws PipedriveMetaException
*
* @return string|null
*/
protected function getConfApiKey(): ?string
{
return $this->confService->getDecryptedConfiguration();
}
/**
* @param string $ressource
* @param array $data
* @param array $endPointArgs
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function post(string $ressource, array $data = [], array $endPointArgs = []): PipedriveServerResponseInterface
{
return $this->client->post($this->getEndPoint($ressource, $endPointArgs), $data);
}
/**
* @param string $ressource
* @param array $data
* @param array $endPointArgs
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function delete(string $ressource, array $endPointArgs = [], array $data = []): PipedriveServerResponseInterface
{
return $this->client->delete($this->getEndPoint($ressource, $endPointArgs), $data);
}
/**
* @param string $ressource
* @param array $data
* @param array $endPointArgs
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function put(string $ressource, array $data = [], array $endPointArgs = []): PipedriveServerResponseInterface
{
return $this->client->put($this->getEndPoint($ressource, $endPointArgs), $data);
}
}
@@ -0,0 +1,488 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Service;
use Xentral\Modules\Pipedrive\Exception\PipedriveConfigurationException;
use Xentral\Modules\Pipedrive\Exception\PipedriveMetaException;
use Xentral\Modules\Pipedrive\Gateway\PipedriveDealGateway;
use Xentral\Modules\Pipedrive\Gateway\PipedrivePersonPropertyGateway;
use Xentral\Modules\Pipedrive\Wrapper\PipedriveAddAddressRoleWrapper;
use Xentral\Modules\SystemConfig\SystemConfigModule;
final class PipedriveConfigurationService
{
/** @var string */
private const PIPEDRIVE_SETTINGS = 'pipedrive_settings';
/** @var string */
private const PIPEDRIVE_CONF_NAME = 'pipedrive_conf.json';
/** @var array $_defaultSettings */
private static $_defaultSettings = [
'pd_sync_deals' => true,
'pd_sync_addresses' => true,
'pd_api_key' => null,
];
/** @var SystemConfigModule $configWrapper */
private $configWrapper;
/** @var PipedriveMetaWriterService $metaWriterService */
private $metaWriterService;
/** @var PipedrivePersonPropertyGateway $propertyGateway */
private $propertyGateway;
/** @var PipedriveDealGateway $pipedriveDealGateway */
private $pipedriveDealGateway;
/** @var PipedriveMetaReaderService $metaReaderService */
private $metaReaderService;
/** @var PipedriveAddAddressRoleWrapper $addAddressRoleWrapper */
private $addAddressRoleWrapper;
/**
* @param SystemConfigModule $configWrapper
* @param PipedriveMetaWriterService $metaWriterService
* @param PipedrivePersonPropertyGateway $propertyGateway
* @param PipedriveDealGateway $pipedriveDealGateway
* @param PipedriveMetaReaderService $metaReaderService
* @param PipedriveAddAddressRoleWrapper $addAddressRoleWrapper
*/
public function __construct(
SystemConfigModule $configWrapper,
PipedriveMetaWriterService $metaWriterService,
PipedrivePersonPropertyGateway $propertyGateway,
PipedriveDealGateway $pipedriveDealGateway,
PipedriveMetaReaderService $metaReaderService,
PipedriveAddAddressRoleWrapper $addAddressRoleWrapper
) {
$this->configWrapper = $configWrapper;
$this->metaWriterService = $metaWriterService;
$this->propertyGateway = $propertyGateway;
$this->pipedriveDealGateway = $pipedriveDealGateway;
$this->metaReaderService = $metaReaderService;
$this->addAddressRoleWrapper = $addAddressRoleWrapper;
}
/**
* @param string $name
* @param string $value
*
* @throws PipedriveConfigurationException
*
* @return void
*/
public function trySetConfiguration(string $name, string $value): void
{
if (empty($name)) {
throw new PipedriveConfigurationException('Cannot set Configuration');
}
$this->configWrapper->setValue(self::PIPEDRIVE_SETTINGS, $name, $value);
}
/**
* @param string $name
*
* @throws PipedriveConfigurationException
*
* @return string|null
*/
public function tryGetConfiguration(string $name): ?string
{
if (empty($name)) {
throw new PipedriveConfigurationException('Cannot get configuration on Empty');
}
return $this->configWrapper->tryGetValue(self::PIPEDRIVE_SETTINGS, $name);
}
/**
* @param array $settings
* @param string $value
*
* @throws PipedriveConfigurationException
* @throws PipedriveMetaException
*
* @return array
*/
public function getEncryptedConfiguration(array $settings, string $value): array
{
if (empty($settings)) {
throw new PipedriveConfigurationException('Cannot set Configuration');
}
$encValue = $this->encrypt($value);
$settings['pd_api_key'] = $encValue;
return $settings;
}
/**
* @throws PipedriveConfigurationException
* @throws PipedriveMetaException
*
* @return string|null
*/
public function getDecryptedConfiguration(): ?string
{
$settings = $this->getSettings();
return $settings['pd_api_key'] ?? null;
}
/**
* @param string $plaintext
* @param string $sCipher
*
* @throws PipedriveConfigurationException
* @throws PipedriveMetaException
*
* @return string
*/
protected function encrypt(string $plaintext, string $sCipher = 'aes-128-gcm'): string
{
if (!in_array($sCipher, openssl_get_cipher_methods(), true)) {
throw new PipedriveConfigurationException(sprintf('Cipher method %s does not exist', $sCipher));
}
if (null === $this->getNonceSalt()) {
return $plaintext;
}
$key = hash('sha256', $this->getNonceSalt());
$ivLen = openssl_cipher_iv_length($sCipher);
$iv = openssl_random_pseudo_bytes($ivLen, $crypto_strong);
if ($iv === false || $crypto_strong === false) {
throw new PipedriveConfigurationException('Bad Random length');
}
$cipherTextRaw = openssl_encrypt($plaintext, $sCipher, $key, $options = 0, $iv, $tag);
return base64_encode($iv . $cipherTextRaw . '..' . $tag);
}
/**
* @param string $string
* @param string $sCipher
*
* @throws PipedriveMetaException
*
* @return false|string|null
*/
protected function decrypt(string $string, string $sCipher = 'aes-128-gcm')
{
if (empty($string) || !$this->isBase64Encoded($string)) {
return '';
}
if (null === $this->getNonceSalt()) {
return $this->isBase64Encoded($string) ? null : $string;
}
$stringDecode = base64_decode($string);
$encExploded = explode('..', $stringDecode);
$enc = array_shift($encExploded);
$tag = implode('', $encExploded);
$key = hash('sha256', $this->getNonceSalt());
$ivLen = openssl_cipher_iv_length($sCipher);
$iv = substr($enc, 0, $ivLen);
$cipherTextRaw = substr($enc, $ivLen);
return openssl_decrypt($cipherTextRaw, $sCipher, $key, $options = 0, $iv, $tag);
}
/**
* @param string $string
*
* @return bool
*/
private function isBase64Encoded(string $string): bool
{
return base64_encode(base64_decode($string)) === $string;
}
/**
* @return false|string|null
*/
private function generateSecureSalt()
{
$rand = sprintf('%s', mt_rand());
return password_hash(uniqid($rand, true), PASSWORD_BCRYPT);
}
/**
* @param bool $force
*
* @throws PipedriveMetaException
*
* @return false|int
*/
public function createSalt(bool $force = false)
{
if ($force === true) {
$this->metaWriterService->delete(self::PIPEDRIVE_CONF_NAME);
}
if ($this->metaReaderService->exists(self::PIPEDRIVE_CONF_NAME) && $this->metaReaderService->hasKey(
'nonce_salt',
self::PIPEDRIVE_CONF_NAME
)) {
return -1;
}
return $this->metaWriterService->save(self::PIPEDRIVE_CONF_NAME, ['nonce_salt' => $this->generateSecureSalt()]);
}
/**
* @throws PipedriveMetaException
*
* @return string|null
*/
private function getNonceSalt(): ?string
{
$data = $this->metaReaderService->readFromFile(self::PIPEDRIVE_CONF_NAME);
return $data['nonce_salt'] ?? null;
}
/**
* @param array $hContact
*
* @throws PipedriveConfigurationException
*
* @return array
*/
public function formatAddressByResponse(array $hContact): array
{
if (empty($hContact)) {
throw new PipedriveConfigurationException('Invalid contact');
}
$leadFields = $this->matchSelectedAddressFreeField();
$lsField = $leadFields['pipedrive_ls_field'];
$ahEmail = array_map(
static function ($email) {
if (!array_key_exists('primary', $email) && $email['primary'] === true) {
return null;
}
return $email;
},
$hContact['email']
);
$email = array_filter($ahEmail);
$primaryEmail = is_array($email[0]) && array_key_exists('value', $email[0]) ? $email[0]['value'] : '';
$ahPhone = array_map(
static function ($phone) {
if (!array_key_exists('primary', $phone) && $phone['primary'] === true) {
return null;
}
return $phone;
},
$hContact['phone']
);
$phone = array_filter($ahPhone);
$primaryPhone = is_array($phone[0]) && array_key_exists('value', $phone[0]) ? $phone[0]['value'] : '';
return [
'lead' => 1,
'typ' => !empty($hContact['org_name']) ? 'firma' : 'herr',
'sprache' => 'deutsch',
'name' => $hContact['org_name'] ?? $hContact['name'],
'vorname' => empty($hContact['first_name']) ? 'Pipedrive - ' : $hContact['first_name'],
'nachname' => empty($hContact['last_name']) ? $primaryEmail : $hContact['last_name'],
'land' => empty($hContact['country']) ? 'DE' : $hContact['country'],
'telefon' => $primaryPhone ?? '',
'email' => $primaryEmail,
'kundenfreigabe' => 1,
'waehrung' => 'EUR',
'ansprechpartner' => !empty($hContact['org_name']) ? $hContact['name'] : '',
$lsField => empty($hContact['label']) ? '' : $hContact['label'],
];
}
/**
* @throws PipedriveConfigurationException
*
* @return array
*/
public function matchSelectedAddressFreeField(): array
{
$hFields = [];
$asAddressFreeFieldValues = $this->propertyGateway->getConfiguredFreeAddressFieldValues();
$pdConfFields = [
'pipedrive_ls_field' => $this->tryGetConfiguration('pipedrive_ls_field'),
];
foreach ($asAddressFreeFieldValues as $fieldName) {
$addrField = 'adresse' . $fieldName;
if (in_array($addrField, $pdConfFields, true)) {
$indexField = array_search($addrField, $pdConfFields, true);
$hFields[$indexField] = $fieldName;
}
}
if (empty($hFields)) {
throw new PipedriveConfigurationException('Pipedrive Label-Status fields cannot be matched');
}
return $hFields;
}
/**
* @param array $address
*
* @throws PipedriveConfigurationException
*
* @return array
*/
public function formatAddressToPipedriveContact(array $address): array
{
if (empty($address)) {
throw new PipedriveConfigurationException('Address is invalid');
}
$leadFields = $this->matchSelectedAddressFreeField();
$lsField = $leadFields['pipedrive_ls_field'];
return [
'email' => $address['email'],
'first_name' => empty($address['vorname']) ? $address['name'] : $address['vorname'],
'last_name' => empty($address['nachname']) ? $address['name'] : $address['nachname'],
'name' => $address['name'],
'phone' => $address['telefon'],
'label' => $address[$lsField],
];
}
/**
* @param array $hDeal
*
* @throws PipedriveConfigurationException
*
* @return array
*/
public function formatDealToInternal(array $hDeal): array
{
if (empty($hDeal)) {
throw new PipedriveConfigurationException('Error! Deal cannot be formatted for Xentral');
}
$dealStage = $this->propertyGateway->getMappingByValueAndType($hDeal['stage_id'], 'deals');
$asAddTime = [];
$addTime = $hDeal['add_time'] ?? null;
if ($addTime !== null) {
$asAddTime = explode(' ', $addTime);
}
return [
'bezeichnung' => $hDeal['title'],
'datum_angelegt' => !empty($asAddTime) ? $asAddTime[0] : null,
'zeit_angelegt' => !empty($asAddTime) ? $asAddTime[1] : null,
'datum_erinnerung' => null,
'zeit_erinnerung' => null,
'betrag' => array_key_exists('value', $hDeal) ? (float)$hDeal['value'] : 0.00,
'stages' => !empty($dealStage['wiedervorlage_stage_id']) ?
$dealStage['wiedervorlage_stage_id'] : 0,
'chance' => !empty($hDeal['probability']) ? $hDeal['probability'] : 0,
];
}
/**
* @param array $resubmission
*
* @throws PipedriveConfigurationException
*
* @return array
*/
public function formatResubmissionToPipedriveDeal(array $resubmission): array
{
if (empty($resubmission)) {
throw new PipedriveConfigurationException('Resubmission is invalid');
}
$status = 'open';
if (!empty($resubmission['abgeschlossen'])) {
if ($resubmission['status'] === 'gewonnen') {
$status = 'won';
} elseif ($resubmission['status'] === 'verloren') {
$status = 'lost';
}
}
$mapping = $this->pipedriveDealGateway->getMappingStageByResubmissionStageId($resubmission['stages']);
return [
'title' => $resubmission['bezeichnung'],
'stage_id' => !empty($mapping) ? $mapping['value'] : null,
'value' => empty($resubmission['betrag']) ? 0.00 : $resubmission['betrag'],
'probability' => $resubmission['chance'],
'status' => $status,
];
}
/**
* @param array $settings
*
* @throws PipedriveConfigurationException
*/
public function setSettings($settings = []): void
{
$this->trySetConfiguration(
self::PIPEDRIVE_SETTINGS,
json_encode($settings, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT)
);
}
/**
* @throws PipedriveConfigurationException
* @throws PipedriveMetaException
*
* @return array
*/
public function getSettings(): array
{
$settingsRaw = $this->tryGetConfiguration(self::PIPEDRIVE_SETTINGS);
if (empty($settingsRaw)) {
return static::$_defaultSettings;
}
$settings = json_decode($settingsRaw, true);
if ($settings === null && json_last_error() === JSON_ERROR_NONE) {
throw new PipedriveConfigurationException(json_last_error_msg());
}
if (empty($settings)) {
return static::$_defaultSettings;
}
if (array_key_exists('pd_api_key', $settings) && !empty($settings['pd_api_key'])) {
$settings['pd_api_key'] = $this->decrypt($settings['pd_api_key']);
}
return $settings;
}
/**
* @param int $contactId
*
* @throws PipedriveConfigurationException
* @throws PipedriveMetaException
*
* @return void
*/
public function addContactToGroup(int $contactId = 0): void
{
$defaultSettings = $this->getSettings();
$contactGrpId = array_key_exists('pd_contact_grp', $defaultSettings) ? $defaultSettings['pd_contact_grp'] : 0;
if (!empty($contactGrpId)) {
$this->addAddressRoleWrapper->add($contactId, $contactGrpId);
}
}
}
@@ -0,0 +1,186 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\Pipedrive\Exception\PipedriveClientException;
use Xentral\Modules\Pipedrive\Exception\PipedriveConfigurationException;
use Xentral\Modules\Pipedrive\Exception\PipedriveDealPropertyServiceException;
use Xentral\Modules\Pipedrive\Exception\PipedriveHttpClientException;
use Xentral\Modules\Pipedrive\Exception\PipedriveMetaException;
use Xentral\Modules\Pipedrive\Gateway\PipedrivePersonPropertyGateway;
use Xentral\Modules\Pipedrive\Wrapper\PipedriveResubmissionWrapper;
use Xentral\Modules\Resubmission\Exception\StageNotFoundException;
use Xentral\Modules\Resubmission\Exception\ViewNotFoundException;
use Xentral\Modules\Resubmission\Service\ResubmissionGateway;
final class PipedriveDealPropertyService
{
/** @var PipedriveClientService $client */
private $client;
/** @var Database $db */
private $db;
/** @var PipedrivePersonPropertyGateway $propertyGateway */
private $propertyGateway;
/** @var ResubmissionGateway $resubmissionGateway */
private $resubmissionGateway;
/** @var PipedriveResubmissionWrapper $resubmissionWrapper */
private $resubmissionWrapper;
/**
* @param PipedriveClientService $client
* @param Database $db
* @param PipedrivePersonPropertyGateway $propertyGateway
* @param ResubmissionGateway $resubmissionGateway
* @param PipedriveResubmissionWrapper $resubmissionWrapper
*/
public function __construct(
PipedriveClientService $client,
Database $db,
PipedrivePersonPropertyGateway $propertyGateway,
ResubmissionGateway $resubmissionGateway,
PipedriveResubmissionWrapper $resubmissionWrapper
) {
$this->client = $client;
$this->db = $db;
$this->propertyGateway = $propertyGateway;
$this->resubmissionGateway = $resubmissionGateway;
$this->resubmissionWrapper = $resubmissionWrapper;
}
/**
* @param int $pipelineId
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveDealPropertyServiceException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return array
*/
public function getDealStages(int $pipelineId = 0): array
{
$data = [];
if ($pipelineId !== 0) {
$data = ['pipeline_id' => $pipelineId];
}
$response = $this->client->read('getStages', $data);
if ($response->getStatusCode() !== 200) {
throw new PipedriveDealPropertyServiceException($response->getError());
}
return $response->getData();
}
/**
* @param int|null $pipelineId
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveDealPropertyServiceException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
* @throws StageNotFoundException
*
* @return void
*/
public function installDealStages(?int $pipelineId = null): void
{
// 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->resubmissionGateway->getViewIdByStage($firstStage);
}
if (empty($firstStage) || empty($viewId)) {
try {
$viewId = $this->resubmissionGateway->getViewIdByNameAndDescription('Pipedrive', 'Pipedrive');
} catch (ViewNotFoundException $exception) {
$viewId = $this->resubmissionWrapper->addResubmissionView('Pipedrive', 'Pipedrive');
}
}
// Default Pipeline
if ($pipelineId === null) {
$pipeline = $this->getDefaultPipeline();
$pipelineId = $pipeline['id'] ?? 0;
}
if ($viewId > 0 && ($ahDealStages = $this->getDealStages($pipelineId))) {
$position = $this->resubmissionGateway->getMaxSortByViewId($viewId) + 1;
foreach ($ahDealStages as $hDealStage) {
if ($this->db->fetchValue(
'SELECT hm.id FROM `pipedrive_mappings` AS `hm` WHERE hm.value=:value AND hm.type = :type',
[
'value' => $hDealStage['id'],
'type' => 'deals',
]
)) {
continue;
}
$stage = [
'desc' => $hDealStage['name'],
'name' => $hDealStage['name'],
'position' => $position,
'wiedervorlage_view_id' => $viewId,
'enabled' => 1,
'ausblenden' => 0,
];
$stageId = $this->resubmissionWrapper->addResubmissionStage($stage);
if (!empty($stageId)) {
$this->db->perform(
'INSERT INTO `pipedrive_mappings` (`label`, `value`, `type`, `wiedervorlage_stage_id`,
`is_system`, `wiedervorlage_view_id`)
VALUES(:label, :value,:type, :wstage_id, :is_system, :view_id)',
[
'label' => $hDealStage['name'],
'value' => $hDealStage['id'],
'type' => 'deals',
'wstage_id' => $stageId,
'is_system' => 1,
'view_id' => $viewId,
]
);
}
$position++;
}
}
}
/**
* @throws PipedriveClientException
* @throws PipedriveDealPropertyServiceException
* @throws PipedriveHttpClientException
* @throws PipedriveConfigurationException
* @throws PipedriveMetaException
*
* @return array
*/
public function getDefaultPipeline(): array
{
$response = $this->client->read('getPipelines');
if ($response->getStatusCode() !== 200) {
throw new PipedriveDealPropertyServiceException($response->getError());
}
$data = $response->getData();
return array_shift($data);
}
}
@@ -0,0 +1,216 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Service;
use Xentral\Modules\Pipedrive\Exception\PipedriveClientException;
use Xentral\Modules\Pipedrive\Exception\PipedriveConfigurationException;
use Xentral\Modules\Pipedrive\Exception\PipedriveDealServiceException;
use Xentral\Modules\Pipedrive\Exception\PipedriveHttpClientException;
use Xentral\Modules\Pipedrive\Exception\PipedriveMetaException;
use Xentral\Modules\Pipedrive\Exception\PipedriveValidatorException;
use Xentral\Modules\Pipedrive\Validator\PipedriveDealValidator;
final class PipedriveDealService
{
/** @var array $allowedSyncDealsOptions */
private $allowedSyncDealsOptions = [
'pipedrive_recently_updated_deals' => 'getRecentlyUpdatedDeals',
'pipedrive_all_deals' => 'getDeals',
];
/** @var array $itemOption */
private $itemOption = [
'limit' => 100,
'items' => 'deal',
'start' => 0,
'since_timestamp' => '1970-01-01 23:59:59',
];
/** @var PipedriveClientService $client */
private $client;
/** @var PipedriveDealValidator $validator */
private $validator;
/** @var PipedriveMetaReaderService $metaReaderService */
private $metaReaderService;
/**
* @param PipedriveClientService $client
* @param PipedriveDealValidator $validator
* @param PipedriveMetaReaderService $metaReaderService
*/
public function __construct(
PipedriveClientService $client,
PipedriveDealValidator $validator,
PipedriveMetaReaderService $metaReaderService
) {
$this->client = $client;
$this->validator = $validator;
$this->metaReaderService = $metaReaderService;
}
/**
* @param array $data
*
* @throws PipedriveClientException
* @throws PipedriveDealServiceException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
* @throws PipedriveValidatorException
* @throws PipedriveConfigurationException
*
* @return PipedriveServerResponseInterface
*/
public function createDeal(array $data = []): PipedriveServerResponseInterface
{
$default = ['status' => 'open', 'stage_id' => 1];
$data += $default;
if (!$this->validator->isValid($data)) {
throw new PipedriveDealServiceException(sprintf('%s::createDeal Invalid Deal data', get_class($this)));
}
$deal = $this->validator->getData();
return $this->client->post('createDeal', $deal);
}
/**
* @param int $dealId
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function getDealById(int $dealId): PipedriveServerResponseInterface
{
return $this->client->read('getDealById', [], [$dealId]);
}
/**
* @param array $options
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function getDeals(array $options = []): PipedriveServerResponseInterface
{
$options += $this->itemOption;
return $this->client->read('allDeals', $options);
}
/**
* @param array $options
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function getRecentlyUpdatedDeals(array $options = []): PipedriveServerResponseInterface
{
$options += $this->itemOption;
return $this->client->read('recentlyUpdatedDeals', $options);
}
/**
* @param int $dealId
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function deleteDeal(int $dealId): PipedriveServerResponseInterface
{
return $this->client->delete('deleteDeal', [$dealId]);
}
/**
* @param int $dealId
* @param array $data
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveDealServiceException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
* @throws PipedriveValidatorException
*
* @return PipedriveServerResponseInterface
*/
public function updateDealById(int $dealId, array $data = []): PipedriveServerResponseInterface
{
if (!$this->validator->isValid($data)) {
throw new PipedriveDealServiceException(sprintf('%s::updateDealById Invalid Deal data', get_class($this)));
}
$deal = $this->validator->getData();
return $this->client->put('updateDeal', $deal, [$dealId]);
}
/**
* @param string $type
* @param array $options
*
* @throws PipedriveDealServiceException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function pullDeals(
string $type = 'pipedrive_recently_updated_deals',
array $options = []
): PipedriveServerResponseInterface {
if ('pipedrive_all_deals' !== $type && !array_key_exists($type, $this->allowedSyncDealsOptions)) {
throw new PipedriveDealServiceException(sprintf('Sync Type %s not allowed', $type));
}
$metaFile = sprintf('%s.json', $type);
if ($type !== 'pipedrive_all_deals') {
$options = $this->addMetaOption($metaFile, $options);
}
/** @var PipedriveServerResponseInterface $response */
return $this->{$this->allowedSyncDealsOptions[$type]}($options);
}
/**
* @param string $metaFile
* @param array $options
*
* @throws PipedriveMetaException
*
* @return array
*/
private function addMetaOption(string $metaFile, array $options) : array
{
$metaInfo = $this->metaReaderService->readFromFile($metaFile);
if (!empty($metaInfo)) {
$timeOffset = $metaInfo['timeOffset'];
$offset = strtotime($metaInfo['timeOffset']);
if ($offset !== false) {
$timeOffset = gmdate('Y-m-d H:i:s', $offset - 3600);
}
$options = array_merge($options, ['since_timestamp' => $timeOffset]);
}
return $options;
}
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Service;
use Xentral\Components\Database\Database;
use Xentral\Components\Database\Exception\DatabaseExceptionInterface;
use Xentral\Modules\Pipedrive\Exception\PipedriveEventException;
final class PipedriveEventService
{
/** @var Database $db */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param string $event
*
* @throws PipedriveEventException
*
* @return int
*/
public function add(string $event): int
{
$add = 'INSERT INTO `pipedrive_events` (`event`, `created_at`)
VALUES (:event, NOW())';
try {
$this->db->perform($add, ['event' => $event]);
} catch (DatabaseExceptionInterface $exception) {
throw new PipedriveEventException($exception->getMessage());
}
return $this->db->lastInsertId();
}
/**
* @param int $id
*
* @return void
*/
public function deleteById(int $id): void
{
$this->db->perform('DELETE FROM `pipedrive_events` WHERE `id` = :id', ['id' => $id]);
}
/**
* @return void
*/
public function deleteAll(): void
{
$this->db->perform('DELETE FROM `pipedrive_events`');
}
/**
* @param int $days
*
* @return void
*/
public function deleteByInterval(int $days = 30): void
{
$sql = 'DELETE FROM `pipedrive_events` WHERE `created_at` < DATE_SUB(NOW(), INTERVAL :days DAY)';
$this->db->perform($sql, ['days' => $days]);
}
}
@@ -0,0 +1,246 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Service;
use Xentral\Components\HttpClient\Exception\TransferErrorExceptionInterface;
use Xentral\Components\HttpClient\HttpClientFactory;
use Xentral\Components\HttpClient\HttpClientInterface;
use Xentral\Components\HttpClient\Request\ClientRequest;
use \Xentral\Components\HttpClient\RequestOptions;
use Xentral\Modules\Pipedrive\Exception\PipedriveHttpClientException;
final class PipedriveHttpClientService
{
/** @var string */
private const GET_REQUEST = 'GET';
/** @var string */
private const POST_REQUEST = 'POST';
/** @var string */
private const DELETE_REQUEST = 'DELETE';
/** @var string */
private const PATCH_REQUEST = 'PATCH';
/** @var string */
private const PUT_REQUEST = 'PUT';
/** @var null|string $endpoint */
protected $endpoint;
/** @var array $userAgent */
protected $userAgent = [];
/** @var array $hRequestVerbs */
protected $hRequestVerbs = [
self::GET_REQUEST => null,
self::POST_REQUEST => 'json',
self::PUT_REQUEST => 'json',
self::PATCH_REQUEST => 'json',
self::DELETE_REQUEST => null,
];
/** @var int $timeout */
private $timeout = 10;
/** @var array $_headers */
private $_headers = [];
/** @var HttpClientInterface $client */
private $client;
/** @var null|RequestOptions $requestOption */
private $requestOption;
/** @var HttpClientFactory|null $factory */
private $factory;
/**
* @param HttpClientFactory $factory
* @param int $timeout
* @param HttpClientInterface|null $client
*
* @throws PipedriveHttpClientException
*/
public function __construct(
HttpClientFactory $factory,
int $timeout = 0,
?HttpClientInterface $client = null
) {
if (!empty($timeout)) {
$this->timeout = $timeout;
}
$this->factory = $factory;
$this->client = $client ?? $this->createClient();
}
/**
* @param string $url
* @param string $method
* @param array $data
* @param array $headers
*
* @throws PipedriveHttpClientException
*
* @return PipedriveServerResponseInterface|null
*/
protected function performRequest(
string $url,
string $method,
array $data = [],
array $headers = []
): ?PipedriveServerResponseInterface {
$this->setHeader($headers);
$keyParam = $this->hRequestVerbs[$method];
$paramData = null;
if ($keyParam !== null && $keyParam === 'json') {
$paramData = json_encode($data);
}
$rqHeaders = array_merge(
['Accept' => 'application/json', 'Content-Type' => 'application/json'],
$this->getHeaders()
);
$request = new ClientRequest($method, $url, $rqHeaders);
if (!empty($paramData)) {
if ($this->requestOption === null) {
$this->requestOption = new RequestOptions();
}
$this->requestOption->setBody($paramData);
}
try {
$response = $this->client->sendRequest($request, $this->requestOption);
return new PipedriveHttpResponseService($response);
} catch (TransferErrorExceptionInterface $exception) {
throw new PipedriveHttpClientException($exception->getMessage());
}
}
/**
* @throws PipedriveHttpClientException
*
* @return HttpClientInterface
*/
private function createClient(): HttpClientInterface
{
if (!is_int($this->timeout) || $this->timeout < 0) {
throw new PipedriveHttpClientException(
sprintf('Connection timeout must be an int >= 0, got "%s".', gettype($this->timeout))
);
}
if ($this->factory === null) {
throw new PipedriveHttpClientException('HttpClientFactory is missing!');
}
$options = new RequestOptions();
if ($this->timeout > 0) {
$options->setTimeout($this->timeout);
}
$options->setHeader('Accept', 'application/json');
$options->setHeader('Content-Type', 'application/json');
$this->requestOption = $options;
return $this->factory->createClient($this->requestOption);
}
/**
* @param string $url
* @param array $data
* @param array $header
*
* @throws PipedriveHttpClientException
*
* @return PipedriveServerResponseInterface|null
*/
public function get(string $url, array $data = [], array $header = []): ?PipedriveServerResponseInterface
{
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, self::GET_REQUEST, [], $header);
}
/**
* @param string $url
* @param array $data
* @param array $header
*
* @throws PipedriveHttpClientException
*
* @return PipedriveServerResponseInterface|null
*/
public function post(string $url, array $data = [], array $header = []): ?PipedriveServerResponseInterface
{
return $this->performRequest($url, self::POST_REQUEST, $data, $header);
}
/**
* @param string $url
* @param array $data
* @param array $header
*
* @throws PipedriveHttpClientException
*
* @return PipedriveServerResponseInterface|null
*/
public function patch(string $url, array $data = [], array $header = []): ?PipedriveServerResponseInterface
{
return $this->performRequest($url, self::PATCH_REQUEST, $data, $header);
}
/**
* @param string $url
* @param array $data
* @param array $header
*
* @throws PipedriveHttpClientException
*
* @return PipedriveServerResponseInterface|null
*/
public function delete(string $url, array $data = [], array $header = []): ?PipedriveServerResponseInterface
{
return $this->performRequest($url, self::DELETE_REQUEST, $data, $header);
}
/**
* @param array $option
*/
protected function setHeader(array $option = []): void
{
$this->_headers += $option;
}
/**
* @return array
*/
protected function getHeaders(): array
{
$default = ['User-Agent' => 'Xentral-ERP-CRM'];
return $this->_headers += $default;
}
/**
* @param string $url
* @param array $data
* @param array $header
*
* @throws PipedriveHttpClientException
*
* @return PipedriveServerResponseInterface|null
*/
public function put(string $url, array $data = [], array $header = []): ?PipedriveServerResponseInterface
{
return $this->performRequest($url, self::PUT_REQUEST, $data, $header);
}
}
@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Service;
use Psr\Http\Message\StreamInterface;
use Xentral\Components\HttpClient\Response\ServerResponseInterface;
final class PipedriveHttpResponseService implements PipedriveServerResponseInterface
{
/** @var ServerResponseInterface $response */
private $response;
/** @var null|array $json */
private $json;
/**
* @param ServerResponseInterface $response
*/
public function __construct(ServerResponseInterface $response)
{
$this->response = $response;
$this->json = json_decode((string)$this->response->getBody(), true);
}
/**
* Returns the json response body
*
* @return null|array
*/
public function getJson(): ?array
{
return $this->json;
}
/**
* @return StreamInterface
*/
public function getBody(): StreamInterface
{
return $this->response->getBody();
}
/**
* @return int
*/
public function getStatusCode(): int
{
return $this->response->getStatusCode();
}
/**
* @return bool
*/
public function isSuccess(): bool
{
$success = false;
if (null !== $this->json && is_array($this->json)) {
$success = array_key_exists('success', $this->json) && $this->json['success'] === true;
}
return $success;
}
/**
* @return array
*/
public function getData(): array
{
$data = [];
if (null !== $this->json && is_array($this->json) && array_key_exists('data', $this->json)) {
$data = $this->json['data'] ?? [];
}
return $data;
}
/**
* Returns the error message
*
* @return string
*/
public function getError(): string
{
$error = '';
if (null !== $this->json && is_array($this->json) && array_key_exists('error', $this->json) &&
!in_array($this->getStatusCode(), [200, 201])) {
$error = $this->json['error'] ?? 'Unknown Error';
}
return $error;
}
/**
* @return array
*/
public function getAdditionalData(): array
{
$additionalData = [];
if (null !== $this->json && is_array($this->json) && array_key_exists('additional_data', $this->json)) {
$additionalData = $this->json['additional_data'];
}
return $additionalData;
}
/**
* @return array|null
*/
public function getPagination(): ?array
{
$pagination = null;
if (($additional_data = $this->getAdditionalData()) && array_key_exists('pagination', $additional_data)) {
$pagination = $additional_data['pagination'];
}
return $pagination;
}
}
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Service;
use Xentral\Modules\Pipedrive\Exception\PipedriveExceptionInterface;
use Xentral\Modules\Pipedrive\Exception\PipedriveMetaException;
final class PipedriveMetaReaderService
{
/** @var string $tmpDir directory to save the meta file */
private $tmpDir;
/**
* @param string $tmpDir
*/
public function __construct(string $tmpDir)
{
$this->tmpDir = $tmpDir;
}
/**
* @param string $fileName
*
* @throws PipedriveMetaException
*
* @return array|null
*/
public function readFromFile(string $fileName): ?array
{
if (empty($fileName)) {
throw new PipedriveMetaException(
sprintf('::readFromFile() Expects Meta content to be non empty string file, %s given', $fileName)
);
}
if (!$this->exists($fileName)) {
return null;
}
$fullFileName = $this->getFullFileName($fileName);
$meta = @file_get_contents($fullFileName);
if (($meta = json_decode($meta, true)) === null || (json_last_error() !== JSON_ERROR_NONE)) {
return [];
}
return $meta;
}
/**
* @param string $fileName
*
* @throws PipedriveMetaException
*
* @return string|null
*/
private function getFullFileName(string $fileName): ?string
{
$metaFile = sprintf($this->tmpDir . DIRECTORY_SEPARATOR . '%s', $fileName);
if (!is_file($metaFile)) {
throw new PipedriveMetaException(sprintf('File "%s" was not found', $metaFile));
}
return $metaFile;
}
/**
* @param string $fileName
*
* @return bool
*/
public function exists(string $fileName): bool
{
try {
$fullFileName = $this->getFullFileName($fileName);
return file_exists($fullFileName) && is_file($fullFileName);
} catch (PipedriveExceptionInterface $exception) {
//
}
return false;
}
/**
* @param string $key
* @param string $fileName
*
* @throws PipedriveMetaException
*
* @return bool
*/
public function hasKey(string $key, string $fileName): bool
{
return $this->exists($fileName) && ($meta = $this->readFromFile($fileName)) && array_key_exists($key, $meta);
}
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Service;
use Xentral\Modules\Pipedrive\Exception\PipedriveMetaException;
final class PipedriveMetaWriterService
{
/** @var string $tmpDir directory to save the meta file */
private $tmpDir;
/**
* PipedriveMetaWriterService constructor.
*
* @param string $tmpDir
*/
public function __construct(string $tmpDir)
{
$this->tmpDir = $tmpDir;
}
/**
* @param string $fileName
* @param array $data
*
* @throws PipedriveMetaException
*
* @return false|int
*/
public function save(string $fileName, array $data)
{
if (empty($fileName)) {
throw new PipedriveMetaException('Name cannot be empty');
}
if (!function_exists('json_encode')) {
throw new PipedriveMetaException('Required PHP extension "json" is missing.');
}
$content = json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
return file_put_contents(
$this->getFullFileName($fileName),
$content
);
}
/**
* @param string $fileName
*
* @throws PipedriveMetaException
*
* @return string
*/
private function getFullFileName(string $fileName): string
{
if (!is_dir($this->tmpDir) && !@mkdir($this->tmpDir, 0777, true) && !is_dir($this->tmpDir)) {
throw new PipedriveMetaException(sprintf('Directory "%s" was not created', $this->tmpDir));
}
return sprintf($this->tmpDir . DIRECTORY_SEPARATOR . '%s', $fileName);
}
/**
* @param string $fileName
*
* @throws PipedriveMetaException
*
* @return bool
*/
public function delete(string $fileName): bool
{
$metaFile = $this->getFullFileName($fileName);
return file_exists($metaFile) && is_file($metaFile) && @unlink($metaFile);
}
}
@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Service;
use Xentral\Modules\Pipedrive\Exception\PipedriveClientException;
use Xentral\Modules\Pipedrive\Exception\PipedriveConfigurationException;
use Xentral\Modules\Pipedrive\Exception\PipedriveHttpClientException;
use Xentral\Modules\Pipedrive\Exception\PipedriveMetaException;
use Xentral\Modules\Pipedrive\Exception\PipedrivePersonPropertyServiceException;
final class PipedrivePersonPropertyService
{
/** @var PipedriveClientService $client */
private $client;
/**
* @param PipedriveClientService $client
*/
public function __construct(PipedriveClientService $client)
{
$this->client = $client;
}
/**
* @throws PipedriveClientException
* @throws PipedriveHttpClientException
* @throws PipedriveConfigurationException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function getProperties(): PipedriveServerResponseInterface
{
return $this->client->read('getPersonFields');
}
/**
* @param int $id
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function getProperty(int $id): PipedriveServerResponseInterface
{
return $this->client->read('getOnePersonField', [], [$id]);
}
/**
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
* @throws PipedrivePersonPropertyServiceException
*
* @return array
*/
public function getPdLeadStatus(): array
{
$response = $this->getProperty(9039);
if ($response->getStatusCode() !== 200) {
throw new PipedrivePersonPropertyServiceException($response->getError());
}
if (($data = $response->getData()) && array_key_exists('options', $data)) {
return $data['options'];
}
return [];
}
}
@@ -0,0 +1,241 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Service;
use Xentral\Modules\Pipedrive\Exception\PipedriveClientException;
use Xentral\Modules\Pipedrive\Exception\PipedriveConfigurationException;
use Xentral\Modules\Pipedrive\Exception\PipedriveHttpClientException;
use Xentral\Modules\Pipedrive\Exception\PipedriveMetaException;
use Xentral\Modules\Pipedrive\Exception\PipedrivePersonServiceException;
use Xentral\Modules\Pipedrive\Exception\PipedriveValidatorException;
use Xentral\Modules\Pipedrive\Validator\PipedrivePersonValidator;
final class PipedrivePersonService
{
/** @var array $itemOption */
private $itemOption = [
'limit' => 100,
'items' => 'person',
'start' => 0,
'since_timestamp' => '1970-01-01 23:59:59',
];
/** @var string[] $allowedSyncPeronOptions */
private $allowedSyncPeronOptions = [
'pipedrive_recently_updated' => 'getRecentlyUpdatedPersons',
'pipedrive_all' => 'getAllPersons',
];
/** @var PipedriveClientService $client */
private $client;
/** @var PipedriveMetaReaderService $metaReaderService */
private $metaReaderService;
/** @var PipedrivePersonValidator $validator */
private $validator;
/**
* PipedrivePersonService constructor.
*
* @param PipedriveClientService $client
* @param PipedrivePersonValidator $validator
* @param PipedriveMetaReaderService $metaReaderService
*/
public function __construct(
PipedriveClientService $client,
PipedrivePersonValidator $validator,
PipedriveMetaReaderService $metaReaderService
) {
$this->client = $client;
$this->validator = $validator;
$this->metaReaderService = $metaReaderService;
}
/**
* @param array $options
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function getAllPersons(array $options = []): PipedriveServerResponseInterface
{
return $this->client->read('allPersons', $options);
}
/**
* @param array $options
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function getRecentlyUpdatedPersons(array $options = []): PipedriveServerResponseInterface
{
$options = array_merge($this->itemOption, $options);
return $this->client->read('recentlyUpdatedPersons', $options);
}
/**
* @param int $contactId
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function deleteContact(int $contactId = 0): PipedriveServerResponseInterface
{
return $this->client->delete('deleteContact', [$contactId]);
}
/**
* @param array $data
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
* @throws PipedrivePersonServiceException
* @throws PipedriveValidatorException
*
* @return PipedriveServerResponseInterface
*/
public function createContact(array $data = []): PipedriveServerResponseInterface
{
if (!$this->validator->isValid($data)) {
throw new PipedrivePersonServiceException(
sprintf('%s::createContact Invalid contact data', get_class($this))
);
}
$identity = $this->formatContactIdentity($this->validator->getData());
return $this->client->post('createContact', $identity);
}
/**
* @param int $contactId
* @param array $data
*
* @throws PipedriveClientException
* @throws PipedriveConfigurationException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
* @throws PipedrivePersonServiceException
* @throws PipedriveValidatorException
*
* @return PipedriveServerResponseInterface
*/
public function updateContactById(int $contactId, array $data = []): PipedriveServerResponseInterface
{
if (!$this->validator->isValid($data)) {
throw new PipedrivePersonServiceException(
sprintf('%s::updateContactById Invalid contact data', get_class($this))
);
}
$identity = $this->formatContactIdentity($this->validator->getData());
return $this->client->put('updateContact', $identity, [$contactId]);
}
/**
* @param array $data
*
* @return array
*/
private function formatContactIdentity(array $data): array
{
$identity = [];
foreach ($data as $property => $value) {
if (in_array($property, ['email', 'phone'])) {
$identity[$property][] = [
'label' => 'other',
'value' => $value,
];
} else {
$identity[$property] = $value;
}
}
return $identity;
}
/**
* @param string $type
* @param array $options
*
* @throws PipedrivePersonServiceException
* @throws PipedriveMetaException
*
* @return PipedriveServerResponseInterface
*/
public function pullPersons(string $type = 'pipedrive_all', array $options = []): PipedriveServerResponseInterface
{
$options = array_merge($this->itemOption, $options);
if ('pipedrive_all' !== $type && !array_key_exists($type, $this->allowedSyncPeronOptions)) {
throw new PipedrivePersonServiceException(sprintf('Sync Type %s not allowed', $type));
}
$metaFile = sprintf('%s.json', $type);
if ($type !== 'pipedrive_all') {
$options = $this->addMetaOption($metaFile, $options);
}
/** @var PipedriveServerResponseInterface $response */
return $this->{$this->allowedSyncPeronOptions[$type]}($options);
}
/**
* @param int $contactId
*
* @throws PipedriveClientException
* @throws PipedriveHttpClientException
* @throws PipedriveMetaException
* @throws PipedriveConfigurationException
*
* @return PipedriveServerResponseInterface
*/
public function getContactById(int $contactId): PipedriveServerResponseInterface
{
return $this->client->read('getContactById', [], [$contactId]);
}
/**
* @param string $metaFile
* @param array $options
*
* @throws PipedriveMetaException
*
* @return array
*/
private function addMetaOption(string $metaFile, array $options) : array
{
$metaInfo = $this->metaReaderService->readFromFile($metaFile);
if (!empty($metaInfo)) {
$timeOffset = $metaInfo['timeOffset'];
$offset = strtotime($metaInfo['timeOffset']);
if ($offset !== false) {
$timeOffset = gmdate('Y-m-d H:i:s', $offset - 3600);
}
$options = array_merge($options, ['since_timestamp' => $timeOffset]);
}
return $options;
}
}
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Service;
use Psr\Http\Message\StreamInterface;
interface PipedriveServerResponseInterface
{
/**
* Returns the json response body
*
* @return array|mixed
*/
public function getJson();
/**
* Gets the response body.
*
* @return StreamInterface
*/
public function getBody(): StreamInterface;
/**
* Gets the response status code.
*
* @return int Status code.
*/
public function getStatusCode(): int;
/**
* Returns the error message
*
* @return string
*/
public function getError(): string;
/**
* Checks whether the call was successful or not
*
* @return bool
*/
public function isSuccess(): bool;
/**
* Retrieves Data from the response
*
* @return array
*/
public function getData(): array;
/**
* Retrieves Additional Data from the response
*
* @return array
*/
public function getAdditionalData(): array;
}
@@ -0,0 +1,141 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Validator;
use Xentral\Modules\Pipedrive\Exception\PipedriveValidatorException;
final class PipedriveDealValidator implements PipedriveValidatorInterface
{
/** @var string $rules */
private $rules;
/** @var array $data */
private $data;
/**
* PipedriveDealValidator constructor.
*
* @param string $rule
*/
public function __construct(string $rule = 'default')
{
$this->rules = $rule;
}
/**
* @inheritDoc
*/
public function isValid(array $data = []): bool
{
$this->data = $data;
$validatorMethod = 'validatorRule' . ucfirst($this->rules);
if (!method_exists($this, $validatorMethod)) {
throw new PipedriveValidatorException(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;
}
/**
* @inheritDoc
*/
public function validatorRuleDefault(): array
{
return [
'title' => [
'rule' => static function ($data) {
return !empty($data['title']) && is_string($data['title']);
},
'required' => true,
'message' => sprintf('%s should be a non empty String', 'Title'),
],
'value' => [
'rule' => static function ($data) {
return !empty($data['value']) && is_string($data['value']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'Deal Value'),
],
'currency' => [
'rule' => static function ($data) {
return !empty($data['currency']) && is_string($data['currency']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'currency'),
],
'user_id' => [
'rule' => static function ($data) {
return !empty($data['user_id']) && is_numeric($data['user_id']);
},
'required' => false,
'message' => sprintf('%d should be a non empty String', 'user_id'),
],
'person_id' => [
'rule' => static function ($data) {
return !empty($data['person_id']) && is_numeric($data['person_id']);
},
'required' => false,
'message' => sprintf('%d should be a non empty String', 'person_id'),
],
'stage_id' => [
'rule' => static function ($data) {
return !empty($data['stage_id']) && is_numeric($data['stage_id']);
},
'required' => false,
'message' => sprintf('%d should be a non empty String', 'stage_id'),
],
'probability' => [
'rule' => static function ($data) {
return !empty($data['probability']) && is_numeric($data['probability']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'probability'),
],
'status' => [
'rule' => static function ($data) {
return !empty($data['status']) && in_array($data['status'], ['open', 'won', 'lost', 'deleted']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'status'),
],
'lost_reason' => [
'rule' => static function ($data) {
return !empty($data['lost_reason']) && is_string($data['lost_reason']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'lost reason'),
],
];
}
/**
* @return array
*/
public function getData(): array
{
return array_filter(
$this->data,
static function ($value) {
return is_numeric($value) || (is_string($value) && trim($value) !== '');
}
);
}
}
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Validator;
class PipedrivePersonPropertyValidator implements PipedriveValidatorInterface
{
/**
* @inheritDoc
*/
public function isValid(array $data = []): bool
{
// TODO: Implement isValid() method.
}
/**
* @inheritDoc
*/
public function validatorRuleDefault(): array
{
// TODO: Implement validatorRuleDefault() method.
}
}
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Validator;
use Xentral\Modules\Pipedrive\Exception\PipedriveValidatorException;
final class PipedrivePersonValidator implements PipedriveValidatorInterface
{
/** @var string $rules */
private $rules;
/** @var array $data */
private $data;
/**
* PipedrivePersonValidator constructor.
*
* @param string $rules
*/
public function __construct(string $rules = 'default')
{
$this->rules = $rules;
}
/**
* @inheritDoc
*/
public function isValid(array $data = []): bool
{
$this->data = $data;
$validatorMethod = 'validatorRule' . ucfirst($this->rules);
if (!method_exists($this, $validatorMethod)) {
throw new PipedriveValidatorException(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;
}
/**
* @inheritDoc
*/
public function validatorRuleDefault(): array
{
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'),
],
'name' => [
'rule' => static function ($data) {
return !empty($data['name']) && is_string($data['name']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'Name'),
],
'first_name' => [
'rule' => static function ($data) {
return !empty($data['first_name']) && is_string($data['first_name']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'First name'),
],
'last_name' => [
'rule' => static function ($data) {
return !empty($data['last_name']) && is_string($data['last_name']);
},
'required' => false,
'message' => sprintf('%s should be a non empty String', 'Last name'),
],
'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'),
],
'label' => [
'rule' => static function ($data) {
return !empty($data['label']) && is_numeric($data['label']);
},
'required' => false,
'message' => sprintf('%s should be a non empty numeric', 'Pipedrive-Label:'),
],
];
}
/**
* @return array
*/
public function getData(): array
{
return array_filter(
$this->data,
static function ($value) {
return is_numeric($value) || (is_string($value) && trim($value) !== '');
}
);
}
}
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Validator;
use Xentral\Modules\Pipedrive\Exception\PipedriveValidatorException;
interface PipedriveValidatorInterface
{
/**
* Checks whether the given data is valid or not
*
* @param array $data
*
* @throws PipedriveValidatorException
*
* @return bool
*/
public function isValid(array $data = []): bool;
/**
* Defines the default validator data object
*
* @return array
*/
public function validatorRuleDefault(): array;
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Wrapper;
use erpAPI;
final class PipedriveAddAddressRoleWrapper
{
/** @var erpAPI $erp */
private $erp;
/**
* @param erpAPI $erp
*/
public function __construct(erpAPI $erp)
{
$this->erp = $erp;
}
/**
* @param int $contactId
* @param int $groupId
*
* @return void
*/
public function add(int $contactId, int $groupId): void
{
$this->erp->AddRolleZuAdresse($contactId, 'Mitglied', 'von', 'Gruppe', $groupId);
}
}
@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Pipedrive\Wrapper;
use Xentral\Components\Database\Database;
final class PipedriveResubmissionWrapper
{
/** @var Database $db */
private $db;
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param array $deal
*
* @return int
*/
public function addResubmission(array $deal): int
{
$this->db->perform(
'INSERT INTO `wiedervorlage` (`bezeichnung`,
`datum_angelegt`,
`zeit_angelegt`,
`datum_erinnerung`,
`zeit_erinnerung`,
`stages`,
`chance`,
`beschreibung`,
`ergebnis`)
VALUES(:bezeichnung,
:datum_angelegt,
:zeit_angelegt,
:datum_erinnerung,
:zeit_erinnerung,
:stages,
:chance, \'\', \'\'
)',
$deal
);
return $this->db->lastInsertId();
}
/**
* @param int $resubmissionId
* @param array $deal
*
* @return void
*/
public function updateResubmission(int $resubmissionId, array $deal): void
{
$sql = sprintf(
'UPDATE `wiedervorlage`
SET `chance` = :chance, `bezeichnung` = :bezeichnung, `datum_angelegt` = :datum_angelegt,
`zeit_angelegt` = :zeit_angelegt, `datum_erinnerung` = :datum_erinnerung,
`zeit_erinnerung` = :zeit_erinnerung, `stages` = :stages
WHERE id = %d',
$resubmissionId
);
$this->db->perform($sql, $deal);
}
/**
* @param string $name
* @param string $shortName
* @param int $project
*
* @return int
*/
public function addResubmissionView(string $name, string $shortName, int $project = 0): int
{
$this->db->perform(
'INSERT INTO `wiedervorlage_view` (`name`, `shortname`, `project`, `active`)
VALUES (:name, :desc_short, :project, 1)',
[
'name' => $name,
'desc_short' => $shortName,
'project' => $project,
]
);
return $this->db->lastInsertId();
}
/**
* @param array $stage
*
* @return int
*/
public function addResubmissionStage(array $stage): int
{
$this->db->perform(
'INSERT INTO `wiedervorlage_stages` (`kurzbezeichnung`, `name`,
`stageausblenden`, `sort`, `view`, `ausblenden`)
VALUES(:desc, :name,:enabled, :position,:wiedervorlage_view_id,:ausblenden)',
$stage
);
return $this->db->lastInsertId();
}
}
@@ -0,0 +1,96 @@
var PipedriveModule = 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_pd_xt').on('click', function (event) {
me.syncAccount();
event.preventDefault();
});
$('#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();
});
$('#addresses_interval-checkbox').add('#deals_interval-checkbox').on('change', function () {
me.setReadOnly(this);
})
},
setReadOnly: function(src) {
var id = $(src).attr('id');
var idExploded = id.split('-');
$('#'+ idExploded[0]).prop('readonly',!$('#'+ idExploded[0]).prop('readonly'));
},
showMatchingSetting: function () {
$('.deal-system').removeClass('hd-invisible').find('select').prop('disabled', false);
},
hideMatchingSetting: function () {
$('.deal-system').addClass('hd-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 = $('#hd-configurator-form');
$form.action = 'index.php?module=pipedrive&action=apikey';
$form.submit();
}
};
return {
init: me.init,
addApiKey: me.addApiKey
};
}(jQuery);
$(function () {
if ($('#sync_pd_xt').length > 0) {
PipedriveModule.init();
}
});