<?php
/**
* Class MailChimp_WooCommerce_Subscriber_Sync
*/
class MailChimp_WooCommerce_Subscriber_Sync extends Mailchimp_Woocommerce_Job
{
public $data = array();
/**
* SubscriberSync constructor.
* Pass in the raw data from the webhook from Mailchimp
* @param $data
*/
public function __construct($data)
{
$this->data = (array) $data;
}
/**
* @return bool|null
*/
public function handle()
{
try {
// if the store is not properly connected to Mailchimp - we need to skip this.
if (!mailchimp_is_configured()) {
mailchimp_log('subscriber_sync', 'Mailchimp is not configured, can not process job.');
return null;
}
// grab the hook type, and the new data
list($hook_type, $data, $failed) = $this->parseInputData();
// extract the service ids from the data we get
list ($service_id, $email) = $this->extractServiceIDs($data);
// ignore the empty submissions or certain events or emails
if ($this->hasInvalidEvent($hook_type, $failed, $data)) {
mailchimp_log('subscriber_sync', 'Webhook has invalid event', compact('email'));
return false;
}
if ($this->shouldIgnoreEmail($email)) {
mailchimp_log('subscriber_sync', 'Webhook is ignoring email', compact('email'));
return false;
}
// if hook type is 'subscribe' that means we need ot subscribe them
$subscribe = $hook_type === 'subscribe';
// if we don't have a user by email
if (!($user = get_user_by('email', $email))) {
// if the user is not found and we should create new customers
return ($subscribe && $this->shouldCreateNewCustomers()) ?
(bool) $this->createNewCustomer($email) :
false;
}
try {
$handled_key = "subscriber_sync.{$service_id}.handled";
// see if we've saved a service call in the last 30 minutes.
$handled = mailchimp_get_transient($handled_key);
// if we've got the subscriber sync id and it's the same as the previous submission, just skip out now.
if ($handled === $subscribe) {
mailchimp_log('subscriber_sync', "didn't need to do anything");
return true;
}
// if they unsubscribed, we need to put a cache on this because it's causing issues in the
// shopify webhooks for some reason being re-subscribed.
if (!$subscribe) {
// update the cached status just in case this is causing trouble with the webhook.
$hashed = md5(trim(strtolower($email)));
// tell the webhooks that we've just synced this customer with a certain status.
mailchimp_set_transient("{$hashed}.subscriber_sync", array('time' => time(), 'status' => false), 90);
}
// update the user meta to show the proper value.
update_user_meta($user->ID, 'mailchimp_woocommerce_is_subscribed', $subscribe);
// cache it for 90 seconds to be used above.
mailchimp_set_transient($handled_key, $subscribe, 90);
mailchimp_log('webhook', "Subscriber Sync :: {$hook_type} :: {$email}", array(
'subscribed' => $subscribe,
'user_id' => $user->ID,
));
return true;
} catch (Exception $e) {
$error = $e->getMessage();
mailchimp_error('webhook', "Updating Subscriber Status :: MC service ID {$service_id} :: {$hook_type} :: {$error}");
return false;
}
} catch (Throwable $e) {
mailchimp_error('webhook', $e->getMessage(), array(
'data' => isset($data) && $data ? json_encode($data) : null
));
}
return false;
}
/**
* @param $data
* @return array
*/
private function extractServiceIDs($data)
{
if (is_object($data)) {
$service_id = isset($data->web_id) ?
$data->web_id :
(isset($data->id) ? $data->id : null);
$email = isset($data->email) ? $data->email : null;
return array($service_id, $email);
} else {
$service_id = isset($data['web_id']) ? $data['web_id'] : false;
if (!$service_id) {
$service_id = isset($data['id']) ? $data['id'] : false;
}
$email = isset($data['email']) ? $data['email'] : false;
return array($service_id, $email);
}
}
/**
* @return array
*/
private function parseInputData()
{
$hook_type = isset($this->data['type']) ? $this->data['type'] : 'certainly_not';
$data = isset($this->data['data']) ? $this->data['data'] : [];
$failed = false;
$allowed_hooks = array('subscribe' => true, 'unsubscribe' => true,);
if (!is_string($hook_type) || !isset($allowed_hooks[$hook_type])) {
$failed = true;
}
return array($hook_type, $data, $failed);
}
/**
* @param $email
*
* @return int|WP_Error
* @throws MailChimp_WooCommerce_Error
* @throws MailChimp_WooCommerce_RateLimitError
* @throws MailChimp_WooCommerce_ServerError
*/
private function createNewCustomer($email)
{
$member = mailchimp_get_api()->member(mailchimp_get_list_id(), $email);
$first_name = !empty($member['merge_fields']['FNAME']) ? $member['merge_fields']['FNAME'] : 'Guest';
$last_name = !empty($member['merge_fields']['LNAME']) ? $member['merge_fields']['LNAME'] : 'Customer';
if (empty($first_name)) $first_name = null;
if (empty($last_name)) $last_name = null;
// TODO maybe use the registration method and keep a record for when the user is verified later
$user = wp_create_user(strtolower($email), wp_generate_password(), strtolower($email));
// subscribe them because this function only runs for subscribers.
update_user_meta($user, 'mailchimp_woocommerce_is_subscribed', true);
// if we have a first and last name from the MC account, just use that.
if ($first_name && $last_name) {
wp_update_user(array(
'ID' => $user,
'first_name' => $first_name,
'last_name' => $last_name
));
}
mailchimp_log('webhook', "CREATED CUSTOMER :: {$email} :: {$first_name} {$last_name}");
return $user;
}
/**
* @return false
*/
private function shouldCreateNewCustomers()
{
// maybe we add a setting for this in the UI and use this here.
return false;
}
/**
* @param $email
* @return bool
*/
private function shouldIgnoreEmail($email)
{
return mailchimp_string_contains($email, array(
'forgotten.mailchimp.com'
));
}
/**
* @param $hook_type
* @param $failed
* @param $data
* @return bool
*/
private function hasInvalidEvent($hook_type, $failed, $data)
{
if (empty($hook_type) || empty($data)) {
return true;
}
// if the flag is failed, or deleted, don't do anything.
if ($failed || $hook_type === 'deleted' || $hook_type === 'delete') {
return true;
}
return false;
}
}