Files
kupshop/bundles/KupShop/MailerLiteBundle/MailerLite.php
2025-08-02 16:30:27 +02:00

620 lines
25 KiB
PHP

<?php
namespace KupShop\MailerLiteBundle;
use KupShop\KupShopBundle\Config;
class MailerLite
{
public $api_key;
public $groupID;
public $version;
public $group;
public $error;
public $limit = 5000;
public const VERSION = 2;
public $custom_fields = [
['Registered', 'TEXT'], ['Currency', 'TEXT'],
['User Groups', 'TEXT'], ['User Price Level', 'TEXT'],
['Greeting Name', 'TEXT'], ['Greeting Surname', 'TEXT'], ['Sex', 'TEXT'],
['Orders Count', 'NUMBER'], ['Orders Total', 'NUMBER'], ['Last Order Date', 'DATE'],
['Last Year Orders Count', 'NUMBER'], ['Last Year Orders Total', 'NUMBER'],
['Bonus Points', 'NUMBER', \Modules::BONUS_PROGRAM],
];
public $webhooks = ['subscriber.unsubscribe' => '_mailerlite/webhook/'];
public $upgrade_message;
public function __construct()
{
$settings = \Settings::getDefault();
if (isset($settings['mailerlite'])) {
$this->api_key = $settings['mailerlite']['api_key'];
$this->groupID = $settings['mailerlite']['group'];
if (isset($settings['mailerlite']['version'])) {
$this->version = $settings['mailerlite']['version'];
}
}
if ($this->api_key && $this->groupID) {
} else {
$this->error = 'MailerLite modul je špatně nastavený!';
}
}
public function getSubscriberGroup()
{
if ($this->api_key && $this->groupID) {
$response = $this->execCurl('https://api.mailerlite.com/api/v2/groups/'.$this->groupID, 5);
if (!$this->error) {
$group_response = json_decode($response, true);
if (!empty($group_response['error'])) {
$this->error = 'MailerLite: Group Error: '.$group_response['error']['message'];
} else {
$this->group = $group_response;
}
}
} else {
$this->error = 'MailerLite modul je špatně nastavený!';
}
}
public function upgradeCustomFields()
{
if ($this->error) {
$this->upgrade_message[] = $this->error;
}
if ($this->api_key) {
$response = $this->execCurl('https://api.mailerlite.com/api/v2/fields', 5);
$fields = json_decode($response, true);
if (!empty($fields['error'])) {
$this->upgrade_message[] = 'MailerLite: Fields Error: '.$fields['error']['message'];
} else {
$fields_error = false;
$fields_titles = array_flip(array_column($fields, 'title'));
foreach ($this->custom_fields as $customField) {
if (isset($customField[2]) && !findModule($customField[2])) {
$this->upgrade_message[] = 'Field skipped - '.$customField[0].' - module not found - '.$customField[2].'; ';
continue;
}
if (!array_key_exists($customField[0], $fields_titles)) {
$new_field = '{"title": "'.$customField[0].'", "type": "'.$customField[1].'"}';
$response = $this->execCurl('https://api.mailerlite.com/api/v2/fields', 5, 'POST', $new_field);
$field_response = json_decode($response, true);
if (!empty($field_response['error'])) {
$fields_error = true;
$this->upgrade_message[] = 'MailerLite: Fields Error: '.$field_response['error']['message'];
} else {
$this->upgrade_message[] = 'New field created - '.$field_response['title'].', '.$field_response['id'].', '.$field_response['key'].'; ';
}
}
}
if (!$fields_error) {
$settings = \Settings::getDefault();
if ($this->version < self::VERSION) {
$mailerlite_settings = ['version' => self::VERSION];
$settings->updateValue('mailerlite', $mailerlite_settings);
$this->version = $settings['mailerlite']['version'];
$this->upgrade_message[] = 'MailerLite: version upgraded to \''.$this->version.'\' ';
}
return true;
}
}
}
return false;
}
public function upgradeWebhooks()
{
if ($this->error) {
$this->upgrade_message[] = $this->error;
}
if ($this->api_key) {
$response = $this->execCurl('https://api.mailerlite.com/api/v2/webhooks', 5);
$webhooks = json_decode($response, true);
if (!empty($webhooks['error'])) {
$this->upgrade_message[] = 'MailerLite: Webhooks Error: '.$webhooks['error']['message'];
} else {
$webhooks_error = false;
$domain = Config::get()['Addr']['full'];
$webhooks = $webhooks['webhooks'];
foreach ($webhooks as $webhook) {
$webhook_type = $webhook['event'];
if (array_key_exists($webhook_type, $this->webhooks)) {
$url = $domain.$this->webhooks[$webhook_type];
if ($webhook['url'] != $url) {
$response = $this->execCurl('https://api.mailerlite.com/api/v2/webhooks/'.$webhook['id'], 5, 'DELETE');
$webhook_response = json_decode($response, true);
if (!empty($webhook_response['error'])) {
$webhooks_error = true;
$this->upgrade_message[] = 'MailerLite: Webhooks Error: '.$webhook_response['error']['message'];
} else {
$this->upgrade_message[] = 'Webhook deleted - '.$webhook['id'].', '.$webhook_type.', '.$webhook['url'].'; ';
}
}
}
}
foreach ($this->webhooks as $event => $url) {
$new_webhook = '{"event": "'.$event.'", "url": "'.$domain.$url.'"}';
$response = $this->execCurl('https://api.mailerlite.com/api/v2/webhooks/', 5, 'POST', $new_webhook);
$webhook_response = json_decode($response, true);
if (!empty($webhook_response['error'])) {
$webhooks_error = true;
$this->upgrade_message[] = 'MailerLite: Webhooks Error: '.$webhook_response['error']['message'];
} else {
$this->upgrade_message[] = 'New webhook created - '.$webhook_response['id'].', '.$webhook_response['event'].', '.$webhook_response['url'].'; ';
}
}
return !$webhooks_error;
}
}
return false;
}
public function execCurl($url, $timeout = 50, $http_method = 'GET', $post_data = null)
{
$curl = curl_init();
curl_setopt_array(
$curl,
[
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => $http_method,
CURLOPT_HTTPHEADER => [
'content-type: application/json',
'x-mailerlite-apikey: '.$this->api_key,
],
]
);
if ($post_data) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
}
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
$this->error = 'MailerLite: cURL Error: '.$err;
}
return $response;
}
public function updateUser($user, $newsletter)
{
$mailerlite_data_array['email'] = $user->email;
$cur_date = date('Y-m-d H:i:s');
if ($newsletter == 'Y') {
$mailerlite_data_array['type'] = 'active';
$mailerlite_data_array['date_subscribe'] = $cur_date;
$user_data = $this->getKupShopUsers([$user->id]);
if ($user_data['subscribers_count'] == 1) {
$mailerlite_data_array += reset($user_data['subscribers']);
} elseif ($user_data['unsubscribed_count'] == 1) {
$mailerlite_data_array += reset($user_data['unsubscribed']);
}
$mailerlite_data_array['resubscribe'] = true;
} else {
$mailerlite_data_array['type'] = 'unsubscribed';
$mailerlite_data_array['date_unsubscribe'] = $cur_date;
}
$mailerlite_data = $this->prepareExportData($mailerlite_data_array);
$response = $this->execCurl('https://api.mailerlite.com/api/v2/groups/'.$this->groupID.'/subscribers', 10, 'POST', $mailerlite_data);
if ($this->error) {
return false;
}
$subscriber_response = json_decode($response, true);
if (!empty($subscriber_response['error'])) {
$this->error = 'MailerLite: updateUser Error: '.$subscriber_response['error']['message'];
return false;
}
return true;
}
public function getMailerLiteSubscribers($type = 'Unsubscribed', $page = null, $filter_date = null)
{
$data = [];
$get_params = '?limit='.$this->limit;
if ($filter_date && ($type == 'Unsubscribed')) {
$get_params .= '&filters[date_unsubscribe][$gte]='.$filter_date;
$data['filter_date'] = $filter_date;
}
if (!is_null($page)) {
if ($page > 0) {
$get_params .= '&offset='.($page * $this->limit);
}
}
$response = $this->execCurl('https://api.mailerlite.com/api/v2/groups/'.$this->groupID.'/subscribers/'.strtolower($type).$get_params);
if ($this->error) {
$data['err'] = $this->error;
} else {
$subscribers = json_decode($response, true);
if (is_null($subscribers)) {
$data['err'] = 'MailerLite: Subscribers Error: '.$response;
} else {
if (!empty($subscribers['error'])) {
$data['err'] = 'MailerLite: Subscribers Error: '.$subscribers['error']['message'];
} else {
$emails = array_column($subscribers, 'email');
$subscribers = array_combine($emails, $subscribers);
$data['subscribers_count'] = count($subscribers);
$data['subscribers'] = $subscribers;
}
}
}
return $data;
}
public function getMailerLiteSubscribers_All($type = 'Unsubscribed')
{
$data['subscribers_count'] = 0;
$data['subscribers'] = [];
$page = 0;
$next_page = $this->getMailerLiteSubscribers($type, $page);
while (!empty($next_page['subscribers_count']) && ($next_page['subscribers_count'] > 0)) {
$data['subscribers_count'] += $next_page['subscribers_count'];
$data['subscribers'] += $next_page['subscribers'];
$page++;
$next_page = $this->getMailerLiteSubscribers($type, $page);
}
if (!empty($next_page['err'])) {
$data['err'] = $next_page['err'];
}
return $data;
}
public function MailerliteUnsubscribe($users_data)
{
$data = [];
$cur_date = date('Y-m-d H:i:s');
$batch_requests['requests'] = [];
$i = 0;
foreach ($users_data as $email => $value) {
$i++;
$mailerlite_request['method'] = 'POST';
$mailerlite_request['path'] = '/api/v2/groups/'.$this->groupID.'/subscribers';
$mailerlite_request['body']['email'] = $email;
$mailerlite_request['body']['type'] = 'unsubscribed';
$mailerlite_request['body']['date_unsubscribe'] = $cur_date;
$batch_requests['requests'][] = $mailerlite_request;
// if ($i == 50) {
// break; // There is a limit of maximum 50 requests per single batch.
// }
}
/* $batch_requests_json = json_encode($batch_requests);
$response = $this->execCurl('https://api.mailerlite.com/api/v2/batch', 30, 'POST', $batch_requests_json);
if ($this->error) {
$data['err'] = $this->error;
} else {
$batch_response = json_decode($response, true);
if (!empty($batch_response['error'])) {
$data['err'] = 'MailerLite: Unsubscribe Error: '.$batch_response['error']['message'];
} else {
$data['success'] = $batch_response;
}
}
*/
// if ($data['err']) {
// if (strstr($data['err'], 'A website error has occurred.')) {
// 'batch' is not working - A website error has occurred. The website administrator has been notified of the issue. Sorry for the temporary inconvenience
// send one by one
$data['err'] = '';
$unsubscribed_count = 0;
foreach ($batch_requests['requests'] as $mailerlite_request) {
$mailerlite_request_json = json_encode($mailerlite_request['body']);
$response = $this->execCurl('https://api.mailerlite.com/api/v2/groups/'.$this->groupID.'/subscribers', 5, 'POST', $mailerlite_request_json);
if ($this->error) {
$data['err'] = $this->error;
} else {
$unsubscribe_response = json_decode($response, true);
if (!empty($unsubscribe_response['error'])) {
$data['err'] = 'MailerLite: Unsubscribe Error: '.$unsubscribe_response['error']['message'];
} else {
if ($unsubscribe_response['type'] == 'unsubscribed') {
$unsubscribed_count++;
}
}
}
if ($data['err']) {
break;
}
}
if ($unsubscribed_count > 0) {
$data['success'] = 'MailerliteUnsubscribe: '.$unsubscribed_count;
}
// }
// }
return $data;
}
public function MailerliteSubscribe($users_data)
{
$data = [];
if (!empty($users_data)) {
$subscribers = '{"subscribers":['.implode(',', $users_data).'], "resubscribe":true}';
$response = $this->execCurl('https://api.mailerlite.com/api/v2/groups/'.$this->groupID.'/subscribers/import', 300, 'POST', $subscribers);
if ($this->error) {
$data['err'] = $this->error;
} else {
$export_result = json_decode($response, true);
if (!empty($export_result['error'])) {
$data['err'] = 'MailerLite: Subscribe Error: '.$export_result['error']['message'];
} else {
$export_success_str = 'Export: imported = '.count($export_result['imported'] ?? []);
$export_success_str .= ', updated = '.count($export_result['updated'] ?? []);
$export_success_str .= ', unchanged = '.count($export_result['unchanged'] ?? []);
$export_success_str .= ', errors = '.count($export_result['errors'] ?? []);
$data['success'] = $export_success_str;
}
}
}
return $data;
}
public function getKupShopUsers($user_ids = null)
{
$data = [];
if (findModule(\Modules::CURRENCIES)) {
$currency = 'u.currency';
} else {
$currency = "NULL AS 'currency'";
}
$sql = sqlQueryBuilder()
->select('u.id', 'LOWER(u.email) as email', 'u.get_news',
'u.figure as `registered`', 'u.name', 'u.surname as `last_name`',
'u.firm as `company`', 'u.date_reg', 'u.date_updated',
'l.name as user_price_level', 'u.country', $currency,
'(SELECT CONCAT("\"", GROUP_CONCAT(ug.name SEPARATOR "\", \""), "\"")
FROM users_groups_relations ugr LEFT JOIN users_groups ug ON ugr.id_group = ug.id
WHERE ugr.id_user = u.id) AS user_groups',
'COUNT(o.id) AS orders_count',
'ROUND(SUM(o.total_price*o.currency_rate)) AS orders_total',
'MAX(o.date_created) AS last_order_date',
'SUM(o.date_created >= DATE_SUB(current_date, INTERVAL 1 YEAR)) as last_year_orders_count',
'ROUND(SUM(o.total_price*o.currency_rate*(o.date_created >= DATE_SUB(current_date, INTERVAL 1 YEAR)))) as last_year_orders_total')
->from('users', 'u')
->leftJoin('u', 'users_dealer_price_level', 'udpl', 'u.id = udpl.id_user')
->leftJoin('u', 'price_levels', 'l', 'udpl.id_price_level = l.id')
->leftJoin('u', 'orders', 'o', 'u.email = o.invoice_email AND o.status_storno=0')
->groupBy('u.id')
->orderBy('u.date_updated', 'DESC');
if (findModule(\Modules::BONUS_PROGRAM)) {
$sql->addSelect('(SELECT SUM(bp.points)
FROM bonus_points bp
WHERE bp.id_user = u.id AND bp.status = "active") AS bonus_points');
}
if (!is_null($user_ids)) {
$sql->where(\Query\Operator::inIntArray($user_ids, 'u.id'));
}
$qb = $sql->execute();
$users = $qb->fetchAll();
$users_emails = array_column($users, 'email');
$users = array_combine($users_emails, $users);
$subscribers = array_filter($users, function ($v) {return $v['get_news'] == 'Y'; });
$unsubscribed = array_filter($users, function ($v) {return $v['get_news'] == 'N'; });
$data['subscribers'] = $subscribers;
$data['unsubscribed'] = $unsubscribed;
$data['subscribers_count'] = count($data['subscribers']);
$data['unsubscribed_count'] = count($data['unsubscribed']);
return $data;
}
public function ShopUnsubscribe($users_data)
{
$data = [];
if (!empty($users_data)) {
$qb = sqlQueryBuilder()->update('users')
->set('get_news', ':news')->setParameter('news', 'N')
->set('date_unsubscribe', 'NOW()')
->set('date_updated', 'NOW()')
->where(\Query\Operator::inIntArray($users_data, 'id'))
->andWhere('get_news=\'Y\'')
->execute();
$data['success'] = 'ShopUnsubscribe: '.$qb;
}
return $data;
}
public function ShopSubscribe($users_data)
{
$data = [];
if (!empty($users_data)) {
$qb = sqlQueryBuilder()->update('users')
->set('get_news', ':news')->setParameter('news', 'Y')
->where(\Query\Operator::inIntArray($users_data, 'id'))
->execute();
$data['success'] = 'ShopSubscribe: '.$qb;
}
return $data;
}
public function Synchronize($filter_date = null)
{
$success = null;
$error = null;
if ($filter_date) {
$data['Unsubscribed'] = $this->getMailerLiteSubscribers('Unsubscribed', null, $filter_date);
} else {
$data['Unsubscribed'] = $this->getMailerLiteSubscribers_All('Unsubscribed');
}
if (array_key_exists('err', $data['Unsubscribed'])) {
$error[] = $data['Unsubscribed']['err'];
}
$data['Junk'] = $this->getMailerLiteSubscribers('Junk');
if (!empty($data['Junk']['subscribers_count'])) {
if ($error) {
$data['Unsubscribed'] = $data['Junk'];
} else {
$data['Unsubscribed']['subscribers_count'] += $data['Junk']['subscribers_count'];
$data['Unsubscribed']['subscribers'] += $data['Junk']['subscribers'];
}
}
$data['Bounced'] = $this->getMailerLiteSubscribers('Bounced');
if (!empty($data['Bounced']['subscribers_count'])) {
if ($error && empty($data['Unsubscribed'])) {
$data['Unsubscribed'] = $data['Bounced'];
} else {
$data['Unsubscribed']['subscribers_count'] += $data['Bounced']['subscribers_count'];
$data['Unsubscribed']['subscribers'] += $data['Bounced']['subscribers'];
}
}
$data['ShopUsers'] = $this->getKupShopUsers(); // all
if (!empty($data['Unsubscribed']['subscribers_count'])) {
$mailerlite_unsubscribed = $data['Unsubscribed']['subscribers'];
$shop_users_subscribers = $data['ShopUsers']['subscribers'];
$array_unsubscribe = array_intersect_key($shop_users_subscribers, $mailerlite_unsubscribed);
if ($array_unsubscribe) {
// Unsubscribe on Shop
$ids = array_column($array_unsubscribe, 'id');
$result = $this->ShopUnsubscribe($ids);
if (!empty($result['success'])) {
$success[] = $result['success'];
}
if (!empty($result['error'])) {
$error[] = $result['error'];
}
}
}
$user_ids = null;
if ($filter_date) {
$sql = sqlQueryBuilder()
->select('u.id')
->from('users', 'u')
->where('u.date_updated >= "'.$filter_date.'" ');
$qb = $sql->execute();
$user_ids = $qb->fetchAll();
$user_ids = array_column($user_ids, 'id');
$sql = sqlQueryBuilder()
->select('DISTINCT o.id_user')
->from('orders', 'o')
->where('o.id_user IS NOT NULL')
->andWhere('(o.date_created >= "'.$filter_date.'") OR (o.date_created >= DATE_SUB(current_date, INTERVAL 370 DAY) AND o.date_created < DATE_SUB(current_date, INTERVAL 1 YEAR) )');
$qb = $sql->execute();
$user_ids2 = $qb->fetchAll();
$user_ids2 = array_column($user_ids2, 'id_user');
$user_ids = array_unique(array_merge($user_ids, $user_ids2));
}
$data['ShopUsers'] = $this->getKupShopUsers($user_ids);
if ($data['ShopUsers']['subscribers_count']) {
// Subscribe on MailerLite
$shop_users_subscribers = $data['ShopUsers']['subscribers'];
foreach (array_chunk($shop_users_subscribers, 10000) as $chunk) {
$users_data = array_map([$this, 'prepareExportData'], $chunk);
$result = $this->MailerliteSubscribe($users_data);
if (!empty($result['success'])) {
$success[] = $result['success'];
}
if (!empty($result['error'])) {
$error[] = $result['error'];
}
}
}
if ($data['ShopUsers']['unsubscribed_count']) {
$shop_users_unsubscribed = $data['ShopUsers']['unsubscribed'];
$mailerlite_unsubscribed = $data['Unsubscribed']['subscribers'] ?? [];
$array_unsubscribe = array_diff_key($shop_users_unsubscribed, $mailerlite_unsubscribed);
if ($array_unsubscribe) {
// Unsubscribe on MailerLite
$result = $this->MailerliteUnsubscribe($array_unsubscribe);
if (!empty($result['success'])) {
$success[] = $result['success'];
}
if (!empty($result['error'])) {
$error[] = $result['error'];
}
}
}
$result = null;
if ($success) {
$result['success'] = $success;
}
if ($error) {
$result['error'] = $error;
}
if (empty($success) && empty($error)) {
$result['success'][] = 'Nothing to Synchronize';
}
return $result;
}
public static function prepareExportData($subscriber)
{
$export_data = null;
$export_data['email'] = $subscriber['email'];
foreach ($subscriber as $key => $value) {
if (in_array($key, ['email', 'type', 'date_subscribe', 'date_unsubscribe', 'resubscribe'])) {
$export_data[$key] = $value;
} else {
$export_data['fields'][$key] = $value;
}
}
$unsubscribed = (isset($subscriber['type']) && ($subscriber['type'] == 'unsubscribed'));
if (!$unsubscribed) {
$greeting = \Greeting::getGreeting($subscriber['name'], $subscriber['last_name'], 'name');
$export_data['fields']['greeting_name'] = $greeting['greeting'];
$export_data['fields']['sex'] = (!empty($greeting['sex']) ? $greeting['sex'] : '');
$greeting = \Greeting::getGreeting($subscriber['name'], $subscriber['last_name'], 'surname');
$export_data['fields']['greeting_surname'] = $greeting['greeting'];
}
$export_str = json_encode($export_data, 256);
return $export_str;
}
}