323 lines
12 KiB
PHP
323 lines
12 KiB
PHP
<?php
|
|
|
|
namespace FluentCrm\App\Modules\AbandonCart;
|
|
|
|
use FluentCrm\App\Modules\AbandonCart\Drivers\DriverManager;
|
|
use FluentCrm\App\Models\FunnelMetric;
|
|
use FluentCrm\App\Models\FunnelSubscriber;
|
|
use FluentCrm\App\Services\Funnel\FunnelProcessor;
|
|
use FluentCrm\App\Services\Libs\ConditionAssessor;
|
|
use FluentCrm\Framework\Support\Arr;
|
|
|
|
class AbandonCartRunner
|
|
{
|
|
public function runAbandonCart(AbandonCartModel $abandonCart)
|
|
{
|
|
if ($abandonCart->status != 'draft') {
|
|
return false;
|
|
}
|
|
|
|
$driver = DriverManager::getDriver($abandonCart->provider);
|
|
|
|
if (!$driver) {
|
|
$abandonCart->status = 'skipped';
|
|
$abandonCart->note = 'No driver found for provider: ' . $abandonCart->provider;
|
|
$abandonCart->save();
|
|
return $abandonCart;
|
|
}
|
|
|
|
if ($driver->isWithinCoolOffPeriod($abandonCart)) {
|
|
$abandonCart->status = 'skipped';
|
|
$abandonCart->note = 'Under Cool Off Period';
|
|
$abandonCart->save();
|
|
return $abandonCart;
|
|
}
|
|
|
|
$automationData = $this->getEligibleAutomation($abandonCart);
|
|
|
|
if (!$automationData) {
|
|
$abandonCart->status = 'skipped';
|
|
$abandonCart->note = 'No automation found for this cart';
|
|
$abandonCart->save();
|
|
return $abandonCart;
|
|
}
|
|
|
|
if (is_wp_error($automationData)) {
|
|
$abandonCart->status = 'skipped';
|
|
$abandonCart->note = $automationData->get_error_message();
|
|
$abandonCart->save();
|
|
return $abandonCart;
|
|
}
|
|
|
|
$automation = $automationData['automation'];
|
|
$contact = $automationData['contact'];
|
|
|
|
if (!$contact) {
|
|
$contact = $this->createContactFromCart($abandonCart);
|
|
}
|
|
|
|
// Check if exit
|
|
$existingFunnelSub = FunnelSubscriber::where('funnel_id', $automation->id)
|
|
->where('subscriber_id', $contact->id)
|
|
->first();
|
|
|
|
if ($existingFunnelSub) {
|
|
FunnelMetric::where('funnel_id', $existingFunnelSub->funnel_id)
|
|
->where('subscriber_id', $contact->id)
|
|
->delete();
|
|
|
|
$existingFunnelSub->delete();
|
|
}
|
|
|
|
$settings = AbCartHelper::getSettings();
|
|
if ($attachLists = Arr::get($settings, 'lists_on_cart_abandoned', [])) {
|
|
$contact->attachLists($attachLists);
|
|
}
|
|
|
|
if ($attachTags = Arr::get($settings, 'tags_on_cart_abandoned', [])) {
|
|
$contact->attachTags($attachTags);
|
|
}
|
|
|
|
$abandonCart->status = 'processing';
|
|
$abandonCart->automation_id = $automation->id;
|
|
$abandonCart->contact_id = $contact->id;
|
|
$abandonCart->abandoned_at = current_time('mysql');
|
|
$abandonCart->save();
|
|
|
|
(new FunnelProcessor())->startFunnelSequence($automation, [], [
|
|
'source_trigger_name' => $driver->getTriggerName(),
|
|
'source_ref_id' => $abandonCart->id
|
|
], $contact);
|
|
|
|
return $abandonCart;
|
|
}
|
|
|
|
public function getEligibleAutomation(AbandonCartModel $abandonCart)
|
|
{
|
|
$automations = AbCartHelper::getSortedAutomations($abandonCart->provider);
|
|
|
|
if (!$automations) {
|
|
return new \WP_Error('no_automation', 'No automation found for this cart');
|
|
}
|
|
|
|
$existingContact = fluentCrmApi('contacts')->getContact($abandonCart->email);
|
|
|
|
$processableStatuses = ['subscribed', 'transactional'];
|
|
|
|
if ($existingContact && !in_array($existingContact->status, $processableStatuses)) {
|
|
return new \WP_Error('contact_unsubscribed', 'Contact status is not allowed to process this cart');
|
|
}
|
|
|
|
$items = Arr::get($abandonCart->cart, 'cart_contents', []);
|
|
|
|
// Use driver for provider-specific condition data extraction
|
|
$driver = DriverManager::getDriver($abandonCart->provider);
|
|
|
|
if ($driver) {
|
|
$conditionData = $driver->extractCartConditionData($abandonCart);
|
|
$productIds = $conditionData['product_ids'];
|
|
$categoryIds = $conditionData['category_ids'];
|
|
} else {
|
|
$productIds = [];
|
|
$categoryIds = [];
|
|
foreach ($items as $item) {
|
|
$productIds[] = $item['product_id'];
|
|
}
|
|
}
|
|
|
|
$cartData = [
|
|
'cart_total' => $abandonCart->total,
|
|
'cart_items_count' => count($items),
|
|
'cart_items' => $productIds,
|
|
'cart_items_categories' => $categoryIds,
|
|
];
|
|
|
|
$contact = $existingContact;
|
|
|
|
foreach ($automations as $automation) {
|
|
$conditions = (array)$automation->conditions;
|
|
if (Arr::get($conditions, 'require_subscribed') === 'yes' && (!$existingContact || $existingContact->status != 'subscribed')) {
|
|
continue;
|
|
}
|
|
|
|
$existingFunnelSub = null;
|
|
$checkActive = Arr::get($conditions, 'active_once') === 'yes';
|
|
if ($checkActive && $existingContact) {
|
|
// check if the contact is already has an automation for this one
|
|
$existingFunnelSub = FunnelSubscriber::where('funnel_id', $automation->id)
|
|
->where('subscriber_id', $existingContact->id)
|
|
->first();
|
|
|
|
if ($existingFunnelSub && $existingFunnelSub->status == 'active') {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$cartConditions = array_filter(Arr::get($conditions, 'cart_conditions', []));
|
|
|
|
if (!$cartConditions) {
|
|
return [
|
|
'automation' => $automation,
|
|
'contact' => $contact
|
|
];
|
|
}
|
|
|
|
if (!$contact && $this->hasContactConditions($cartConditions)) {
|
|
$contact = $this->createContactFromCart($abandonCart);
|
|
}
|
|
|
|
if ($this->assessConditionGroups($cartConditions, $contact, $cartData)) {
|
|
return [
|
|
'automation' => $automation,
|
|
'contact' => $contact
|
|
];
|
|
}
|
|
}
|
|
|
|
return new \WP_Error('no_automation', 'No automation found for this cart based on condition match');
|
|
}
|
|
|
|
protected function createContactFromCart(AbandonCartModel $abandonCart)
|
|
{
|
|
$customData = Arr::get($abandonCart->cart, 'customer_data', []);
|
|
$cartSettings = AbCartHelper::getSettings();
|
|
$contactData = array_filter([
|
|
'email' => $abandonCart->email,
|
|
'first_name' => Arr::get($customData, 'billingAddress.first_name'),
|
|
'last_name' => Arr::get($customData, 'billingAddress.last_name'),
|
|
'user_id' => $abandonCart->user_id,
|
|
'full_name' => $abandonCart->full_name,
|
|
'status' => Arr::get($cartSettings, 'new_contact_status', 'transactional'),
|
|
'address_line_1' => Arr::get($customData, 'billingAddress.address_1'),
|
|
'address_line_2' => Arr::get($customData, 'billingAddress.address_2'),
|
|
'city' => Arr::get($customData, 'billingAddress.city'),
|
|
'state' => Arr::get($customData, 'billingAddress.state'),
|
|
'postal_code' => Arr::get($customData, 'billingAddress.postcode'),
|
|
'country' => Arr::get($customData, 'billingAddress.country'),
|
|
'phone' => Arr::get($customData, 'billingAddress.phone'),
|
|
'tags' => Arr::get($cartSettings, 'tags_on_cart_abandoned', []),
|
|
'lists' => Arr::get($cartSettings, 'lists_on_cart_abandoned', [])
|
|
]);
|
|
|
|
|
|
return fluentCrmApi('contacts')->createOrUpdate($contactData);
|
|
}
|
|
|
|
protected function hasContactConditions($conditions)
|
|
{
|
|
$cartGroupKeys = DriverManager::getAllSmartCodeGroupKeys();
|
|
|
|
foreach ($conditions as $conditionGroup) {
|
|
foreach ($conditionGroup as $filterItem) {
|
|
if (count($filterItem['source']) != 2 || empty($filterItem['source'][0]) || empty($filterItem['source'][1]) || empty($filterItem['operator'])) {
|
|
continue;
|
|
}
|
|
|
|
$provider = $filterItem['source'][0];
|
|
|
|
if (!in_array($provider, $cartGroupKeys)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected function assessConditionGroups($conditionGroups, $subscriber, $cartData = [])
|
|
{
|
|
foreach ($conditionGroups as $conditions) {
|
|
$result = $this->assessConditions($conditions, $subscriber, $cartData);
|
|
if ($result) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected function assessConditions($conditions, $subscriber, $cartData = [])
|
|
{
|
|
if (!defined('FLUENTCAMPAIGN_DIR_FILE')) {
|
|
return true;
|
|
}
|
|
|
|
$helperClass = 'FluentCampaign\App\Services\Funnel\Conditions\FunnelConditionHelper';
|
|
|
|
if (!class_exists($helperClass)) {
|
|
// Free-only: no advanced condition engine, accept all conditions
|
|
return true;
|
|
}
|
|
|
|
$formattedGroups = $helperClass::formatConditionGroups($conditions);
|
|
|
|
foreach ($formattedGroups as $groupName => $group) {
|
|
if ($groupName == 'subscriber') {
|
|
|
|
if (!$subscriber) {
|
|
return false;
|
|
}
|
|
|
|
$subscriberData = $subscriber->toArray();
|
|
if (!ConditionAssessor::matchAllConditions($group, $subscriberData)) {
|
|
return false;
|
|
}
|
|
} else if ($groupName == 'custom_fields') {
|
|
if (!$subscriber) {
|
|
return false;
|
|
}
|
|
$customData = $subscriber->custom_fields();
|
|
if (!ConditionAssessor::matchAllConditions($group, $customData)) {
|
|
return false;
|
|
}
|
|
} else if ($groupName == 'segment') {
|
|
if (!$subscriber) {
|
|
return false;
|
|
}
|
|
if (!$helperClass::assessSegmentConditions($group, $subscriber)) {
|
|
return false;
|
|
}
|
|
} else if ($groupName == 'activities') {
|
|
if (!$subscriber) {
|
|
return false;
|
|
}
|
|
if (!$helperClass::assessActivities($group, $subscriber)) {
|
|
return false;
|
|
}
|
|
} else if ($groupName == 'event_tracking') {
|
|
if (!$subscriber) {
|
|
return false;
|
|
}
|
|
if (!$helperClass::assessEventTrackingConditions($group, $subscriber)) {
|
|
return false;
|
|
}
|
|
} else if ($groupName == 'other') {
|
|
if (!$subscriber) {
|
|
return false;
|
|
}
|
|
foreach ($group as $condition) {
|
|
$prop = $condition['data_key'];
|
|
if (!apply_filters('fluentcrm_automation_custom_condition_assert_' . $prop, true, $condition, $subscriber, null, null)) {
|
|
return false;
|
|
}
|
|
}
|
|
} else if (DriverManager::getDriverByGroupKey($groupName)) {
|
|
if (!ConditionAssessor::matchAllConditions($group, $cartData)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!$subscriber) {
|
|
return false;
|
|
}
|
|
|
|
$result = apply_filters("fluentcrm_automation_conditions_assess_$groupName", true, $group, $subscriber, null, null);
|
|
if (!$result) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|