Initial commit

This commit is contained in:
gustavooth
2026-07-23 22:56:30 -03:00
commit 22a1006598
1816 changed files with 410742 additions and 0 deletions
@@ -0,0 +1,136 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\Benchmarks;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\CartHelper;
use FluentCrm\App\Services\Funnel\BaseBenchMark;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\App\Services\Funnel\FunnelProcessor;
use FluentCrm\Framework\Support\Arr;
class OrderSuccessBenchmark extends BaseBenchMark
{
public function __construct()
{
$this->triggerName = 'fluent_cart/order_paid_done';
$this->actionArgNum = 3;
$this->priority = 20;
parent::__construct();
}
public function getBlock()
{
return [
'title' => __('Order Paid (Payment/Subscription)', 'fluent-crm'),
'description' => __('This will run once new order will be placed as paid in FluentCRM', 'fluent-crm'),
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><g id="surface1"><path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 2.398438 0 L 21.601562 0 C 22.925781 0 24 1.074219 24 2.398438 L 24 21.601562 C 24 22.925781 22.925781 24 21.601562 24 L 2.398438 24 C 1.074219 24 0 22.925781 0 21.601562 L 0 2.398438 C 0 1.074219 1.074219 0 2.398438 0 Z M 2.398438 0 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 10.925781 16.476562 L 3.769531 16.476562 L 4.894531 13.878906 C 5.222656 13.117188 5.972656 12.625 6.804688 12.625 L 15.328125 12.625 L 14.746094 13.964844 C 14.085938 15.488281 12.585938 16.476562 10.925781 16.476562 Z M 10.925781 16.476562 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 16.851562 11.394531 L 6.789062 11.394531 L 7.367188 10.054688 C 8.027344 8.53125 9.53125 7.542969 11.191406 7.542969 L 19.886719 7.542969 L 18.761719 10.140625 C 18.433594 10.902344 17.683594 11.394531 16.851562 11.394531 Z M 16.851562 11.394531 "/></g></svg>',
'settings' => [
'product_ids' => [],
'product_categories' => [],
'type' => 'required',
'can_enter' => 'yes'
]
];
}
public function getDefaultSettings()
{
return [
'product_ids' => [],
'product_categories' => [],
'type' => 'required',
'can_enter' => 'yes'
];
}
public function getBlockFields($funnel)
{
return [
'title' => __('New Order Paid in FluentCart', 'fluent-crm'),
'sub_title' => __('This will run once new order will be placed as paid in FluentCart', 'fluent-crm'),
'fields' => [
'product_ids' => [
'type' => 'rest_selector',
'label' => __('Target Products', 'fluent-crm'),
'option_key' => 'fluent_cart_products',
'is_multiple' => true,
'help' => __('Select for which products this automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any product purchase', 'fluent-crm')
],
'product_categories' => [
'type' => 'rest_selector',
'label' => __('Or Target Product Categories', 'fluent-crm'),
'option_key' => 'fluent_cart_product_categories',
'is_multiple' => true,
'help' => __('Select for which product category the automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any category products', 'fluent-crm')
],
'type' => $this->benchmarkTypeField(),
'can_enter' => $this->canEnterField()
]
];
}
public function handle($benchMark, $originalArgs)
{
$orderData = $originalArgs[0] ?? [];
$order = Arr::get($orderData, 'order', []);
$customer = Arr::get($orderData, 'customer', []);
$settings = $benchMark->settings;;
if (!$this->checkConditions($settings, $order)) {
return;
}
$subscriberData = CartHelper::prepareSubscriberData($customer);
if (!is_email($subscriberData['email'])) {
return;
}
$subscriberData['status'] = 'subscribed';
$subscriber = FunnelHelper::createOrUpdateContact($subscriberData);
$funnelProcessor = new FunnelProcessor();
$funnelProcessor->startFunnelFromSequencePoint($benchMark, $subscriber, [], [
'benchmark_value' => $order->total_paid, // converted to cents
'benchmark_currency' => $order->currency,
]);
}
private function checkConditions($conditions, $order)
{
$orderItems = Arr::get($order, 'order_items', []);
// Post IDs of ordered products are the product IDs in FluentCart
$orderedProductIds = [];
foreach ($orderItems as $item) {
$productId = $item->post_id;
if ($productId) {
$orderedProductIds[] = $productId;
}
}
$orderProductCategories = CartHelper::getProductCategoriesByIds($orderedProductIds);
$selectedProductIds = Arr::get($conditions, 'product_ids', []);
$selectedProductCategories = Arr::get($conditions, 'product_categories', []);
// If no products or categories are selected, return true
if (empty($selectedProductIds) && empty($selectedProductCategories)) {
return true;
}
// Check for matches in product IDs and categories
$productMatch = !empty($selectedProductIds) && !empty(array_intersect($selectedProductIds, $orderedProductIds));
$categoryMatch = !empty($selectedProductCategories) && !empty(array_intersect($selectedProductCategories, $orderProductCategories));
// Return true if either matches
return $productMatch || $categoryMatch;
}
}
@@ -0,0 +1,220 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart;
use FluentCart\Api\Taxonomy;
use FluentCart\App\Helpers\Status;
use FluentCart\App\Models\Coupon;
use FluentCart\App\Models\Product;
use FluentCart\App\Models\ProductVariation;
use FluentCart\Framework\Database\Orm\Collection;
use FluentCart\Framework\Support\Str;
use FluentCart\App\Models\Customer;
class CartHelper
{
public static function getFluentCartProducts($items, $search, $ids = [])
{
$search = (string)$search;
$ids = is_array($ids) ? $ids : [];
try {
$productQuery = Product::query()->published();
if ($search) {
$productQuery->where('post_title', 'like', '%' . $search . '%');
}
$queried = $productQuery
->orderBy('post_title')
->limit(50)
->get(['ID', 'post_title']);
$options = [];
$pushedIds = [];
foreach ($queried as $product) {
$options[] = [
'id' => $product->ID,
'title' => $product->ID . '# ' . $product->post_title,
];
$pushedIds[] = $product->ID;
}
if ($ids) {
$remaining = array_diff($ids, $pushedIds);
if ($remaining) {
$extraProducts = Product::query()->published()->whereIn('ID', $remaining)->get(['ID', 'post_title']);
foreach ($extraProducts as $product) {
$options[] = [
'id' => $product->ID,
'title' => $product->ID . '# ' . $product->post_title,
];
}
}
}
return $options;
} catch (\Exception $e) {
return [];
}
}
public static function getFluentCartCoupons($items, $search, $ids)
{
try {
$coupons = Coupon::all(['id', 'title'])
->map(function ($coupon) {
return [
'id' => $coupon->id,
'title' => $coupon->title,
];
})
->toArray();
} catch (\Exception $e) {
$coupons = [];
}
return $coupons;
}
public static function getFluentCartProductCategories($items, $search, $ids)
{
try {
$taxonomies = Taxonomy::getTaxonomies();
$taxonomies = Collection::make($taxonomies)
->map(function ($taxonomy) {
return [
'name' => $taxonomy,
'label' => Str::headline($taxonomy),
'terms' => Taxonomy::getFormattedTerms($taxonomy),
];
});
$categories = Collection::make($taxonomies['product-categories']['terms'])
->map(function ($term) {
return [
'id' => $term['value'],
'title' => $term['label'],
];
})
->toArray();
} catch (\Exception $e) {
$categories = [];
}
return $categories;
}
public static function getProductCategoriesByIds($ids)
{
try {
$products = Product::with('wp_terms')->whereIn('id', $ids)->get();
$categories = $products->flatMap(function ($product) {
return $product->wp_terms->pluck('term_taxonomy_id');
})->unique()->values()->toArray();
} catch (\Exception $e) {
$categories = [];
}
return $categories;
}
public static function getFluentCartSubscriptionProducts($items, $search, $ids)
{
$search = (string)$search;
$ids = is_array($ids) ? $ids : [];
try {
$variationQuery = ProductVariation::query()
->where('payment_type', 'subscription')
->where('item_status', 'active');
if ($search) {
$variationQuery->where('variation_title', 'like', '%' . $search . '%');
}
$productIds = $variationQuery->pluck('post_id')->unique()->slice(0, 50)->values();
$pushedIds = $productIds->toArray();
if ($ids) {
$appendIds = array_diff($ids, $pushedIds);
if ($appendIds) {
$productIds = $productIds->merge($appendIds);
}
}
if ($productIds->isEmpty()) {
return [];
}
$products = Product::query()
->published()
->whereIn('ID', $productIds->toArray())
->orderBy('post_title')
->get(['ID', 'post_title']);
$formatted = [];
foreach ($products as $product) {
$formatted[] = [
'id' => $product->ID,
'title' => $product->ID . '# ' . $product->post_title,
];
}
return $formatted;
} catch (\Exception $e) {
return [];
}
}
public static function prepareSubscriberData($customer)
{
if(!is_object($customer)) {
$customer = (object) $customer;
}
return [
'email' => $customer->email,
'first_name' => $customer->first_name,
'last_name' => $customer->last_name,
'full_name' => $customer->first_name . ' ' . $customer->last_name,
'user_id' => $customer->user_id,
'postal_code' => $customer->postcode,
'country' => $customer->country,
'state' => $customer->state,
'city' => $customer->city,
'phone' => $customer->phone,
];
}
public static function getCustomersByProductIds($productIds, $offset = 0, $limit = 100)
{
$customers = [];
try {
$customers = Customer::query()->whereHas('success_order_items', function ($q) use ($productIds) {
$q->whereIn('post_id', $productIds);
})->offset($offset)->limit($limit)->get();
} catch (\Exception $e) {
}
return $customers;
}
public static function getPurchasedProductsByCustomerId($customerId)
{
$productIds = [];
try {
$orderIds = fluentCrmDb()->table('fct_orders')
->where('customer_id', $customerId)
->pluck('id');
$productIds = fluentCrmDb()->table('fct_order_items')
->whereIn('order_id', $orderIds)
->pluck('post_id');
} catch (\Exception $e) {
}
return $productIds;
}
}
@@ -0,0 +1,332 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart;
use Automattic\WooCommerce\Blocks\BlockTypes\Cart;
use FluentCrm\App\Models\Subscriber;
use FluentCrm\App\Models\Tag;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\App\Services\Helper;
class CartImporter
{
// Customers processed per request (page size)
const PER_PAGE = 150;
// public function __construct()
// {
// $this->importKey = 'fluent_cart';
// parent::__construct();
// }
private static function getPluginName()
{
return 'FluentCart';
}
// public function getInfo()
// {
// return [
// 'label' => $this->getPluginName(),
// 'logo' => fluentCrmMix('images/woo.svg'),
// 'disabled' => false
// ];
// }
public static function processUserDriver($config, $request)
{
$summary = $request->get('summary');
if ($summary) {
// Initialize defaults to avoid undefined variables in fallback returns
$formattedCustomers = [];
$total = 0;
$config = $request->get('config');
$type = Arr::get($config, 'import_type');
if ($type == 'customers_sync') {
// $customersQuery = fluentCrmDb()->table('wc_customer_lookup');
// $total = $customersQuery->count();
// $formattedUsers = $customersQuery->select(['first_name', 'last_name', 'email'])->limit(5)->get();
// foreach ($formattedUsers as $formattedUser) {
// $formattedUser->name = trim($formattedUser->first_name . ' ' . $formattedUser->last_name);
// }
} else if ($type == 'product_tags') {
$productIds = [];
foreach ($config['product_type_maps'] as $map) {
$productIds[] = absint($map['field_key']);
}
// get customers who purchased these products from fluent cart
// get orderIds from
$customers = CartHelper::getCustomersByProductIds($productIds, 0, 100000);
$total = count($customers);
fluentcrm_update_option('_fluent_cart_product_tag_total_count', $total);
$formattedCustomers = [];
foreach ($customers as $customer) {
$formattedCustomers[] = [
'name' => $customer->first_name . ' ' . $customer->last_name,
'email' => $customer->email
];
}
return [
'import_info' => [
'subscribers' => $formattedCustomers,
'total' => $total,
'has_tag_config' => false,
'has_list_config' => true,
'has_status_config' => false,
'has_update_config' => false,
'has_silent_config' => true
]
];
}
return [
'import_info' => [
'subscribers' => $formattedCustomers,
'total' => $total,
'has_tag_config' => true,
'has_list_config' => true,
'has_status_config' => true,
'has_update_config' => false,
'has_silent_config' => true
]
];
}
$importType = 'customers_sync';
/* translators: %s: the external commerce plugin name */
$importTitle = sprintf(__('Sync %s Customers Now', 'fluent-crm'), self::getPluginName());
if(defined('FLUENTCART_VERSION')) {
$importType = 'product_tags';
/* translators: %s: the external commerce plugin name */
$importTitle = sprintf(__('Import %s Customers Now', 'fluent-crm'), self::getPluginName());
}
$configFields = [
'config' => [
'import_type' => $importType,
'product_type_maps' => [
[
'field_key' => '',
'field_value' => ''
]
]
],
'fields' => [
'product_type_maps' => [
'label' => __('Please map your Product and associate FluentCRM Tags', 'fluent-crm'),
'type' => 'form-many-drop-down-mapper',
/* translators: %s: the external commerce plugin name */
'local_label' => sprintf(__('Select %s Product', 'fluent-crm'), self::getPluginName()),
'remote_label' => __('Select FluentCRM Tag that will be applied', 'fluent-crm'),
/* translators: %s: the external commerce plugin name */
'local_placeholder' => sprintf(__('Select %s Product', 'fluent-crm'), self::getPluginName()),
'remote_placeholder' => __('Select FluentCRM Tag', 'fluent-crm'),
'field_ajax_selector' => [
'option_key' => 'fluent_cart_products'
],
'value_option_selector' => [
'option_key' => 'tags',
'creatable' => true
],
'dependency' => [
'depends_on' => 'import_type',
'operator' => '=',
'value' => 'product_tags'
]
],
'sync_import_html' => [
'type' => 'html-viewer',
'heading' => __('FluentCart Data Sync', 'fluent-crm'),
'info' => __('You can sync all your FluentCart Customers into FluentCRM and all future customers and purchase data will be synced.', 'fluent-crm').'<br />'.__('After this sync you can import by product by product and provide appropriate tags', 'fluent-crm'),
'dependency' => [
'depends_on' => 'import_type',
'operator' => '=',
'value' => 'customers_sync'
]
]
],
'labels' => [
'step_2' => __('Next [Review Data]', 'fluent-crm'),
'step_3' => $importTitle
]
];
return $configFields;
}
public static function importData($returnData, $config, $page)
{
$inputs = Arr::only($config, [
'lists', 'tags', 'status', 'double_optin_email', 'import_silently'
]);
$inputs = wp_parse_args($inputs, [
'lists' => [],
'tags' => [],
// keep backward compatibility but use `status` key everywhere
'status' => 'subscribed',
'double_optin_email' => 'no',
'import_silently' => 'yes'
]);
if (Arr::get($inputs, 'import_silently') == 'yes') {
if (!defined('FLUENTCRM_DISABLE_TAG_LIST_EVENTS')) {
define('FLUENTCRM_DISABLE_TAG_LIST_EVENTS', true);
}
}
$sendDoubleOptin = Arr::get($inputs, 'double_optin_email') == 'yes';
$contactStatus = Arr::get($inputs, 'status', 'subscribed');
$productTagMaps = [];
$productIds = [];
foreach ($config['product_type_maps'] as $map) {
$productId = absint($map['field_key']);
$productIds[] = $productId;
if (!isset($productTagMaps[$productId])) {
$productTagMaps[$productId] = [];
}
$productTagMaps[$productId][] = absint($map['field_value']);
}
$productIds = array_unique($productIds);
$startTime = time();
$runTime = 20; // seconds
// normalize and initialize paging
$perPage = self::PER_PAGE;
$page = max(1, absint($page));
// reset counters on first page to start a fresh run
if ($page === 1) {
fluentcrm_update_option('_fluent_cart_sync_count', 0);
fluentcrm_update_option('_fluent_cart_import_current_page', 1);
}
$customers = CartHelper::getCustomersByProductIds($productIds, ($page - 1) * $perPage, $perPage);
// Even if the current page has no customers (e.g., last sparse page),
// advance the page pointer to avoid getting stuck reprocessing the same page.
if (empty($customers)) {
fluentcrm_update_option('_fluent_cart_import_current_page', $page);
return self::getSyncStatus();
}
$importedCustomers = [];
// pushing all customers without tags
foreach ($customers as $customer) {
$subscribers = CartHelper::prepareSubscriberData($customer);
if($customer->user_id) {
$subscribers = Helper::getWPMapUserInfo($customer->user_id);
}
Subscriber::import(
[$subscribers],
[],
Arr::get($inputs, 'lists', []),
true,
$contactStatus,
$sendDoubleOptin
);
// keeping track of imported customers
$purchasedProducts = self::purchasedProductsOfImportedCustomer($customer->id, $productIds);
$assignedTags = self::mapCustomerTags($purchasedProducts, $productTagMaps);
$importedCustomers[] = [
'customer_id' => $customer->id,
'email' => $subscribers['email'],
'user_id' => $customer->user_id,
'purchased_products' => $purchasedProducts,
'tags' => $assignedTags
];
// update tags for the subscriber
if ($assignedTags) {
$importedSubscriber = Subscriber::where('email', $subscribers['email'])->first();
if ($importedSubscriber) {
$importedSubscriber->attachTags($assignedTags);
}
}
}
$totalSynced = count($importedCustomers) + (($page - 1) * $perPage);
fluentcrm_update_option('_fluent_cart_sync_count', $totalSynced);
fluentcrm_update_option('_fluent_cart_import_current_page', $page);
// check time limit
if (time() - $startTime > $runTime) {
return self::getSyncStatus();
}
return self::getSyncStatus();
}
private static function mapCustomerTags($purchasedProducts, $productTagMaps)
{
$assignedTags = [];
foreach ($purchasedProducts as $purchasedProduct) {
if (isset($productTagMaps[$purchasedProduct])) {
$assignedTags = array_merge($productTagMaps[$purchasedProduct], $assignedTags); //
}
}
return array_values(array_unique($assignedTags)); // return only unique tags
}
private static function purchasedProductsOfImportedCustomer($customerId, $productIds)
{
$allProductsIds = CartHelper::getPurchasedProductsByCustomerId($customerId);
// check in products
// getPurchasedProductsByCustomerId() returns a Collection via pluck(), convert to array for array_intersect()
return array_intersect($allProductsIds->toArray(), $productIds); // return only those products which are in productIds
}
private static function getSyncStatus()
{
$total = fluentcrm_get_option('_fluent_cart_product_tag_total_count', 0);
$perPage = self::PER_PAGE;
// Calculate page-based progress for the frontend
$totalPages = max(1, (int) ceil($total / max(1, $perPage)));
$currentPage = (int) fluentcrm_get_option('_fluent_cart_import_current_page', 1);
$currentPage = max(1, min($currentPage, $totalPages));
$hasMore = $currentPage < $totalPages;
return [
// Total number of pages to import
'page_total' => $totalPages,
// Total records for reference
'record_total' => $total,
'has_more' => $hasMore,
'current_page' => $currentPage,
'next_page' => $hasMore ? ($currentPage + 1) : 0,
'reload_page' => !$hasMore
];
}
}
@@ -0,0 +1,211 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart;
use FluentCart\App\Helpers\AddressHelper;
use FluentCrm\App\Services\AutoSubscribe;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\Framework\Support\Arr;
/**
* Adds a newsletter opt-in checkbox to the FluentCart checkout form and
* subscribes the customer to FluentCRM when the order is paid.
*
* Controlled by the 'FluentCart Checkout Subscription Field' panel in
* FluentCRM -> Settings -> General Settings (option key:
* fluent_cart_checkout_form_subscribe_settings, defined in the base plugin's
* AutoSubscribe service).
*/
class CheckoutSubscription
{
/**
* Form field name posted with the checkout request and the
* order meta key holding the captured opt-in state.
*/
const OPTIN_FIELD = '_fc_cart_checkout_subscribe';
/**
* Order meta flag preventing the same order from being processed twice.
*/
const PROCESSED_META = '_fc_cart_checkout_optin_processed';
public function init()
{
// Renders inside the checkout <form> on both the standard checkout
// page and the modal checkout (FormData picks the value up on submit)
add_action('fluent_cart/before_payment_methods', [$this, 'renderOptinCheckbox']);
// Fires while the order is being created from the checkout request —
// the only point where the raw POST data is available
add_action('fluent_cart/checkout/prepare_other_data', [$this, 'captureOptinState']);
// Async (Action Scheduler) hook recommended by FluentCart for
// third-party post-payment processing
add_action('fluent_cart/order_paid_done', [$this, 'maybeSubscribeContact'], 20);
}
protected function getSettings()
{
return (new AutoSubscribe())->getFluentCartCheckoutSettings();
}
/**
* Prints the opt-in checkbox above the payment methods section,
* using FluentCart's native checkbox markup classes.
*/
public function renderOptinCheckbox($data)
{
$settings = $this->getSettings();
if (Arr::get($settings, 'status') != 'yes') {
return;
}
if (Arr::get($settings, 'show_only_new') == 'yes') {
$contact = fluentcrm_get_current_contact();
if ($contact && $contact->status == 'subscribed') {
return;
}
}
$label = Arr::get($settings, 'checkbox_label');
if (!$label) {
$label = __('Sign me up for the newsletter!', 'fluent-crm');
}
$isChecked = Arr::get($settings, 'auto_checked') == 'yes';
?>
<div class="fct_checkout_form_section fcrm_checkout_subscribe" data-fct-checkout-form-section>
<div class="fct_form_section_body">
<label class="fct_input_label fct_input_label_checkbox" for="fcrm_cart_subscribe">
<input type="checkbox" class="fct-input fct-input-checkbox" id="fcrm_cart_subscribe"
name="<?php echo esc_attr(self::OPTIN_FIELD); ?>" value="1" <?php checked($isChecked); ?>>
<?php echo esc_html($label); ?>
</label>
</div>
</div>
<?php
}
/**
* Persists the posted opt-in state to order meta so the async
* order-paid handler can read it later. Missing value = unchecked.
*/
public function captureOptinState($eventData)
{
$order = Arr::get($eventData, 'order');
if (!$order || !is_object($order)) {
return;
}
$settings = $this->getSettings();
if (Arr::get($settings, 'status') != 'yes') {
return;
}
$isChecked = Arr::get((array)Arr::get($eventData, 'request_data', []), self::OPTIN_FIELD) == '1';
$order->updateMeta(self::OPTIN_FIELD, $isChecked ? '1' : '0');
}
/**
* Creates/updates the FluentCRM contact when a paid order has the
* opt-in flag. Runs async via fluent_cart/order_paid_done. Idempotent —
* payment retries or hook re-runs will not duplicate processing.
*/
public function maybeSubscribeContact($eventData)
{
$order = Arr::get($eventData, 'order');
$customer = Arr::get($eventData, 'customer');
if (!$order || !$customer) {
return false;
}
// Treat both the final marker and an in-flight claim as processed so
// serial retries of the paid-order event (e.g. Action Scheduler
// re-runs) cannot double-process. This read-then-write guard is not
// atomic, so it does not protect against truly concurrent invocations
$processedState = $order->getMeta(self::PROCESSED_META);
if ($processedState == 'yes' || $processedState == 'processing') {
return false;
}
if ($order->getMeta(self::OPTIN_FIELD) != '1') {
return false;
}
$settings = $this->getSettings();
if (Arr::get($settings, 'status') != 'yes') {
return false;
}
$email = sanitize_email($customer->email);
if (!$email || !is_email($email)) {
return false;
}
$orderBillingAddress = $order->billing_address;
$address1 = Arr::get($orderBillingAddress, 'address_1', '');
$address2 = Arr::get($orderBillingAddress, 'address_2', '');
$state = AddressHelper::getStateNameByCode($customer->state, $customer->country);
$subscriberData = [
'first_name' => sanitize_text_field($customer->first_name),
'last_name' => sanitize_text_field($customer->last_name),
'country' => sanitize_text_field($customer->country),
'state' => sanitize_text_field($state),
'city' => sanitize_text_field($customer->city),
'postal_code' => sanitize_text_field($customer->postcode),
'address_line_1' => sanitize_text_field($address1),
'address_line_2' => sanitize_text_field($address2),
'email' => $email
];
if ($listId = Arr::get($settings, 'target_list')) {
$subscriberData['lists'] = [$listId];
}
if ($tags = Arr::get($settings, 'target_tags')) {
$subscriberData['tags'] = $tags;
}
if (Arr::get($settings, 'double_optin') == 'yes') {
$subscriberData['status'] = 'pending';
} else {
$subscriberData['status'] = 'subscribed';
}
$subscriberData = apply_filters('fluent_crm/fluent_cart_checkout_auto_subscribe_data', $subscriberData, $order);
// Claim the order before contact creation / email side effects so an
// Action Scheduler retry re-entering this handler cannot run them twice
$order->updateMeta(self::PROCESSED_META, 'processing');
try {
$contact = FunnelHelper::createOrUpdateContact($subscriberData);
if (!$contact) {
// Release the claim so a later retry can attempt processing again
$order->updateMeta(self::PROCESSED_META, '0');
return false;
}
if ($contact->status == 'pending') {
$contact->sendDoubleOptinEmail();
}
} catch (\Throwable $e) {
// Release the claim on failure too — otherwise the order would be
// stuck in 'processing' and permanently skipped by the guard above
$order->updateMeta(self::PROCESSED_META, '0');
throw $e;
}
$order->updateMeta(self::PROCESSED_META, 'yes');
return true;
}
}
@@ -0,0 +1,508 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart;
use FluentCart\Api\ModuleSettings;
use FluentCart\App\Helpers\Helper;
use FluentCart\App\Models\Customer;
use FluentCart\App\Models\Order;
use FluentCrm\App\Models\Subscriber;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\Benchmarks\OrderSuccessBenchmark;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\SmartCode\SmartCodeParser;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\SmartCode\SmartCodeRegister;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers\OrderCanceledTrigger;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers\OrderPaidTrigger;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers\OrderDeliveredTrigger;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers\OrderRefundedTrigger;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers\OrderShippedTrigger;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers\OrderStatusChangedTrigger;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers\SubscriptionActivatedTrigger;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers\SubscriptionCancelledTrigger;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers\SubscriptionEndOfTermTrigger;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers\SubscriptionExpiredTrigger;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers\SubscriptionRenewedTrigger;
class FluentCart
{
public function init()
{
$this->addAutomations();
$this->addHooks();
SmartCodeRegister::push();
(new RevenueTracker())->init();
}
public function addAutomations()
{
new OrderPaidTrigger();
new OrderShippedTrigger();
new OrderDeliveredTrigger();
new OrderRefundedTrigger();
new OrderCanceledTrigger();
// new OrderStatusChangedTrigger();
new SubscriptionExpiredTrigger();
//subscription activated
new SubscriptionActivatedTrigger();
//subscription cancelled
new SubscriptionCancelledTrigger();
//subscription renewed
new SubscriptionRenewedTrigger();
//subscription end of term(completed)
new SubscriptionEndOfTermTrigger();
// Goals
new OrderSuccessBenchmark();
}
public function addHooks()
{
add_filter('fluent_crm/get_import_driver_fluent_cart', [CartImporter::class, 'processUserDriver'], 10, 2);
add_filter('fluent_crm/post_import_driver_fluent_cart', [CartImporter::class, 'importData'], 10, 3);
add_filter('fluentcrm_ajax_options_fluent_cart_products', [$this, 'getProducts'], 10, 3);
add_filter('fluentcrm_ajax_options_fluent_cart_product_categories', [$this, 'getProductCategories'], 10, 3);
add_filter('fluentcrm_ajax_options_fluent_cart_subscription_products', [$this, 'getSubscriptionProducts'], 10, 3);
add_filter('fluent_crm/funnel_icons', [$this, 'addCartIcon'], 10 , 1);
add_filter('fluent_crm/purchase_history_fluent_cart', [$this, 'purchaseHistory'], 10, 2);
add_filter('fluent_crm/smartcode_group_callback_cart_order', [SmartCodeParser::class, 'parseCartOrder'], 10, 4);
add_filter('fluent_crm/smartcode_group_callback_cart_customer', [SmartCodeParser::class, 'parseCartCustomer'], 10, 4);
// add_filter('fluent_crm/smartcode_group_callback_cart_transaction', [SmartCodeParser::class, 'parseCartTransaction'], 10, 4);
add_filter('fluent_crm/smartcode_group_callback_cart_receipt', [SmartCodeParser::class, 'parseCartReceipt'], 10, 4);
add_filter('fluentcrm_automation_condition_groups', array($this, 'addAutomationConditions'), 10, 2);
add_filter('fluentcrm_automation_conditions_assess_fluent_cart', array($this, 'assessAutomationConditions'), 10, 3);
// add_filter('fluentcrm_automation_conditions_assess_woo_order', array($this, 'assessAutomationOrderConditions'), 10, 5);
}
public function getProducts($items, $search, $ids)
{
return CartHelper::getFluentCartProducts($items, $search, $ids);
}
public function getProductCategories($items, $search, $ids)
{
return CartHelper::getFluentCartProductCategories($items, $search, $ids);
}
public function getSubscriptionProducts($items, $search, $ids)
{
return CartHelper::getFluentCartSubscriptionProducts($items, $search, $ids);
}
public function addCartIcon($icons)
{
$icons['fluentcart'] = [
'svg' => '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="18" height="18" rx="1.8" fill="#00009F"/><path d="M9.19408 13.3557H3.82861L4.67063 11.4086C4.91784 10.8369 5.48112 10.4668 6.10395 10.4668H12.4955L12.0607 11.4722C11.5663 12.6155 10.4397 13.3557 9.19408 13.3557Z" fill="white"/><path d="M13.6389 9.54518H6.0918L6.52656 8.5398C7.02098 7.39646 8.14753 6.65625 9.39319 6.65625H15.9142L15.0722 8.60341C14.825 9.17507 14.2617 9.54518 13.6389 9.54518Z" fill="white"/></svg>',
];
return $icons;
}
public function purchaseHistory($data, $subscriber)
{
$customer = Customer::where('email', $subscriber->email)->first();
if (!$customer) {
return [];
}
$ordersQuery = Order::with('appliedCoupons')->where('customer_id', $customer->id);
$totalCount = $ordersQuery->count();
if (!$totalCount) {
return [];
}
// Pagination params (using super global to avoid dependency on request wrapper here)
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
if ($page < 1) { $page = 1; }
$perPage = isset($_GET['per_page']) ? (int)$_GET['per_page'] : 10;
if ($perPage < 1) { $perPage = 10; }
$orders = $ordersQuery
->orderBy('id', 'DESC')
->limit($perPage)
->offset(($page - 1) * $perPage)
->get();
// Use a helper method for formatting
$formattedOrders = $this->formatOrders($orders);
return [
'data' => $formattedOrders,
'total' => $totalCount,
'sidebar_html' => $this->getSidebarHtml($subscriber),
'after_html' => '',
'has_recount' => false,
'columns_config' => [
'order' => ['label' => __('Order', 'fluent-crm'), 'width' => '100px', 'sortable' => true, 'key' => 'id'],
'date' => ['label' => __('Date', 'fluent-crm'), 'sortable' => true, 'key' => 'created_at'],
// 'coupon' => ['label' => __('Coupon', 'fluent-crm'), 'sortable' => true, 'key' => 'created_at'],
'status' => ['label' => __('Status', 'fluent-crm'), 'width' => '140px', 'sortable' => false],
// 'payment' => ['label' => __('Payment', 'fluent-crm'), 'width' => '140px', 'sortable' => false, 'key' => 'payment_status'],
'total' => ['label' => __('Total', 'fluent-crm'), 'width' => '120px', 'sortable' => true, 'key' => 'total'],
'action' => ['label' => __('', 'fluent-crm'), 'width' => '100px', 'sortable' => false],
],
];
}
private function formatOrders($orders)
{
$formattedOrders = [];
foreach ($orders as $order) {
$orderActionHtml = '<a target="_blank" href="' . admin_url('admin.php?page=fluent-cart#/orders/' . $order->id . '/view') . '">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.5 5.5V7H4.75V15.25H13V11.5H14.5V16C14.5 16.1989 14.421 16.3897 14.2803 16.5303C14.1397 16.671 13.9489 16.75 13.75 16.75H4C3.80109 16.75 3.61032 16.671 3.46967 16.5303C3.32902 16.3897 3.25 16.1989 3.25 16V6.25C3.25 6.05109 3.32902 5.86032 3.46967 5.71967C3.61032 5.57902 3.80109 5.5 4 5.5H8.5ZM16.75 3.25V9.25H15.25V5.80975L9.40525 11.6553L8.34475 10.5948L14.1888 4.75H10.75V3.25H16.75Z" fill="#525866"/>
</svg>
</a>';
$coupons = implode(', ', array_column($order['appliedCoupons']->toArray(), 'code'));
$date = '<span class="order_id">#' . $order->id .'</span><span class="order_date">'.date_i18n(get_option('date_format'), strtotime($order->created_at)).'</span>';
$status = '<span class="fcrm_badge fcrm_badge_'.esc_attr($order->status).'">'. \FluentCrm\App\Services\Helper::getStatusText($order->status) .'</span>';
$formattedOrders[] = [
'date' => $date,
'status' => $status,
// 'payment' => $order->payment_status,
// 'coupons' => $coupons,
'total' => Helper::toDecimal($order->total_amount), // Adjust if using a different helper
'action' => $orderActionHtml,
];
}
return $formattedOrders;
}
private function getSidebarHtml($subscriber = null)
{
// We will build a similar widget like WooCommerce's purchase sidebar
// Show a quick customer summary + recently purchased products (flat list of items)
$customer = null;
if ($subscriber && !empty($subscriber->email)) {
$customer = Customer::where('email', $subscriber->email)->first();
}
if (!$customer) {
// Fallback: Just return the static block as before
return '<div class="fluent-crm-sidebar-content">'
. '<h3>' . __('Product History', 'fluent-crm') . '</h3>'
. '<p>' . __('View your purchase history from FluentCart.', 'fluent-crm') . '</p>'
. '</div>';
}
// Aggregate order stats
$ordersQuery = Order::where('customer_id', $customer->id);
$orderCount = (clone $ordersQuery)->count();
if (!$orderCount) {
return '<div class="fluent-crm-sidebar-content">'
. '<h3>' . __('Product History', 'fluent-crm') . '</h3>'
. '<p>' . __('No purchases found for this contact in FluentCart.', 'fluent-crm') . '</p>'
. '</div>';
}
$totalSpent = (clone $ordersQuery)->sum('total_amount');
$firstOrder = (clone $ordersQuery)->orderBy('id', 'ASC')->value('created_at');
$lastOrder = (clone $ordersQuery)->orderBy('id', 'DESC')->value('created_at');
// Fetch recent purchased items (latest 15 order items across latest orders)
// We'll eager load order_items for performance
$recentOrders = (clone $ordersQuery)
->with(['order_items' => function($q){
$q->orderBy('id', 'DESC');
}])
->orderBy('id', 'DESC')
->limit(200)
->get();
$items = [];
foreach ($recentOrders as $order) {
foreach ($order->order_items as $orderItem) {
$itemDisplayName = $orderItem->post_title;
if($orderItem->post_title != $orderItem->title) {
$itemDisplayName = $orderItem->post_title . ' - ' . $orderItem->title;
}
$items[] = [
'name' => $itemDisplayName,
'price' => isset($orderItem->line_total) ? Helper::toDecimal($orderItem->line_total) : 0,
'created_at' => $order->created_at,
'order_id' => $order->id,
];
if (count($items) >= 15) {
break 2; // Exit both loops once we have enough
}
}
}
$html = '<div class="fluent-crm-sidebar-content fc_payment_summary">';
$html .= '<h3 class="history_title">' . __('Order Summary', 'fluent-crm') . '</h3>';
$html .= '<div class="fc_history_widget"><ul class="fc_full_listed">';
$html .= '<li><span class="fc_list_sub">' . __('Total Orders', 'fluent-crm') . '</span><span class="fc_list_value">' . intval($orderCount) . '</span></li>';
$html .= '<li><span class="fc_list_sub">' . __('Total Spent', 'fluent-crm') . '</span><span class="fc_list_value">' . esc_html(Helper::toDecimal($totalSpent)) . '</span></li>';
if ($firstOrder) {
$html .= '<li><span class="fc_list_sub">' . __('First Order', 'fluent-crm') . '</span><span class="fc_list_value">' . date_i18n(get_option('date_format'), strtotime($firstOrder)) . '</span></li>';
}
if ($lastOrder) {
$html .= '<li><span class="fc_list_sub">' . __('Last Order', 'fluent-crm') . '</span><span class="fc_list_value">' . date_i18n(get_option('date_format'), strtotime($lastOrder)) . '</span></li>';
}
$html .= '</ul></div>';
$html .= '<h3 class="history_title">' . __('Purchased Products', 'fluent-crm') . '</h3>';
$html .= '<div class="fc_history_widget"><ul class="fc_full_listed max_height_550">';
foreach ($items as $item) {
$orderUrl = admin_url('admin.php?page=fluent-cart#/orders/' . $item['order_id'] . '/view');
$badges = '<span class="el-tag el-tag--primary">' . esc_html(Helper::toDecimal($item['price'])) . '</span>';
$badges .= '<span class="el-tag el-tag--primary"><a target="_blank" rel="noopener" href="' . esc_url($orderUrl) . '">' . date_i18n(get_option('date_format'), strtotime($item['created_at'])) . '</a></span>';
$html .= '<li class="fc_product_name">' . esc_html($item['name']) . ' ' . $badges . '</li>';
}
if (!$items) {
$html .= '<li>' . __('No purchased products found.', 'fluent-crm') . '</li>';
}
$html .= '</ul></div>';
$html .= '</div>';
return $html;
}
public function addAutomationConditions($groups)
{
$conditionItems = [
[
'value' => 'commerce_exist',
'label' => __('Is a customer?', 'fluent-crm'),
'type' => 'selections',
'is_multiple' => false,
'disable_values' => true,
'value_description' => __('This filter will check if a contact has at least one shop order or not', 'fluent-crm'),
'custom_operators' => [
'exist' => __('Yes', 'fluent-crm'),
'not_exist' => __('No', 'fluent-crm'),
]
],
[
'value' => 'ltv',
'label' => __('Lifetime Value', 'fluent-crm'),
'type' => 'numeric'
],
[
'value' => 'aov',
'label' => __('Average Order Value', 'fluent-crm'),
'type' => 'numeric',
],
[
'value' => 'first_purchase_date',
'label' => __('First Order Date', 'fluent-crm'),
'type' => 'dates'
],
[
'value' => 'last_purchase_date',
'label' => __('Last Order Date', 'fluent-crm'),
'type' => 'dates'
],
[
'value' => 'purchased_items',
'label' => __('Products', 'fluent-crm'),
'type' => 'selections',
'component' => 'product_selector',
'is_multiple' => true,
'custom_operators' => [
'exist' => __('purchased', 'fluent-crm'),
'not_exist' => __('not purchased', 'fluent-crm'),
],
'help' => __('Will filter the contacts who have at least one order', 'fluent-crm')
],
[
'value' => 'variation_purchased',
'label' => __('Product Variations', 'fluent-crm'),
'type' => 'cascade_selections',
'provider' => 'fct_variations',
'is_multiple' => true,
'value_description' => __('This filter will check if a contact has purchased at least one specific product variation or not', 'fluent-crm'),
'custom_operators' => [
'exist' => __('purchased', 'fluent-crm'),
'not_exist' => __('not purchased', 'fluent-crm'),
]
],
[
'value' => 'purchased_categories',
'label' => __('Product Categories', 'fluent-crm'),
'type' => 'selections',
'component' => 'tax_selector',
'taxonomy' => 'product-categories',
'is_multiple' => true,
'disabled' => true,
'help' => __('Will filter the contacts who have at least one order', 'fluent-crm'),
'custom_operators' => [
'exist' => __('purchased', 'fluent-crm'),
'not_exist' => __('not purchased', 'fluent-crm'),
]
],
[
'value' => 'commerce_coupons',
'label' => __('Used Coupons', 'fluent-crm'),
'type' => 'selections',
'component' => 'ajax_selector',
'option_key' => 'fct_coupons',
'is_multiple' => true,
'disabled' => true,
'custom_operators' => [
'exist' => __('in', 'fluent-crm'),
'not_exist' => __('not in', 'fluent-crm'),
],
'help' => __('Will filter the contacts who have at least one order', 'fluent-crm')
]
];
if (ModuleSettings::isActive('license')) {
$conditionItems[] = [
'value' => 'active_licenses',
'label' => __('Active Licenses', 'fluent-crm'),
'type' => 'selections',
'component' => 'product_selector',
'is_multiple' => true,
'custom_operators' => [
'exist' => __('have', 'fluent-crm'),
'not_exist' => __('do not have', 'fluent-crm'),
],
'help' => __('Will filter the contacts who have at least one active licenses or not', 'fluent-crm')
];
$conditionItems[] = [
'value' => 'active_variation_licenses',
'label' => __('Active Variation Licenses', 'fluent-crm'),
'type' => 'cascade_selections',
'provider' => 'fct_variations',
'is_multiple' => true,
'value_description' => __('This filter will check if a contact has at least one specific variation license or not', 'fluent-crm'),
'custom_operators' => [
'exist' => __('have', 'fluent-crm'),
'not_exist' => __('do not have', 'fluent-crm'),
]
];
$conditionItems[] = [
'value' => 'expired_licenses',
'label' => __('Expired Licenses', 'fluent-crm'),
'type' => 'selections',
'component' => 'product_selector',
'is_multiple' => true,
'custom_operators' => [
'exist' => __('have', 'fluent-crm'),
'not_exist' => __('do not have', 'fluent-crm'),
],
'help' => __('Will filter the contacts who have at least one expired licenses or not', 'fluent-crm')
];
$conditionItems[] = [
'value' => 'expired_variation_licenses',
'label' => __('Expired Variation Licenses', 'fluent-crm'),
'type' => 'cascade_selections',
'provider' => 'fct_variations',
'is_multiple' => true,
'value_description' => __('This filter will check if a contact has at least one specific variation expired license or not', 'fluent-crm'),
'custom_operators' => [
'exist' => __('have', 'fluent-crm'),
'not_exist' => __('do not have', 'fluent-crm'),
]
];
$conditionItems[] = [
'value' => 'license_exist',
'label' => __('Has any active license?', 'fluent-crm'),
'type' => 'selections',
'is_multiple' => false,
'disable_values' => true,
'value_description' => __('Check if contacts has any active license from any products', 'fluent-crm'),
'custom_operators' => [
'exist' => __('Yes', 'fluent-crm'),
'not_exist' => __('No', 'fluent-crm'),
]
];
}
$groups['fluent_cart'] = [
'label' => __('FluentCart', 'fluent-crm'),
'value' => 'fluent_cart',
'children' => $conditionItems
];
return $groups;
}
public function assessAutomationConditions($result, $conditions, $subscriber)
{
$legacyConditions = [];
// if (Commerce::isEnabled('woo')) {
$formattedConditions = [];
$commerceProps = [
'commerce_exist',
'ltv', // lifetime value
'aov', // average order value
'first_purchase_date',
'last_purchase_date',
'purchased_items', // products purchased
'variation_purchased', // product variations purchased
'purchased_categories', // product categories
'commerce_coupons', // used coupons
];
foreach ($conditions as $condition) {
$prop = $condition['data_key'];
$operator = $condition['operator'];
if (in_array($prop, $commerceProps)) {
$formattedConditions[] = [
'operator' => $operator,
'value' => $condition['data_value'],
'property' => $prop,
];
} else {
$legacyConditions[] = $condition;
}
}
if ($formattedConditions) {
$hasSubscriber = Subscriber::where('id', $subscriber->id)->where(function ($q) use ($formattedConditions) {
do_action_ref_array('fluentcrm_contacts_filter_fluent_cart', [&$q, $formattedConditions]);
})->first();
if (!$hasSubscriber) {
return false;
}
}
// } else {
// $legacyConditions = $conditions;
// }
if ($legacyConditions) {
$cartCustomer = Customer::query()
->where('email', $subscriber->email)
->when($subscriber->user_id, function ($q) use ($subscriber) {
return $q->orWhere('user_id', $subscriber->user_id);
})
->first();
if (!$cartCustomer) {
return false;
}
}
return $result;
}
}
@@ -0,0 +1,93 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart;
use FluentCrm\App\Models\Campaign;
use FluentCrm\App\Services\Helper as FluentCrmHelper;
use FluentCrm\Framework\Support\Arr;
/**
* Stamps the originating campaign id (`_fc_cid` cookie set by RedirectionHandler when an
* email link is clicked) onto each FluentCart order at creation time, then rolls the
* paid total into the `_campaign_revenue` campaign meta when the order is paid.
*
* This mirrors the WooCommerce/EDD revenue attribution flow used by
* CampaignAnalyticsController so the existing campaign analytics UI works for FluentCart.
*/
class RevenueTracker
{
public function init()
{
add_action('fluent_cart/order_created', [$this, 'attributeOrder'], 10, 1);
add_action('fluent_cart/order_paid', [$this, 'recordRevenue'], 10, 1);
}
/**
* Stamp the originating campaign id on the order while we still have access to
* the visitor's `fc_cid` cookie (the request runs in the user's browser context here;
* by the time `order_paid` fires from a gateway webhook the cookie is gone).
*/
public function attributeOrder($eventData)
{
$cid = $this->getCampaignIdFromCookie();
if (!$cid) {
return;
}
$order = Arr::get($eventData, 'order');
if (!$order || empty($order->id)) {
return;
}
if ($order->getMeta('_fc_cid')) {
return;
}
$order->updateMeta('_fc_cid', $cid);
}
/**
* On payment success, accumulate this order's total into the campaign's
* `_campaign_revenue` meta. Helper::recordCampaignRevenue handles dedup
* (same order_id won't be counted twice) and per-currency bucketing.
*/
public function recordRevenue($eventData)
{
$order = Arr::get($eventData, 'order');
if (!$order || empty($order->id)) {
return;
}
$cid = (int) $order->getMeta('_fc_cid');
if (!$cid) {
return;
}
// FluentCart stores monetary amounts as integer cents — same convention the
// Woo re-sync uses when writing into `_campaign_revenue`.
$amountCents = (int) $order->total_amount;
if ($amountCents <= 0) {
return;
}
$currency = $order->currency ? strtoupper($order->currency) : 'USD';
FluentCrmHelper::recordCampaignRevenue($cid, $amountCents, $order->id, $currency);
}
private function getCampaignIdFromCookie()
{
if (empty($_COOKIE['fc_cid'])) {
return 0;
}
$cid = (int) $_COOKIE['fc_cid'];
if ($cid <= 0) {
return 0;
}
$exists = Campaign::withoutGlobalScopes()->where('id', $cid)->exists();
return $exists ? $cid : 0;
}
}
@@ -0,0 +1,297 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\SmartCode;
use FluentCart\App\Helpers\Helper;
use FluentCart\App\Models\Order;
use FluentCart\App\Services\Payments\PaymentReceipt;
use FluentCart\App\Services\URL;
use FluentCrm\App\Models\FunnelSubscriber;
use FluentCart\Api\Resource\OrderResource;
class SmartCodeParser {
public static function parseCartOrder($code, $valueKey, $defaultValue, $subscriber)
{
$funnelSub = FunnelSubscriber::where('id', $subscriber->funnel_subscriber_id)->first();
try {
if (!$funnelSub) {
return $defaultValue;
}
$order = OrderResource::view($funnelSub['source_ref_id']);
} catch (\Exception $exception) {
return $defaultValue;
}
if (empty($order) || !isset($order['order'])) {
return $defaultValue;
}
return self::parseOrderProps($order['order'], $valueKey, $defaultValue);
}
public static function parseCartCustomer($code, $valueKey, $defaultValue, $subscriber)
{
// Try to load customer directly from Customer model
$customer = \FluentCart\App\Models\Customer::where('email', $subscriber->email)->first();
if ($customer) {
return self::parseCustomerProps($customer->toArray(), $valueKey, $defaultValue);
}
// Fallback: Load from order payload if direct model not found
try {
$funnelSub = FunnelSubscriber::where('id', $subscriber->funnel_subscriber_id)->first();
if (!$funnelSub) {
return $defaultValue;
}
$order = OrderResource::view($funnelSub['source_ref_id']);
} catch (\Exception $exception) {
return $defaultValue;
}
if (empty($order) || !isset($order['order']['customer'])) {
return $defaultValue;
}
// Ensure array structure
$orderCustomer = is_array($order['order']['customer'])
? $order['order']['customer']
: (method_exists($order['order']['customer'], 'toArray')
? $order['order']['customer']->toArray()
: []);
if (!$orderCustomer) {
return $defaultValue;
}
return self::parseCustomerProps($orderCustomer, $valueKey, $defaultValue);
}
public static function parseCartTransaction($code, $valueKey, $defaultValue, $subscriber)
{
$funnelSub = FunnelSubscriber::where('id', $subscriber->funnel_subscriber_id)->first();
try {
if (!$funnelSub) {
return $defaultValue;
}
$order = OrderResource::view($funnelSub['source_ref_id']);
} catch (\Exception $exception) {
return $defaultValue;
}
return self::parseTransactionProps($order['order']['transactions'], $valueKey, $defaultValue);
}
public static function parseCartReceipt($code, $valueKey, $defaultValue, $subscriber)
{
$funnelSub = FunnelSubscriber::where('id', $subscriber->funnel_subscriber_id)->first();
if (!$funnelSub) {
return $defaultValue;
}
try {
$order = OrderResource::view($funnelSub['source_ref_id']);
} catch (\Exception $exception) {
return $defaultValue;
}
return self::parseReceiptProps($order, $valueKey, $defaultValue);
}
public static function parseOrderProps($order, $valueKey, $defaultValue)
{
if (!$order) {
return $defaultValue;
}
switch ($valueKey) {
case 'order_id':
return $order['id'];
case 'status':
return $order['status'];
case 'invoice_no':
return $order['invoice_no'];
case 'receipt_number':
return $order['receipt_number'];
case 'type':
return $order['type'];
case 'customer_id':
return $order['customer_id'];
case 'payment_method':
return $order['payment_method'];
case 'payment_method_title':
return $order['payment_method_title'];
case 'payment_status':
return $order['payment_status'];
case 'currency':
return $order['currency'];
case 'subtotal':
return Helper::toDecimal($order['subtotal']);
case 'shipping_total':
return Helper::toDecimal($order['shipping_total']);
case 'total_amount':
return Helper::toDecimal($order['total_amount']);
case 'note':
return $order['note'];
case 'completed_at':
return $order['completed_at'];
case 'total_refund':
return Helper::toDecimal($order['total_refund']);
case 'uuid':
return $order['uuid'];
case 'created_at':
return $order['created_at'];
case 'total_paid':
return Helper::toDecimal($order['total_paid']);
case 'shipping_status':
return $order['shipping_status'];
// case 'order_url':
// return URL::getCustomerOrderUrl ($order['uuid']);
default:
return null;
}
}
public static function parseTransactionProps($transaction, $valueKey, $defaultValue)
{
// this method is not called for now check FluentCart->addHooks Method
//Considering the latest transaction
usort($transaction, function ($a, $b) {
return strtotime($b['created_at']) - strtotime($a['created_at']);
});
$transaction = $transaction[0];
if (!$transaction) {
return $defaultValue;
}
switch ($valueKey) {
case 'transaction_id':
return $transaction['id'];
case 'order_id':
return $transaction['order_id'];
case 'order_type':
return $transaction['order_type'];
case 'vendor_charge_id':
return $transaction['vendor_charge_id'];
case 'payment_method':
return $transaction['payment_method'];
case 'payment_mode':
return $transaction['payment_mode'];
case 'payment_method_type':
return $transaction['payment_method_type'];
case 'transaction_type':
return $transaction['transaction_type'];
case 'subscription_id':
return $transaction['subscription_id'];
case 'card_last_4':
return $transaction['card_last_4'];
case 'card_brand':
return $transaction['card_brand'];
case 'status':
return $transaction['status'];
case 'total':
return $transaction['total'];
case 'rate':
return $transaction['rate'];
case 'meta':
return $transaction['meta'];
case 'uuid':
return $transaction['uuid'];
default:
return null;
}
}
public static function parseCustomerProps($customer, $valueKey, $defaultValue)
{
if (!$customer) {
return $defaultValue;
}
switch ($valueKey) {
case 'user_id':
return $customer['user_id'];
case 'contact_id':
return $customer['contact_id'];
case 'first_name':
return $customer['first_name'];
case 'last_name':
return $customer['last_name'];
case 'email':
return $customer['email'];
case 'status':
return $customer['status'];
case 'purchase_value':
return $customer['purchase_value'];
case 'purchase_count':
return $customer['purchase_count'];
case 'first_purchase_date':
return $customer['first_purchase_date'];
case 'last_purchase_date':
return $customer['last_purchase_date'];
case 'aov':
return $customer['aov'];
case 'notes':
return $customer['notes'];
case 'uuid':
return $customer['uuid'];
case 'country':
return $customer['country'];
case 'city':
return $customer['city'];
case 'state':
return $customer['state'];
case 'postcode':
return $customer['postcode'];
default:
return null;
}
}
public static function parseReceiptProps($order, $valueKey, $defaultValue)
{
if (!$order) {
return $defaultValue;
}
switch ($valueKey) {
case 'payment_summary':
return self::PaymentReceipt($order);
case 'order_summary':
return null;
default:
return null;
}
}
private static function paymentReceipt($order)
{
$order = Order::with('order_items')->find($order['order']['id']);
$paymentReceipt = new PaymentReceipt($order);
$showQuantityColumn = false;
// Check if any item in the receipt is not a subscription.
// If such an item exists, set $showQuantityColumn to true.
foreach ($paymentReceipt->getItems() as $item) {
if ($item['payment_type'] !== 'subscription') {
$showQuantityColumn = true;
break;
}
}
$shop = Helper::shopConfig();
$currencySign = $shop['currency_sign'];
ob_start();
do_action('fluent_cart/views/checkout_order_summary', compact('order', 'paymentReceipt', 'showQuantityColumn', 'currencySign'));
return ob_get_clean();
}
}
@@ -0,0 +1,98 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\SmartCode;
class SmartCodeRegister
{
public static function push()
{
$smartCodesConfig = [
'cart_order' => [
'title' => __('Cart Order', 'fluent-crm'),
'description' => __('Order-related smart codes', 'fluent-crm'),
'shortcodes' => [
'{{cart_order.order_id}}' => __('Order ID', 'fluent-crm'),
'{{cart_order.status}}' => __('Order Status', 'fluent-crm'),
'{{cart_order.invoice_no}}' => __('Invoice Number', 'fluent-crm'),
'{{cart_order.type}}' => __('Order Type', 'fluent-crm'),
'{{cart_order.payment_method}}' => __('Payment Method', 'fluent-crm'),
'{{cart_order.payment_method_title}}' => __('Payment Method Title', 'fluent-crm'),
'{{cart_order.payment_status}}' => __('Payment Status', 'fluent-crm'),
'{{cart_order.currency}}' => __('Currency', 'fluent-crm'),
'{{cart_order.subtotal}}' => __('Order Subtotal', 'fluent-crm'),
'{{cart_order.shipping_total}}' => __('Shipping Total', 'fluent-crm'),
'{{cart_order.total_amount}}' => __('Total Amount', 'fluent-crm'),
'{{cart_order.note}}' => __('Order Note', 'fluent-crm'),
'{{cart_order.completed_at}}' => __('Order Completion Date', 'fluent-crm'),
'{{cart_order.total_refund}}' => __('Total Refund', 'fluent-crm'),
'{{cart_order.created_at}}' => __('Order Creation Date', 'fluent-crm'),
'{{cart_order.total_paid}}' => __('Total Paid Amount', 'fluent-crm'),
'{{cart_order.shipping_status}}' => __('Shipping Status', 'fluent-crm'),
// '##cart_order.order_url##' => 'Order URL',
]
],
'cart_customer' => [
'title' => __('Cart Customer', 'fluent-crm'),
'description' => __('Customer-related smart codes', 'fluent-crm'),
'shortcodes' => [
'{{cart_customer.user_id}}' => __('User ID', 'fluent-crm'),
'{{cart_customer.contact_id}}' => __('Customer Contact ID', 'fluent-crm'),
'{{cart_customer.email}}' => __('Customer Email', 'fluent-crm'),
'{{cart_customer.first_name}}' => __('Customer First Name', 'fluent-crm'),
'{{cart_customer.last_name}}' => __('Customer Last Name', 'fluent-crm'),
'{{cart_customer.status}}' => __('Customer Status', 'fluent-crm'),
'{{cart_customer.purchase_value}}' => __('Total Purchase Value', 'fluent-crm'),
'{{cart_customer.purchase_count}}' => __('Total Purchase Count', 'fluent-crm'),
'{{cart_customer.first_purchase_date}}' => __('First Purchase Date', 'fluent-crm'),
'{{cart_customer.last_purchase_date}}' => __('Last Purchase Date', 'fluent-crm'),
'{{cart_customer.aov}}' => __('Average Order Value (AOV)', 'fluent-crm'),
'{{cart_customer.notes}}' => __('Customer Notes', 'fluent-crm'),
'{{cart_customer.country}}' => __('Customer Country', 'fluent-crm'),
'{{cart_customer.city}}' => __('Customer City', 'fluent-crm'),
'{{cart_customer.state}}' => __('Customer State', 'fluent-crm'),
'{{cart_customer.postcode}}' => __('Customer Postcode', 'fluent-crm'),
]
],
// 'cart_transaction' => [
// 'title' => __('Cart Transaction'),
// 'description' => __('Transaction-related smart codes'),
// 'shortcodes' => [
// '{{cart_transaction.transaction_id}}' => 'Transaction ID',
// '{{cart_transaction.amount}}' => 'Transaction Amount',
// '{{cart_transaction.order_id}}' => 'Order ID',
// '{{cart_transaction.order_type}}' => 'Order Type',
// '{{cart_transaction.payment_method}}' => 'Payment Method',
// '{{cart_transaction.payment_mode}}' => 'Payment Mode',
// '{{cart_transaction.payment_method_type}}' => 'Payment Method Type',
// '{{cart_transaction.transaction_type}}' => 'Transaction Type',
// '{{cart_transaction.subscription_id}}' => 'Subscription ID',
// '{{cart_transaction.status}}' => 'Transaction Status',
// '{{cart_transaction.total}}' => 'Transaction Total',
// '{{cart_transaction.rate}}' => 'Exchange Rate',
// ]
// ],
// 'cart_receipt' => [
// 'title' => __('Cart Receipts'),
// 'description' => __('Transaction-related smart codes'),
// 'shortcodes' => [
// '{{cart_receipt.payment_summary}}' => 'Payment Summary',
// ]
// ],
];
// Dynamically register filters for each smart code group
foreach ($smartCodesConfig as $key => $config) {
add_filter('fluent_crm_funnel_context_smart_codes', function ($codes) use ($key, $config) {
$codes[] = [
'key' => $key,
'title' => $config['title'],
'description' => $config['description'],
'shortcodes' => $config['shortcodes']
];
return $codes;
});
}
}
}
@@ -0,0 +1,218 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers;
use FluentCrm\App\Services\Funnel\FunnelProcessor;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\App\Services\Funnel\BaseTrigger;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\CartHelper;
class OrderCanceledTrigger extends BaseTrigger
{
public function __construct()
{
$this->triggerName = 'fluent_cart/order_status_changed_to_canceled';
$this->priority = 20;
$this->actionArgNum = 1;
parent::__construct();
}
public function getTrigger()
{
return [
'category' => __('FluentCart', 'fluent-crm'),
'label' => __('Order Canceled', 'fluent-crm'),
'description' => __('This will start when an order is canceled', 'fluent-crm'),
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><g id="surface1"><path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 2.398438 0 L 21.601562 0 C 22.925781 0 24 1.074219 24 2.398438 L 24 21.601562 C 24 22.925781 22.925781 24 21.601562 24 L 2.398438 24 C 1.074219 24 0 22.925781 0 21.601562 L 0 2.398438 C 0 1.074219 1.074219 0 2.398438 0 Z M 2.398438 0 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 10.925781 16.476562 L 3.769531 16.476562 L 4.894531 13.878906 C 5.222656 13.117188 5.972656 12.625 6.804688 12.625 L 15.328125 12.625 L 14.746094 13.964844 C 14.085938 15.488281 12.585938 16.476562 10.925781 16.476562 Z M 10.925781 16.476562 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 16.851562 11.394531 L 6.789062 11.394531 L 7.367188 10.054688 C 8.027344 8.53125 9.53125 7.542969 11.191406 7.542969 L 19.886719 7.542969 L 18.761719 10.140625 C 18.433594 10.902344 17.683594 11.394531 16.851562 11.394531 Z M 16.851562 11.394531 "/></g></svg>'
];
}
public function getFunnelSettingsDefaults()
{
return [
'subscription_status' => 'subscribed'
];
}
public function getSettingsFields($funnel)
{
return [
'title' => __('Order Canceled', 'fluent-crm'),
'sub_title' => __('This will start when an order is canceled', 'fluent-crm'),
'fields' => [
'subscription_status' => [
'type' => 'option_selectors',
'option_key' => 'editable_statuses',
'is_multiple' => false,
'label' => __('Subscription Status', 'fluent-crm'),
'placeholder' => __('Select Status', 'fluent-crm')
],
'subscription_status_info' => [
'type' => 'html',
'info' => '<b>' . __('An Automated double-optin email will be sent for new subscribers', 'fluent-crm') . '</b>',
'dependency' => [
'depends_on' => 'subscription_status',
'operator' => '=',
'value' => 'pending'
]
]
]
];
}
public function getFunnelConditionDefaults($funnel)
{
return [
'product_ids' => [],
'product_categories' => [],
// 'purchase_type' => 'all',
'run_multiple' => 'no'
];
}
public function getConditionFields($funnel)
{
return [
// 'update_type' => [
// 'type' => 'radio',
// 'label' => __('If Contact Already Exist?', 'fluent-crm'),
// 'help' => __('Please specify what will happen if the subscriber already exists in the database','fluent-crm'),
// 'options' => FunnelHelper::getUpdateOptions()
// ],
'product_ids' => [
'type' => 'rest_selector',
'label' => __('Target Products', 'fluent-crm'),
'option_key' => 'fluent_cart_products',
'is_multiple' => true,
'help' => __('Select for which products this automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any product purchase', 'fluent-crm')
],
'product_categories' => [
'type' => 'rest_selector',
'label' => __('Or Target Product Categories', 'fluent-crm'),
'option_key' => 'fluent_cart_product_categories',
'is_multiple' => true,
'help' => __('Select for which product category the automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any category products', 'fluent-crm')
],
// 'run_only_if_coupon_applied' => [
// 'type' => 'yes_no_check',
// 'label' => '',
// 'check_label' => __('Run automation only if a coupon is applied to the order', 'fluent-crm'),
// ],
'run_multiple' => [
'type' => 'yes_no_check',
'label' => '',
'check_label' => __('Restart the Automation Multiple times for a contact for this event. (Only enable if you want to restart automation for the same contact)', 'fluent-crm'),
'inline_help' => __('If enabled, it will restart the automation for a contact if the contact is already in the automation. Otherwise, it will skip if it already exists', 'fluent-crm')
]
];
}
public function handle($funnel, $originalArgs)
{
$orderData = $originalArgs[0] ?? [];
$order = Arr::get($orderData, 'order', []);
$customer = Arr::get($order, 'customer', []);
// $transaction = Arr::get($orderData, 'transaction', []);
$orderId = Arr::get($order, 'id', 0);
// Get the funnel settings and conditions
$settings = Arr::get($funnel, 'settings', []);
$subscriberData = CartHelper::prepareSubscriberData($customer);
if (!is_email($subscriberData['email'])) {
return;
}
$willProcess = $this->isProcessable($funnel, $order, $subscriberData);
$willProcess = apply_filters('fluentcrm_funnel_will_process_' . $this->triggerName, $willProcess, $funnel, $subscriberData, $originalArgs);
if (!$willProcess) {
return;
}
$subscriberData = wp_parse_args($subscriberData, $funnel->settings);
$subscriberData['status'] = (!empty($subscriberData['subscription_status'])) ? $subscriberData['subscription_status'] : 'subscribed';
unset($subscriberData['subscription_status']);
(new FunnelProcessor())->startFunnelSequence($funnel, $subscriberData, [
'source_trigger_name' => $this->triggerName,
'source_ref_id' => $orderId
]);
}
private function isProcessable($funnel, $order, $subscriberData)
{
$conditions = Arr::get($funnel, 'conditions', []);
$isProcessable = $this->checkConditions($conditions, $order, $subscriberData);
if(!$isProcessable){
return false;
}
$subscriber = FunnelHelper::getSubscriber($subscriberData['email']);
// check run_only_one
if ($subscriber) {
$funnelSub = FunnelHelper::ifAlreadyInFunnel($funnel->id, $subscriber->id);
if ($funnelSub) {
$multipleRun = Arr::get($conditions, 'run_multiple') == 'yes';
if ($multipleRun) {
if ($funnelSub->source_ref_id == $order->id) {
return false;
}
FunnelHelper::removeSubscribersFromFunnel($funnel->id, [$subscriber->id]);
}
return $multipleRun;
}
}
return true;
}
public function checkConditions($conditions, $order, $subscriber)
{
$orderItems = Arr::get($order, 'order_items', []);
// Post IDs of ordered products are the product IDs in FluentCart
$orderedProductIds = [];
foreach ($orderItems as $item) {
$productId = $item->post_id;
if ($productId) {
$orderedProductIds[] = $productId;
}
}
$orderProductCategories = CartHelper::getProductCategoriesByIds($orderedProductIds);
$selectedProductIds = Arr::get($conditions, 'product_ids', []);
$selectedProductCategories = Arr::get($conditions, 'product_categories', []);
// If no products or categories are selected, return true
if (empty($selectedProductIds) && empty($selectedProductCategories)) {
return true;
}
// Check for matches in product IDs and categories
$productMatch = !empty($selectedProductIds) && !empty(array_intersect($selectedProductIds, $orderedProductIds));
$categoryMatch = !empty($selectedProductCategories) && !empty(array_intersect($selectedProductCategories, $orderProductCategories));
// Return true if either matches
return $productMatch || $categoryMatch;
}
}
@@ -0,0 +1,218 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers;
use FluentCrm\App\Services\Funnel\FunnelProcessor;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\App\Services\Funnel\BaseTrigger;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\CartHelper;
class OrderDeliveredTrigger extends BaseTrigger
{
public function __construct()
{
$this->triggerName = 'fluent_cart/shipping_status_changed_to_delivered';
$this->priority = 20;
$this->actionArgNum = 1;
parent::__construct();
}
public function getTrigger()
{
return [
'category' => __('FluentCart', 'fluent-crm'),
'label' => __('Order Delivered', 'fluent-crm'),
'description' => __('This will start when a successful order is delivered', 'fluent-crm'),
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><g id="surface1"><path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 2.398438 0 L 21.601562 0 C 22.925781 0 24 1.074219 24 2.398438 L 24 21.601562 C 24 22.925781 22.925781 24 21.601562 24 L 2.398438 24 C 1.074219 24 0 22.925781 0 21.601562 L 0 2.398438 C 0 1.074219 1.074219 0 2.398438 0 Z M 2.398438 0 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 10.925781 16.476562 L 3.769531 16.476562 L 4.894531 13.878906 C 5.222656 13.117188 5.972656 12.625 6.804688 12.625 L 15.328125 12.625 L 14.746094 13.964844 C 14.085938 15.488281 12.585938 16.476562 10.925781 16.476562 Z M 10.925781 16.476562 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 16.851562 11.394531 L 6.789062 11.394531 L 7.367188 10.054688 C 8.027344 8.53125 9.53125 7.542969 11.191406 7.542969 L 19.886719 7.542969 L 18.761719 10.140625 C 18.433594 10.902344 17.683594 11.394531 16.851562 11.394531 Z M 16.851562 11.394531 "/></g></svg>'
];
}
public function getFunnelSettingsDefaults()
{
return [
'subscription_status' => 'subscribed'
];
}
public function getSettingsFields($funnel)
{
return [
'title' => __('Order Delivered', 'fluent-crm'),
'sub_title' => __('This will start when an order is delivered', 'fluent-crm'),
'fields' => [
'subscription_status' => [
'type' => 'option_selectors',
'option_key' => 'editable_statuses',
'is_multiple' => false,
'label' => __('Subscription Status', 'fluent-crm'),
'placeholder' => __('Select Status', 'fluent-crm')
],
'subscription_status_info' => [
'type' => 'html',
'info' => '<b>' . __('An Automated double-optin email will be sent for new subscribers', 'fluent-crm') . '</b>',
'dependency' => [
'depends_on' => 'subscription_status',
'operator' => '=',
'value' => 'pending'
]
]
]
];
}
public function getFunnelConditionDefaults($funnel)
{
return [
'product_ids' => [],
'product_categories' => [],
// 'purchase_type' => 'all',
'run_multiple' => 'no'
];
}
public function getConditionFields($funnel)
{
return [
// 'update_type' => [
// 'type' => 'radio',
// 'label' => __('If Contact Already Exist?', 'fluent-crm'),
// 'help' => __('Please specify what will happen if the subscriber already exists in the database','fluent-crm'),
// 'options' => FunnelHelper::getUpdateOptions()
// ],
'product_ids' => [
'type' => 'rest_selector',
'label' => __('Target Products', 'fluent-crm'),
'option_key' => 'fluent_cart_products',
'is_multiple' => true,
'help' => __('Select for which products this automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any product purchase', 'fluent-crm')
],
'product_categories' => [
'type' => 'rest_selector',
'label' => __('Or Target Product Categories', 'fluent-crm'),
'option_key' => 'fluent_cart_product_categories',
'is_multiple' => true,
'help' => __('Select for which product category the automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any category products', 'fluent-crm')
],
// 'run_only_if_coupon_applied' => [
// 'type' => 'yes_no_check',
// 'label' => '',
// 'check_label' => __('Run automation only if a coupon is applied to the order', 'fluent-crm'),
// ],
'run_multiple' => [
'type' => 'yes_no_check',
'label' => '',
'check_label' => __('Restart the Automation Multiple times for a contact for this event. (Only enable if you want to restart automation for the same contact)', 'fluent-crm'),
'inline_help' => __('If enabled, it will restart the automation for a contact if the contact is already in the automation. Otherwise, it will skip if it already exists', 'fluent-crm')
]
];
}
public function handle($funnel, $originalArgs)
{
$orderData = $originalArgs[0] ?? [];
$order = Arr::get($orderData, 'order', []);
$customer = Arr::get($order, 'customer', []);
// $transaction = Arr::get($orderData, 'transaction', []);
$orderId = Arr::get($order, 'id', 0);
// Get the funnel settings and conditions
$settings = Arr::get($funnel, 'settings', []);
$subscriberData = CartHelper::prepareSubscriberData($customer);
if (!is_email($subscriberData['email'])) {
return;
}
$willProcess = $this->isProcessable($funnel, $order, $subscriberData);
$willProcess = apply_filters('fluentcrm_funnel_will_process_' . $this->triggerName, $willProcess, $funnel, $subscriberData, $originalArgs);
if (!$willProcess) {
return;
}
$subscriberData = wp_parse_args($subscriberData, $funnel->settings);
$subscriberData['status'] = (!empty($subscriberData['subscription_status'])) ? $subscriberData['subscription_status'] : 'subscribed';
unset($subscriberData['subscription_status']);
(new FunnelProcessor())->startFunnelSequence($funnel, $subscriberData, [
'source_trigger_name' => $this->triggerName,
'source_ref_id' => $orderId
]);
}
private function isProcessable($funnel, $order, $subscriberData)
{
$conditions = Arr::get($funnel, 'conditions', []);
$isProcessable = $this->checkConditions($conditions, $order, $subscriberData);
if(!$isProcessable){
return false;
}
$subscriber = FunnelHelper::getSubscriber($subscriberData['email']);
// check run_only_one
if ($subscriber) {
$funnelSub = FunnelHelper::ifAlreadyInFunnel($funnel->id, $subscriber->id);
if ($funnelSub) {
$multipleRun = Arr::get($conditions, 'run_multiple') == 'yes';
if ($multipleRun) {
if ($funnelSub->source_ref_id == $order->id) {
return false;
}
FunnelHelper::removeSubscribersFromFunnel($funnel->id, [$subscriber->id]);
}
return $multipleRun;
}
}
return true;
}
public function checkConditions($conditions, $order, $subscriber)
{
$orderItems = Arr::get($order, 'order_items', []);
// Post IDs of ordered products are the product IDs in FluentCart
$orderedProductIds = [];
foreach ($orderItems as $item) {
$productId = $item->post_id;
if ($productId) {
$orderedProductIds[] = $productId;
}
}
$orderProductCategories = CartHelper::getProductCategoriesByIds($orderedProductIds);
$selectedProductIds = Arr::get($conditions, 'product_ids', []);
$selectedProductCategories = Arr::get($conditions, 'product_categories', []);
// If no products or categories are selected, return true
if (empty($selectedProductIds) && empty($selectedProductCategories)) {
return true;
}
// Check for matches in product IDs and categories
$productMatch = !empty($selectedProductIds) && !empty(array_intersect($selectedProductIds, $orderedProductIds));
$categoryMatch = !empty($selectedProductCategories) && !empty(array_intersect($selectedProductCategories, $orderProductCategories));
// Return true if either matches
return $productMatch || $categoryMatch;
}
}
@@ -0,0 +1,218 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers;
use FluentCrm\App\Services\Funnel\FunnelProcessor;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\App\Services\Funnel\BaseTrigger;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\CartHelper;
class OrderPaidTrigger extends BaseTrigger
{
public function __construct()
{
$this->triggerName = 'fluent_cart/order_paid_done';
$this->priority = 20;
$this->actionArgNum = 1;
parent::__construct();
}
public function getTrigger()
{
return [
'category' => __('FluentCart', 'fluent-crm'),
'label' => __('Order Paid (Payment/Subscription)', 'fluent-crm'),
'description' => __('This will start when a successful order is created', 'fluent-crm'),
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><g id="surface1"><path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 2.398438 0 L 21.601562 0 C 22.925781 0 24 1.074219 24 2.398438 L 24 21.601562 C 24 22.925781 22.925781 24 21.601562 24 L 2.398438 24 C 1.074219 24 0 22.925781 0 21.601562 L 0 2.398438 C 0 1.074219 1.074219 0 2.398438 0 Z M 2.398438 0 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 10.925781 16.476562 L 3.769531 16.476562 L 4.894531 13.878906 C 5.222656 13.117188 5.972656 12.625 6.804688 12.625 L 15.328125 12.625 L 14.746094 13.964844 C 14.085938 15.488281 12.585938 16.476562 10.925781 16.476562 Z M 10.925781 16.476562 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 16.851562 11.394531 L 6.789062 11.394531 L 7.367188 10.054688 C 8.027344 8.53125 9.53125 7.542969 11.191406 7.542969 L 19.886719 7.542969 L 18.761719 10.140625 C 18.433594 10.902344 17.683594 11.394531 16.851562 11.394531 Z M 16.851562 11.394531 "/></g></svg>'
];
}
public function getFunnelSettingsDefaults()
{
return [
'subscription_status' => 'subscribed'
];
}
public function getSettingsFields($funnel)
{
return [
'title' => __('Order Paid (Payment/Subscription)', 'fluent-crm'),
'sub_title' => __('This will start when an order is paid', 'fluent-crm'),
'fields' => [
'subscription_status' => [
'type' => 'option_selectors',
'option_key' => 'editable_statuses',
'is_multiple' => false,
'label' => __('Subscription Status', 'fluent-crm'),
'placeholder' => __('Select Status', 'fluent-crm')
],
'subscription_status_info' => [
'type' => 'html',
'info' => '<b>' . __('An Automated double-optin email will be sent for new subscribers', 'fluent-crm') . '</b>',
'dependency' => [
'depends_on' => 'subscription_status',
'operator' => '=',
'value' => 'pending'
]
]
]
];
}
public function getFunnelConditionDefaults($funnel)
{
return [
'product_ids' => [],
'product_categories' => [],
// 'purchase_type' => 'all',
'run_multiple' => 'no'
];
}
public function getConditionFields($funnel)
{
return [
// 'update_type' => [
// 'type' => 'radio',
// 'label' => __('If Contact Already Exist?', 'fluent-crm'),
// 'help' => __('Please specify what will happen if the subscriber already exists in the database','fluent-crm'),
// 'options' => FunnelHelper::getUpdateOptions()
// ],
'product_ids' => [
'type' => 'rest_selector',
'label' => __('Target Products', 'fluent-crm'),
'option_key' => 'fluent_cart_products',
'is_multiple' => true,
'help' => __('Select for which products this automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any product purchase', 'fluent-crm')
],
'product_categories' => [
'type' => 'rest_selector',
'label' => __('Or Target Product Categories', 'fluent-crm'),
'option_key' => 'fluent_cart_product_categories',
'is_multiple' => true,
'help' => __('Select for which product category the automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any category products', 'fluent-crm')
],
// 'run_only_if_coupon_applied' => [
// 'type' => 'yes_no_check',
// 'label' => '',
// 'check_label' => __('Run automation only if a coupon is applied to the order', 'fluent-crm'),
// ],
'run_multiple' => [
'type' => 'yes_no_check',
'label' => '',
'check_label' => __('Restart the Automation Multiple times for a contact for this event. (Only enable if you want to restart automation for the same contact)', 'fluent-crm'),
'inline_help' => __('If enabled, it will restart the automation for a contact if the contact is already in the automation. Otherwise, it will skip if it already exists', 'fluent-crm')
]
];
}
public function handle($funnel, $originalArgs)
{
$orderData = $originalArgs[0] ?? [];
$order = Arr::get($orderData, 'order', []);
$customer = Arr::get($orderData, 'customer', []);
$transaction = Arr::get($orderData, 'transaction', []);
$orderId = Arr::get($order, 'id', 0);
// Get the funnel settings and conditions
$settings = Arr::get($funnel, 'settings', []);
$subscriberData = CartHelper::prepareSubscriberData($customer);
if (!is_email($subscriberData['email'])) {
return;
}
$willProcess = $this->isProcessable($funnel, $order, $subscriberData);
$willProcess = apply_filters('fluentcrm_funnel_will_process_' . $this->triggerName, $willProcess, $funnel, $subscriberData, $originalArgs);
if (!$willProcess) {
return;
}
$subscriberData = wp_parse_args($subscriberData, $funnel->settings);
$subscriberData['status'] = (!empty($subscriberData['subscription_status'])) ? $subscriberData['subscription_status'] : 'subscribed';
unset($subscriberData['subscription_status']);
(new FunnelProcessor())->startFunnelSequence($funnel, $subscriberData, [
'source_trigger_name' => $this->triggerName,
'source_ref_id' => $orderId
]);
}
private function isProcessable($funnel, $order, $subscriberData)
{
$conditions = Arr::get($funnel, 'conditions', []);
$isProcessable = $this->checkConditions($conditions, $order, $subscriberData);
if(!$isProcessable){
return false;
}
$subscriber = FunnelHelper::getSubscriber($subscriberData['email']);
// check run_only_one
if ($subscriber) {
$funnelSub = FunnelHelper::ifAlreadyInFunnel($funnel->id, $subscriber->id);
if ($funnelSub) {
$multipleRun = Arr::get($conditions, 'run_multiple') == 'yes';
if ($multipleRun) {
if ($funnelSub->source_ref_id == $order->id) {
return false;
}
FunnelHelper::removeSubscribersFromFunnel($funnel->id, [$subscriber->id]);
}
return $multipleRun;
}
}
return true;
}
public function checkConditions($conditions, $order, $subscriber)
{
$orderItems = Arr::get($order, 'order_items', []);
// Post IDs of ordered products are the product IDs in FluentCart
$orderedProductIds = [];
foreach ($orderItems as $item) {
$productId = $item->post_id;
if ($productId) {
$orderedProductIds[] = $productId;
}
}
$orderProductCategories = CartHelper::getProductCategoriesByIds($orderedProductIds);
$selectedProductIds = Arr::get($conditions, 'product_ids', []);
$selectedProductCategories = Arr::get($conditions, 'product_categories', []);
// If no products or categories are selected, return true
if (empty($selectedProductIds) && empty($selectedProductCategories)) {
return true;
}
// Check for matches in product IDs and categories
$productMatch = !empty($selectedProductIds) && !empty(array_intersect($selectedProductIds, $orderedProductIds));
$categoryMatch = !empty($selectedProductCategories) && !empty(array_intersect($selectedProductCategories, $orderProductCategories));
// Return true if either matches
return $productMatch || $categoryMatch;
}
}
@@ -0,0 +1,218 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers;
use FluentCrm\App\Services\Funnel\FunnelProcessor;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\App\Services\Funnel\BaseTrigger;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\CartHelper;
class OrderRefundedTrigger extends BaseTrigger
{
public function __construct()
{
$this->triggerName = 'fluent_cart/order_fully_refunded';
$this->priority = 20;
$this->actionArgNum = 1;
parent::__construct();
}
public function getTrigger()
{
return [
'category' => __('FluentCart', 'fluent-crm'),
'label' => __('Order Refunded (Full)', 'fluent-crm'),
'description' => __('This will start when a successful order is refunded', 'fluent-crm'),
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><g id="surface1"><path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 2.398438 0 L 21.601562 0 C 22.925781 0 24 1.074219 24 2.398438 L 24 21.601562 C 24 22.925781 22.925781 24 21.601562 24 L 2.398438 24 C 1.074219 24 0 22.925781 0 21.601562 L 0 2.398438 C 0 1.074219 1.074219 0 2.398438 0 Z M 2.398438 0 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 10.925781 16.476562 L 3.769531 16.476562 L 4.894531 13.878906 C 5.222656 13.117188 5.972656 12.625 6.804688 12.625 L 15.328125 12.625 L 14.746094 13.964844 C 14.085938 15.488281 12.585938 16.476562 10.925781 16.476562 Z M 10.925781 16.476562 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 16.851562 11.394531 L 6.789062 11.394531 L 7.367188 10.054688 C 8.027344 8.53125 9.53125 7.542969 11.191406 7.542969 L 19.886719 7.542969 L 18.761719 10.140625 C 18.433594 10.902344 17.683594 11.394531 16.851562 11.394531 Z M 16.851562 11.394531 "/></g></svg>'
];
}
public function getFunnelSettingsDefaults()
{
return [
'subscription_status' => 'subscribed'
];
}
public function getSettingsFields($funnel)
{
return [
'title' => __('Order Refunded (Full)', 'fluent-crm'),
'sub_title' => __('This will start when an order is refunded', 'fluent-crm'),
'fields' => [
'subscription_status' => [
'type' => 'option_selectors',
'option_key' => 'editable_statuses',
'is_multiple' => false,
'label' => __('Subscription Status', 'fluent-crm'),
'placeholder' => __('Select Status', 'fluent-crm')
],
'subscription_status_info' => [
'type' => 'html',
'info' => '<b>' . __('An Automated double-optin email will be sent for new subscribers', 'fluent-crm') . '</b>',
'dependency' => [
'depends_on' => 'subscription_status',
'operator' => '=',
'value' => 'pending'
]
]
]
];
}
public function getFunnelConditionDefaults($funnel)
{
return [
'product_ids' => [],
'product_categories' => [],
// 'purchase_type' => 'all',
'run_multiple' => 'no'
];
}
public function getConditionFields($funnel)
{
return [
// 'update_type' => [
// 'type' => 'radio',
// 'label' => __('If Contact Already Exist?', 'fluent-crm'),
// 'help' => __('Please specify what will happen if the subscriber already exists in the database','fluent-crm'),
// 'options' => FunnelHelper::getUpdateOptions()
// ],
'product_ids' => [
'type' => 'rest_selector',
'label' => __('Target Products', 'fluent-crm'),
'option_key' => 'fluent_cart_products',
'is_multiple' => true,
'help' => __('Select for which products this automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any product purchase', 'fluent-crm')
],
'product_categories' => [
'type' => 'rest_selector',
'label' => __('Or Target Product Categories', 'fluent-crm'),
'option_key' => 'fluent_cart_product_categories',
'is_multiple' => true,
'help' => __('Select for which product category the automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any category products', 'fluent-crm')
],
// 'run_only_if_coupon_applied' => [
// 'type' => 'yes_no_check',
// 'label' => '',
// 'check_label' => __('Run automation only if a coupon is applied to the order', 'fluent-crm'),
// ],
'run_multiple' => [
'type' => 'yes_no_check',
'label' => '',
'check_label' => __('Restart the Automation Multiple times for a contact for this event. (Only enable if you want to restart automation for the same contact)', 'fluent-crm'),
'inline_help' => __('If enabled, it will restart the automation for a contact if the contact is already in the automation. Otherwise, it will skip if it already exists', 'fluent-crm')
]
];
}
public function handle($funnel, $originalArgs)
{
$orderData = $originalArgs[0] ?? [];
$order = Arr::get($orderData, 'order', []);
$customer = Arr::get($order, 'customer', []);
// $transaction = Arr::get($orderData, 'transaction', []);
$orderId = Arr::get($order, 'id', 0);
// Get the funnel settings and conditions
$settings = Arr::get($funnel, 'settings', []);
$subscriberData = CartHelper::prepareSubscriberData($customer);
if (!is_email($subscriberData['email'])) {
return;
}
$willProcess = $this->isProcessable($funnel, $order, $subscriberData);
$willProcess = apply_filters('fluentcrm_funnel_will_process_' . $this->triggerName, $willProcess, $funnel, $subscriberData, $originalArgs);
if (!$willProcess) {
return;
}
$subscriberData = wp_parse_args($subscriberData, $funnel->settings);
$subscriberData['status'] = (!empty($subscriberData['subscription_status'])) ? $subscriberData['subscription_status'] : 'subscribed';
unset($subscriberData['subscription_status']);
(new FunnelProcessor())->startFunnelSequence($funnel, $subscriberData, [
'source_trigger_name' => $this->triggerName,
'source_ref_id' => $orderId
]);
}
private function isProcessable($funnel, $order, $subscriberData)
{
$conditions = Arr::get($funnel, 'conditions', []);
$isProcessable = $this->checkConditions($conditions, $order, $subscriberData);
if(!$isProcessable){
return false;
}
$subscriber = FunnelHelper::getSubscriber($subscriberData['email']);
// check run_only_one
if ($subscriber) {
$funnelSub = FunnelHelper::ifAlreadyInFunnel($funnel->id, $subscriber->id);
if ($funnelSub) {
$multipleRun = Arr::get($conditions, 'run_multiple') == 'yes';
if ($multipleRun) {
if ($funnelSub->source_ref_id == $order->id) {
return false;
}
FunnelHelper::removeSubscribersFromFunnel($funnel->id, [$subscriber->id]);
}
return $multipleRun;
}
}
return true;
}
public function checkConditions($conditions, $order, $subscriber)
{
$orderItems = Arr::get($order, 'order_items', []);
// Post IDs of ordered products are the product IDs in FluentCart
$orderedProductIds = [];
foreach ($orderItems as $item) {
$productId = $item->post_id;
if ($productId) {
$orderedProductIds[] = $productId;
}
}
$orderProductCategories = CartHelper::getProductCategoriesByIds($orderedProductIds);
$selectedProductIds = Arr::get($conditions, 'product_ids', []);
$selectedProductCategories = Arr::get($conditions, 'product_categories', []);
// If no products or categories are selected, return true
if (empty($selectedProductIds) && empty($selectedProductCategories)) {
return true;
}
// Check for matches in product IDs and categories
$productMatch = !empty($selectedProductIds) && !empty(array_intersect($selectedProductIds, $orderedProductIds));
$categoryMatch = !empty($selectedProductCategories) && !empty(array_intersect($selectedProductCategories, $orderProductCategories));
// Return true if either matches
return $productMatch || $categoryMatch;
}
}
@@ -0,0 +1,218 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers;
use FluentCrm\App\Services\Funnel\FunnelProcessor;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\App\Services\Funnel\BaseTrigger;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\CartHelper;
class OrderShippedTrigger extends BaseTrigger
{
public function __construct()
{
$this->triggerName = 'fluent_cart/shipping_status_changed_to_shipped';
$this->priority = 20;
$this->actionArgNum = 1;
parent::__construct();
}
public function getTrigger()
{
return [
'category' => __('FluentCart', 'fluent-crm'),
'label' => __('Order Shipped', 'fluent-crm'),
'description' => __('This will start when a successful order is shipped', 'fluent-crm'),
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><g id="surface1"><path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 2.398438 0 L 21.601562 0 C 22.925781 0 24 1.074219 24 2.398438 L 24 21.601562 C 24 22.925781 22.925781 24 21.601562 24 L 2.398438 24 C 1.074219 24 0 22.925781 0 21.601562 L 0 2.398438 C 0 1.074219 1.074219 0 2.398438 0 Z M 2.398438 0 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 10.925781 16.476562 L 3.769531 16.476562 L 4.894531 13.878906 C 5.222656 13.117188 5.972656 12.625 6.804688 12.625 L 15.328125 12.625 L 14.746094 13.964844 C 14.085938 15.488281 12.585938 16.476562 10.925781 16.476562 Z M 10.925781 16.476562 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 16.851562 11.394531 L 6.789062 11.394531 L 7.367188 10.054688 C 8.027344 8.53125 9.53125 7.542969 11.191406 7.542969 L 19.886719 7.542969 L 18.761719 10.140625 C 18.433594 10.902344 17.683594 11.394531 16.851562 11.394531 Z M 16.851562 11.394531 "/></g></svg>'
];
}
public function getFunnelSettingsDefaults()
{
return [
'subscription_status' => 'subscribed'
];
}
public function getSettingsFields($funnel)
{
return [
'title' => __('Order Shipped', 'fluent-crm'),
'sub_title' => __('This will start when an order is shipped', 'fluent-crm'),
'fields' => [
'subscription_status' => [
'type' => 'option_selectors',
'option_key' => 'editable_statuses',
'is_multiple' => false,
'label' => __('Subscription Status', 'fluent-crm'),
'placeholder' => __('Select Status', 'fluent-crm')
],
'subscription_status_info' => [
'type' => 'html',
'info' => '<b>' . __('An Automated double-optin email will be sent for new subscribers', 'fluent-crm') . '</b>',
'dependency' => [
'depends_on' => 'subscription_status',
'operator' => '=',
'value' => 'pending'
]
]
]
];
}
public function getFunnelConditionDefaults($funnel)
{
return [
'product_ids' => [],
'product_categories' => [],
// 'purchase_type' => 'all',
'run_multiple' => 'no'
];
}
public function getConditionFields($funnel)
{
return [
// 'update_type' => [
// 'type' => 'radio',
// 'label' => __('If Contact Already Exist?', 'fluent-crm'),
// 'help' => __('Please specify what will happen if the subscriber already exists in the database','fluent-crm'),
// 'options' => FunnelHelper::getUpdateOptions()
// ],
'product_ids' => [
'type' => 'rest_selector',
'label' => __('Target Products', 'fluent-crm'),
'option_key' => 'fluent_cart_products',
'is_multiple' => true,
'help' => __('Select for which products this automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any product purchase', 'fluent-crm')
],
'product_categories' => [
'type' => 'rest_selector',
'label' => __('Or Target Product Categories', 'fluent-crm'),
'option_key' => 'fluent_cart_product_categories',
'is_multiple' => true,
'help' => __('Select for which product category the automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any category products', 'fluent-crm')
],
// 'run_only_if_coupon_applied' => [
// 'type' => 'yes_no_check',
// 'label' => '',
// 'check_label' => __('Run automation only if a coupon is applied to the order', 'fluent-crm'),
// ],
'run_multiple' => [
'type' => 'yes_no_check',
'label' => '',
'check_label' => __('Restart the Automation Multiple times for a contact for this event. (Only enable if you want to restart automation for the same contact)', 'fluent-crm'),
'inline_help' => __('If enabled, it will restart the automation for a contact if the contact is already in the automation. Otherwise, it will skip if it already exists', 'fluent-crm')
]
];
}
public function handle($funnel, $originalArgs)
{
$orderData = $originalArgs[0] ?? [];
$order = Arr::get($orderData, 'order', []);
$customer = Arr::get($order, 'customer', []);
// $transaction = Arr::get($orderData, 'transaction', []);
$orderId = Arr::get($order, 'id', 0);
// Get the funnel settings and conditions
$settings = Arr::get($funnel, 'settings', []);
$subscriberData = CartHelper::prepareSubscriberData($customer);
if (!is_email($subscriberData['email'])) {
return;
}
$willProcess = $this->isProcessable($funnel, $order, $subscriberData);
$willProcess = apply_filters('fluentcrm_funnel_will_process_' . $this->triggerName, $willProcess, $funnel, $subscriberData, $originalArgs);
if (!$willProcess) {
return;
}
$subscriberData = wp_parse_args($subscriberData, $funnel->settings);
$subscriberData['status'] = (!empty($subscriberData['subscription_status'])) ? $subscriberData['subscription_status'] : 'subscribed';
unset($subscriberData['subscription_status']);
(new FunnelProcessor())->startFunnelSequence($funnel, $subscriberData, [
'source_trigger_name' => $this->triggerName,
'source_ref_id' => $orderId
]);
}
private function isProcessable($funnel, $order, $subscriberData)
{
$conditions = Arr::get($funnel, 'conditions', []);
$isProcessable = $this->checkConditions($conditions, $order, $subscriberData);
if(!$isProcessable){
return false;
}
$subscriber = FunnelHelper::getSubscriber($subscriberData['email']);
// check run_only_one
if ($subscriber) {
$funnelSub = FunnelHelper::ifAlreadyInFunnel($funnel->id, $subscriber->id);
if ($funnelSub) {
$multipleRun = Arr::get($conditions, 'run_multiple') == 'yes';
if ($multipleRun) {
if ($funnelSub->source_ref_id == $order->id) {
return false;
}
FunnelHelper::removeSubscribersFromFunnel($funnel->id, [$subscriber->id]);
}
return $multipleRun;
}
}
return true;
}
public function checkConditions($conditions, $order, $subscriber)
{
$orderItems = Arr::get($order, 'order_items', []);
// Post IDs of ordered products are the product IDs in FluentCart
$orderedProductIds = [];
foreach ($orderItems as $item) {
$productId = $item->post_id;
if ($productId) {
$orderedProductIds[] = $productId;
}
}
$orderProductCategories = CartHelper::getProductCategoriesByIds($orderedProductIds);
$selectedProductIds = Arr::get($conditions, 'product_ids', []);
$selectedProductCategories = Arr::get($conditions, 'product_categories', []);
// If no products or categories are selected, return true
if (empty($selectedProductIds) && empty($selectedProductCategories)) {
return true;
}
// Check for matches in product IDs and categories
$productMatch = !empty($selectedProductIds) && !empty(array_intersect($selectedProductIds, $orderedProductIds));
$categoryMatch = !empty($selectedProductCategories) && !empty(array_intersect($selectedProductCategories, $orderProductCategories));
// Return true if either matches
return $productMatch || $categoryMatch;
}
}
@@ -0,0 +1,223 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers;
use FluentCart\App\Helpers\Status;
use FluentCrm\App\Services\Funnel\BaseTrigger;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\App\Services\Funnel\FunnelProcessor;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\CartHelper;
class OrderStatusChangedTrigger extends BaseTrigger
{
public function __construct()
{
$this->triggerName = 'fluent_cart/order_status_changed';
$this->priority = 20;
$this->actionArgNum = 1;
parent::__construct();
}
public function getTrigger()
{
return [
'category' => __('FluentCart', 'fluent-crm'),
'label' => __('Order Status Changed', 'fluent-crm'),
'description' => __('This funnel will start when an order status updates', 'fluent-crm'),
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><g id="surface1"><path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 2.398438 0 L 21.601562 0 C 22.925781 0 24 1.074219 24 2.398438 L 24 21.601562 C 24 22.925781 22.925781 24 21.601562 24 L 2.398438 24 C 1.074219 24 0 22.925781 0 21.601562 L 0 2.398438 C 0 1.074219 1.074219 0 2.398438 0 Z M 2.398438 0 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 10.925781 16.476562 L 3.769531 16.476562 L 4.894531 13.878906 C 5.222656 13.117188 5.972656 12.625 6.804688 12.625 L 15.328125 12.625 L 14.746094 13.964844 C 14.085938 15.488281 12.585938 16.476562 10.925781 16.476562 Z M 10.925781 16.476562 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 16.851562 11.394531 L 6.789062 11.394531 L 7.367188 10.054688 C 8.027344 8.53125 9.53125 7.542969 11.191406 7.542969 L 19.886719 7.542969 L 18.761719 10.140625 C 18.433594 10.902344 17.683594 11.394531 16.851562 11.394531 Z M 16.851562 11.394531 "/></g></svg>'
];
}
public function getFunnelSettingsDefaults()
{
return [
'subscription_status' => 'subscribed'
];
}
public function getSettingsFields($funnel)
{
return [
'title' => __('FluentCart Order Status Changed', 'fluent-crm'),
'sub_title' => __('This Funnel will start when a Order status will change from one state to another', 'fluent-crm'),
'fields' => [
'subscription_status' => [
'type' => 'option_selectors',
'option_key' => 'editable_statuses',
'is_multiple' => false,
'label' => __('Subscription Status', 'fluent-crm'),
'placeholder' => __('Select Status', 'fluent-crm')
],
'subscription_status_info' => [
'type' => 'html',
'info' => '<b>' . __('An Automated double-optin email will be sent for new subscribers', 'fluent-crm') . '</b>',
'dependency' => [
'depends_on' => 'subscription_status',
'operator' => '=',
'value' => 'pending'
]
]
]
];
}
public function getFunnelConditionDefaults($funnel)
{
return [
'product_ids' => [],
'product_categories' => [],
'from_status' => 'any',
'to_status' => 'any',
'run_multiple' => 'no'
];
}
public function getConditionFields($funnel)
{
$orderStatuses = Status::getOrderStatuses();
$formattedStatuses = [[
'id' => 'any',
'title' => __('Any', 'fluent-crm')
]];
foreach ($orderStatuses as $statusId => $statusName) {
$formattedStatuses[] = [
'id' => $statusId,
'title' => $statusName
];
}
return [
'product_ids' => [
'type' => 'rest_selector',
'option_key' => 'fluent_cart_products',
'is_multiple' => true,
'label' => __('Target Products', 'fluent-crm'),
'help' => __('Select for which products this automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run for any product\'s order status change', 'fluent-crm'),
],
'product_categories' => [
'type' => 'rest_selector',
'option_key' => 'fluent_cart_product_categories',
'is_multiple' => true,
'label' => __('Or Target Product Categories', 'fluent-crm'),
'help' => __('Select for which product category the automation will run', 'fluent-crm'),
'inline_help' => __('Keep it blank to run to any category products', 'fluent-crm'),
],
'from_status' => [
'type' => 'select',
'label' => __('From Order Status', 'fluent-crm'),
'help' => __('The current status that will trigger an action when it changes from this status to the \'To Order Status.\'', 'fluent-crm'),
'options' => $formattedStatuses
],
'to_status' => [
'type' => 'select',
'label' => __('To Order Status', 'fluent-crm'),
'help' => __('The target status that will trigger an action when the order moves from the \'From Order Status\' to this status.', 'fluent-crm'),
'options' => $formattedStatuses
],
'run_multiple' => [
'type' => 'yes_no_check',
'label' => '',
'check_label' => __('Restart the Automation Multiple times for a contact for this event. (Only enable if you want to restart automation for the same contact)', 'fluent-crm'),
'inline_help' => __('If enabled, it will restart the automation for a contact if the contact is already in the automation. Otherwise, it will skip if it already exists', 'fluent-crm')
]
];
}
public function handle($funnel, $originalArgs)
{
$orderData = $originalArgs[0] ?? [];
$order = Arr::get($orderData, 'order', []);
$fromStatus = Arr::get($orderData, 'old_status', '');
$toStatus = Arr::get($orderData, 'new_status', '');
$customer = Arr::get($order, 'customer');
$orderId = Arr::get($order, 'id', 0);
$subscriberData = CartHelper::prepareSubscriberData($customer);
if (!is_email($subscriberData['email'])) {
return;
}
$willProcess = $this->isProcessable($funnel, $subscriberData, $fromStatus, $toStatus, $order);
$willProcess = apply_filters('fluentcrm_funnel_will_process_' . $this->triggerName, $willProcess, $funnel, $subscriberData, $originalArgs);
if (!$willProcess) {
return;
}
$subscriberData = wp_parse_args($subscriberData, $funnel->settings);
$subscriberData['status'] = (!empty($subscriberData['subscription_status'])) ? $subscriberData['subscription_status'] : 'subscribed';
unset($subscriberData['subscription_status']);
(new FunnelProcessor())->startFunnelSequence($funnel, $subscriberData, [
'source_trigger_name' => $this->triggerName,
'source_ref_id' => $orderId
]);
}
private function isProcessable($funnel, $subscriberData, $fromStatus, $toStatus, $order)
{
$conditions = (array)$funnel->conditions;
$orderItems = Arr::get($order, 'order_items', []);
// Post IDs of ordered products are the product IDs in FluentCart
$orderedProductIds = [];
foreach ($orderItems as $item) {
$productId = $item->post_id;
if ($productId) {
$orderedProductIds[] = $productId;
}
}
$orderProductCategories = CartHelper::getProductCategoriesByIds($orderedProductIds);
$selectedProductIds = Arr::get($conditions, 'product_ids', []);
$selectedProductCategories = Arr::get($conditions, 'product_categories', []);
if (!empty($selectedProductIds) || !empty($selectedProductCategories)) {
$productMatch = !empty($selectedProductIds) && !empty(array_intersect($selectedProductIds, $orderedProductIds));
$categoryMatch = !empty($selectedProductCategories) && !empty(array_intersect($selectedProductCategories, $orderProductCategories));
if (!$productMatch && !$categoryMatch) {
return false;
}
}
$fromCondition = Arr::get($conditions, 'from_status', 'any');
if ($fromCondition !== 'any' && $fromCondition !== $fromStatus) {
return false;
}
$toCondition = Arr::get($conditions, 'to_status', 'any');
if ($toCondition !== 'any' && $toCondition !== $toStatus) {
return false;
}
$subscriber = FunnelHelper::getSubscriber($subscriberData['email']);
if ($subscriber) {
$funnelSub = FunnelHelper::ifAlreadyInFunnel($funnel->id, $subscriber->id);
if ($funnelSub) {
$multipleRun = Arr::get($conditions, 'run_multiple') == 'yes';
if ($multipleRun) {
FunnelHelper::removeSubscribersFromFunnel($funnel->id, [$subscriber->id]);
}
return $multipleRun;
}
}
return true;
}
}
@@ -0,0 +1,183 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers;
use FluentCrm\App\Services\Funnel\BaseTrigger;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\CartHelper;
class SubscriptionActivatedTrigger extends BaseTrigger
{
public function __construct()
{
$this->triggerName = 'fluent_cart/subscription_activated';
$this->priority = 20;
$this->actionArgNum = 1;
parent::__construct();
}
public function getTrigger()
{
return [
'ribbon' => 'subscription',
'category' => __('FluentCart', 'fluent-crm'),
'label' => __('Subscription Activated', 'fluent-crm'),
'description' => __('This will start when a subscription is activated', 'fluent-crm'),
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><g id="surface1"><path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 2.398438 0 L 21.601562 0 C 22.925781 0 24 1.074219 24 2.398438 L 24 21.601562 C 24 22.925781 22.925781 24 21.601562 24 L 2.398438 24 C 1.074219 24 0 22.925781 0 21.601562 L 0 2.398438 C 0 1.074219 1.074219 0 2.398438 0 Z M 2.398438 0 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 10.925781 16.476562 L 3.769531 16.476562 L 4.894531 13.878906 C 5.222656 13.117188 5.972656 12.625 6.804688 12.625 L 15.328125 12.625 L 14.746094 13.964844 C 14.085938 15.488281 12.585938 16.476562 10.925781 16.476562 Z M 10.925781 16.476562 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 16.851562 11.394531 L 6.789062 11.394531 L 7.367188 10.054688 C 8.027344 8.53125 9.53125 7.542969 11.191406 7.542969 L 19.886719 7.542969 L 18.761719 10.140625 C 18.433594 10.902344 17.683594 11.394531 16.851562 11.394531 Z M 16.851562 11.394531 "/></g></svg>'
];
}
public function getFunnelSettingsDefaults()
{
return [
'subscription_status' => 'subscribed',
];
}
public function getSettingsFields($funnel)
{
$statuses = fluentcrm_subscriber_editable_statuses(true);
return [
'title' => __('Subscription Activated', 'fluent-crm'),
'sub_title' => __('This will start when a subscription is activated', 'fluent-crm'),
'fields' => [
'subscription_status' => [
'type' => 'select',
'options' => $statuses,
'is_multiple' => false,
'label' => __('Subscription Status', 'fluent-crm'),
'placeholder' => __('Select Status', 'fluent-crm'),
],
'subscription_status_info' => [
'type' => 'html',
'info' => '<b>' . __('An Automated double-optin email will be sent for new subscribers', 'fluent-crm') . '</b>',
'dependency' => [
'depends_on' => 'subscription_status',
'operator' => '=',
'value' => 'pending',
],
],
],
];
}
public function getConditionFields($funnel)
{
return [
'product_ids' => [
'type' => 'rest_selector',
'label' => __('Target Products (Subscription Only)', 'fluent-crm'),
'option_key' => 'fluent_cart_subscription_products',
'is_multiple' => true,
'help' => __('Select the products you want to include in the automation.', 'fluent-crm'),
'inline_help' => __('You can select multiple products. If you want to run for all products, then leave it empty', 'fluent-crm'),
],
'run_multiple' => [
'type' => 'yes_no_check',
'label' => '',
'check_label' => __('Restart the Automation Multiple times for a contact for this event. (Only enable if you want to restart automation for the same contact)', 'fluent-crm'),
'inline_help' => __('If enabled, it will restart the automation for a contact if the contact is already in the automation. Otherwise, it will skip if it already exists', 'fluent-crm'),
],
];
}
public function getFunnelConditionDefaults($funnel)
{
return [
'product_ids' => [],
'run_multiple' => 'no'
];
}
public function handle($funnel, $originalArgs)
{
$subscriptionData = $originalArgs[0];
$subscription = $subscriptionData['subscription'];
$order = $subscriptionData['order'];
$customer = $subscriptionData['customer'];
$subscriberData = CartHelper::prepareSubscriberData($customer);
if (!is_email($subscriberData['email'])) {
return;
}
$willProcess = $this->isProcessable($funnel, $order, $subscriberData);
if (!$willProcess) {
return;
}
$subscriberData = wp_parse_args($subscriberData, $funnel->settings);
$subscriberData['status'] = $subscriberData['subscription_status'];
unset($subscriberData['subscription_status']);
(new \FluentCrm\App\Services\Funnel\FunnelProcessor())->startFunnelSequence($funnel, $subscriberData, [
'source_trigger_name' => $this->triggerName,
'source_ref_id' => $order->id, // optional
]);
}
public function isProcessable($funnel, $order, $subscriberData)
{
$conditions = Arr::get($funnel, 'conditions', []);
$isProcessable = $this->checkConditions($conditions, $order, $subscriberData);
if(!$isProcessable){
return false;
}
$subscriber = FunnelHelper::getSubscriber($subscriberData['email']);
// check run_only_one
if ($subscriber) {
$funnelSub = FunnelHelper::ifAlreadyInFunnel($funnel->id, $subscriber->id);
if ($funnelSub) {
$multipleRun = Arr::get($conditions, 'run_multiple') == 'yes';
if ($multipleRun) {
if ($funnelSub->source_ref_id == $order->id) {
return false;
}
FunnelHelper::removeSubscribersFromFunnel($funnel->id, [$subscriber->id]);
}
return $multipleRun;
}
}
return true;
}
private function checkConditions($conditions, $order, $subscriber)
{
$selectedProductIds = Arr::get($conditions, 'product_ids', []);
if (empty($selectedProductIds)) {
return true; // No specific products, process all
}
$orderItems = Arr::get($order, 'order_items', []);
// Post IDs of ordered products are the product IDs in FluentCart
$orderedProductIds = [];
foreach ($orderItems as $item) {
$productId = $item->post_id;
if ($productId) {
$orderedProductIds[] = $productId;
}
}
$productMatch = !empty(array_intersect($selectedProductIds, $orderedProductIds));
return $productMatch; // Return true if any of the selected products match the ordered products
}
}
@@ -0,0 +1,183 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers;
use FluentCrm\App\Services\Funnel\BaseTrigger;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\CartHelper;
class SubscriptionCancelledTrigger extends BaseTrigger
{
public function __construct()
{
$this->triggerName = 'fluent_cart/subscription_canceled';
$this->priority = 20;
$this->actionArgNum = 1;
parent::__construct();
}
public function getTrigger()
{
return [
'ribbon' => 'subscription',
'category' => __('FluentCart', 'fluent-crm'),
'label' => __('Subscription Cancelled', 'fluent-crm'),
'description' => __('This will start when a subscription is cancelled', 'fluent-crm'),
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><g id="surface1"><path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 2.398438 0 L 21.601562 0 C 22.925781 0 24 1.074219 24 2.398438 L 24 21.601562 C 24 22.925781 22.925781 24 21.601562 24 L 2.398438 24 C 1.074219 24 0 22.925781 0 21.601562 L 0 2.398438 C 0 1.074219 1.074219 0 2.398438 0 Z M 2.398438 0 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 10.925781 16.476562 L 3.769531 16.476562 L 4.894531 13.878906 C 5.222656 13.117188 5.972656 12.625 6.804688 12.625 L 15.328125 12.625 L 14.746094 13.964844 C 14.085938 15.488281 12.585938 16.476562 10.925781 16.476562 Z M 10.925781 16.476562 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 16.851562 11.394531 L 6.789062 11.394531 L 7.367188 10.054688 C 8.027344 8.53125 9.53125 7.542969 11.191406 7.542969 L 19.886719 7.542969 L 18.761719 10.140625 C 18.433594 10.902344 17.683594 11.394531 16.851562 11.394531 Z M 16.851562 11.394531 "/></g></svg>'
];
}
public function getFunnelSettingsDefaults()
{
return [
'subscription_status' => 'subscribed',
];
}
public function getSettingsFields($funnel)
{
$statuses = fluentcrm_subscriber_editable_statuses(true);
return [
'title' => __('Subscription Cancelled', 'fluent-crm'),
'sub_title' => __('This will start when a subscription is cancelled', 'fluent-crm'),
'fields' => [
'subscription_status' => [
'type' => 'select',
'options' => $statuses,
'is_multiple' => false,
'label' => __('Subscription Status', 'fluent-crm'),
'placeholder' => __('Select Status', 'fluent-crm'),
],
'subscription_status_info' => [
'type' => 'html',
'info' => '<b>' . __('An Automated double-optin email will be sent for new subscribers', 'fluent-crm') . '</b>',
'dependency' => [
'depends_on' => 'subscription_status',
'operator' => '=',
'value' => 'pending',
],
],
],
];
}
public function getConditionFields($funnel)
{
return [
'product_ids' => [
'type' => 'rest_selector',
'label' => __('Target Products (Subscription Only)', 'fluent-crm'),
'option_key' => 'fluent_cart_subscription_products',
'is_multiple' => true,
'help' => __('Select the products you want to include in the automation.', 'fluent-crm'),
'inline_help' => __('You can select multiple products. If you want to run for all products, then leave it empty', 'fluent-crm'),
],
'run_multiple' => [
'type' => 'yes_no_check',
'label' => '',
'check_label' => __('Restart the Automation Multiple times for a contact for this event. (Only enable if you want to restart automation for the same contact)', 'fluent-crm'),
'inline_help' => __('If enabled, it will restart the automation for a contact if the contact is already in the automation. Otherwise, it will skip if it already exists', 'fluent-crm'),
],
];
}
public function getFunnelConditionDefaults($funnel)
{
return [
'product_ids' => [],
'run_multiple' => 'no'
];
}
public function handle($funnel, $originalArgs)
{
$subscriptionData = $originalArgs[0];
$subscription = $subscriptionData['subscription'];
$order = $subscriptionData['order'];
$customer = $subscriptionData['customer'];
$subscriberData = CartHelper::prepareSubscriberData($customer);
if (!is_email($subscriberData['email'])) {
return;
}
$willProcess = $this->isProcessable($funnel, $order, $subscriberData);
if (!$willProcess) {
return;
}
$subscriberData = wp_parse_args($subscriberData, $funnel->settings);
$subscriberData['status'] = $subscriberData['subscription_status'];
unset($subscriberData['subscription_status']);
(new \FluentCrm\App\Services\Funnel\FunnelProcessor())->startFunnelSequence($funnel, $subscriberData, [
'source_trigger_name' => $this->triggerName,
'source_ref_id' => $order->id, // optional
]);
}
public function isProcessable($funnel, $order, $subscriberData)
{
$conditions = Arr::get($funnel, 'conditions', []);
$isProcessable = $this->checkConditions($conditions, $order, $subscriberData);
if(!$isProcessable){
return false;
}
$subscriber = FunnelHelper::getSubscriber($subscriberData['email']);
// check run_only_one
if ($subscriber) {
$funnelSub = FunnelHelper::ifAlreadyInFunnel($funnel->id, $subscriber->id);
if ($funnelSub) {
$multipleRun = Arr::get($conditions, 'run_multiple') == 'yes';
if ($multipleRun) {
if ($funnelSub->source_ref_id == $order->id) {
return false;
}
FunnelHelper::removeSubscribersFromFunnel($funnel->id, [$subscriber->id]);
}
return $multipleRun;
}
}
return true;
}
private function checkConditions($conditions, $order, $subscriber)
{
$selectedProductIds = Arr::get($conditions, 'product_ids', []);
if (empty($selectedProductIds)) {
return true; // No specific products, process all
}
$orderItems = Arr::get($order, 'order_items', []);
// Post IDs of ordered products are the product IDs in FluentCart
$orderedProductIds = [];
foreach ($orderItems as $item) {
$productId = $item->post_id;
if ($productId) {
$orderedProductIds[] = $productId;
}
}
$productMatch = !empty(array_intersect($selectedProductIds, $orderedProductIds));
return $productMatch; // Return true if any of the selected products match the ordered products
}
}
@@ -0,0 +1,183 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers;
use FluentCrm\App\Services\Funnel\BaseTrigger;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\CartHelper;
class SubscriptionEndOfTermTrigger extends BaseTrigger
{
public function __construct()
{
$this->triggerName = 'fluent_cart/subscription_eot';
$this->priority = 20;
$this->actionArgNum = 1;
parent::__construct();
}
public function getTrigger()
{
return [
'ribbon' => 'subscription',
'category' => __('FluentCart', 'fluent-crm'),
'label' => __('Subscription End of Term (Completed)', 'fluent-crm'),
'description' => __('This will start when a subscription reaches it\'s end of term(completed)', 'fluent-crm'),
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><g id="surface1"><path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 2.398438 0 L 21.601562 0 C 22.925781 0 24 1.074219 24 2.398438 L 24 21.601562 C 24 22.925781 22.925781 24 21.601562 24 L 2.398438 24 C 1.074219 24 0 22.925781 0 21.601562 L 0 2.398438 C 0 1.074219 1.074219 0 2.398438 0 Z M 2.398438 0 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 10.925781 16.476562 L 3.769531 16.476562 L 4.894531 13.878906 C 5.222656 13.117188 5.972656 12.625 6.804688 12.625 L 15.328125 12.625 L 14.746094 13.964844 C 14.085938 15.488281 12.585938 16.476562 10.925781 16.476562 Z M 10.925781 16.476562 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 16.851562 11.394531 L 6.789062 11.394531 L 7.367188 10.054688 C 8.027344 8.53125 9.53125 7.542969 11.191406 7.542969 L 19.886719 7.542969 L 18.761719 10.140625 C 18.433594 10.902344 17.683594 11.394531 16.851562 11.394531 Z M 16.851562 11.394531 "/></g></svg>'
];
}
public function getFunnelSettingsDefaults()
{
return [
'subscription_status' => 'subscribed',
];
}
public function getSettingsFields($funnel)
{
$statuses = fluentcrm_subscriber_editable_statuses(true);
return [
'title' => __('Subscription End of Term (Completed)', 'fluent-crm'),
'sub_title' => __('This will start when a subscription reaches it\'s end of term(completed)', 'fluent-crm'),
'fields' => [
'subscription_status' => [
'type' => 'select',
'options' => $statuses,
'is_multiple' => false,
'label' => __('Subscription Status', 'fluent-crm'),
'placeholder' => __('Select Status', 'fluent-crm'),
],
'subscription_status_info' => [
'type' => 'html',
'info' => '<b>' . __('An Automated double-optin email will be sent for new subscribers', 'fluent-crm') . '</b>',
'dependency' => [
'depends_on' => 'subscription_status',
'operator' => '=',
'value' => 'pending',
],
],
],
];
}
public function getConditionFields($funnel)
{
return [
'product_ids' => [
'type' => 'rest_selector',
'label' => __('Target Products (Subscription Only)', 'fluent-crm'),
'option_key' => 'fluent_cart_subscription_products',
'is_multiple' => true,
'help' => __('Select the products you want to include in the automation.', 'fluent-crm'),
'inline_help' => __('You can select multiple products. If you want to run for all products, then leave it empty', 'fluent-crm'),
],
'run_multiple' => [
'type' => 'yes_no_check',
'label' => '',
'check_label' => __('Restart the Automation Multiple times for a contact for this event. (Only enable if you want to restart automation for the same contact)', 'fluent-crm'),
'inline_help' => __('If enabled, it will restart the automation for a contact if the contact is already in the automation. Otherwise, it will skip if it already exists', 'fluent-crm'),
],
];
}
public function getFunnelConditionDefaults($funnel)
{
return [
'product_ids' => [],
'run_multiple' => 'no'
];
}
public function handle($funnel, $originalArgs)
{
$subscriptionData = $originalArgs[0];
$subscription = $subscriptionData['subscription'];
$order = $subscriptionData['order'];
$customer = $subscriptionData['customer'];
$subscriberData = CartHelper::prepareSubscriberData($customer);
if (!is_email($subscriberData['email'])) {
return;
}
$willProcess = $this->isProcessable($funnel, $order, $subscriberData);
if (!$willProcess) {
return;
}
$subscriberData = wp_parse_args($subscriberData, $funnel->settings);
$subscriberData['status'] = $subscriberData['subscription_status'];
unset($subscriberData['subscription_status']);
(new \FluentCrm\App\Services\Funnel\FunnelProcessor())->startFunnelSequence($funnel, $subscriberData, [
'source_trigger_name' => $this->triggerName,
'source_ref_id' => $order->id, // optional
]);
}
public function isProcessable($funnel, $order, $subscriberData)
{
$conditions = Arr::get($funnel, 'conditions', []);
$isProcessable = $this->checkConditions($conditions, $order, $subscriberData);
if(!$isProcessable){
return false;
}
$subscriber = FunnelHelper::getSubscriber($subscriberData['email']);
// check run_only_one
if ($subscriber) {
$funnelSub = FunnelHelper::ifAlreadyInFunnel($funnel->id, $subscriber->id);
if ($funnelSub) {
$multipleRun = Arr::get($conditions, 'run_multiple') == 'yes';
if ($multipleRun) {
if ($funnelSub->source_ref_id == $order->id) {
return false;
}
FunnelHelper::removeSubscribersFromFunnel($funnel->id, [$subscriber->id]);
}
return $multipleRun;
}
}
return true;
}
private function checkConditions($conditions, $order, $subscriber)
{
$selectedProductIds = Arr::get($conditions, 'product_ids', []);
if (empty($selectedProductIds)) {
return true; // No specific products, process all
}
$orderItems = Arr::get($order, 'order_items', []);
// Post IDs of ordered products are the product IDs in FluentCart
$orderedProductIds = [];
foreach ($orderItems as $item) {
$productId = $item->post_id;
if ($productId) {
$orderedProductIds[] = $productId;
}
}
$productMatch = !empty(array_intersect($selectedProductIds, $orderedProductIds));
return $productMatch; // Return true if any of the selected products match the ordered products
}
}
@@ -0,0 +1,184 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers;
use FluentCrm\App\Services\Funnel\BaseTrigger;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\CartHelper;
class SubscriptionExpiredTrigger extends BaseTrigger
{
public function __construct()
{
$this->triggerName = 'fluent_cart/subscription_expired_validity';
$this->priority = 20;
$this->actionArgNum = 1;
parent::__construct();
}
public function getTrigger()
{
return [
'ribbon' => 'subscription',
'category' => __('FluentCart', 'fluent-crm'),
'label' => __('Subscription Expired / End of Access Validity', 'fluent-crm'),
'description' => __('This will start when a subscription expires', 'fluent-crm'),
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><g id="surface1"><path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 2.398438 0 L 21.601562 0 C 22.925781 0 24 1.074219 24 2.398438 L 24 21.601562 C 24 22.925781 22.925781 24 21.601562 24 L 2.398438 24 C 1.074219 24 0 22.925781 0 21.601562 L 0 2.398438 C 0 1.074219 1.074219 0 2.398438 0 Z M 2.398438 0 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 10.925781 16.476562 L 3.769531 16.476562 L 4.894531 13.878906 C 5.222656 13.117188 5.972656 12.625 6.804688 12.625 L 15.328125 12.625 L 14.746094 13.964844 C 14.085938 15.488281 12.585938 16.476562 10.925781 16.476562 Z M 10.925781 16.476562 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 16.851562 11.394531 L 6.789062 11.394531 L 7.367188 10.054688 C 8.027344 8.53125 9.53125 7.542969 11.191406 7.542969 L 19.886719 7.542969 L 18.761719 10.140625 C 18.433594 10.902344 17.683594 11.394531 16.851562 11.394531 Z M 16.851562 11.394531 "/></g></svg>'
];
}
public function getFunnelSettingsDefaults()
{
return [
'subscription_status' => 'subscribed',
];
}
public function getSettingsFields($funnel)
{
$statuses = fluentcrm_subscriber_editable_statuses(true);
return [
'title' => __('Subscription Expired / End of Access Validity', 'fluent-crm'),
'sub_title' => __('This will start when a subscription expires', 'fluent-crm'),
'fields' => [
'subscription_status' => [
'type' => 'select',
'options' => $statuses,
'is_multiple' => false,
'label' => __('Subscription Status', 'fluent-crm'),
'placeholder' => __('Select Status', 'fluent-crm'),
],
'subscription_status_info' => [
'type' => 'html',
'info' => '<b>' . __('An Automated double-optin email will be sent for new subscribers', 'fluent-crm') . '</b>',
'dependency' => [
'depends_on' => 'subscription_status',
'operator' => '=',
'value' => 'pending',
],
],
],
];
}
public function getConditionFields($funnel)
{
return [
'product_ids' => [
'type' => 'rest_selector',
'label' => __('Target Products (Subscription Only)', 'fluent-crm'),
'option_key' => 'fluent_cart_subscription_products',
'is_multiple' => true,
'help' => __('Select the products you want to include in the automation.', 'fluent-crm'),
'inline_help' => __('You can select multiple products. If you want to run for all products, then leave it empty', 'fluent-crm'),
],
'run_multiple' => [
'type' => 'yes_no_check',
'label' => '',
'check_label' => __('Restart the Automation Multiple times for a contact for this event. (Only enable if you want to restart automation for the same contact)', 'fluent-crm'),
'inline_help' => __('If enabled, it will restart the automation for a contact if the contact is already in the automation. Otherwise, it will skip if it already exists', 'fluent-crm'),
],
];
}
public function getFunnelConditionDefaults($funnel)
{
return [
'product_ids' => [],
'run_multiple' => 'no'
];
}
public function handle($funnel, $originalArgs)
{
$subscriptionData = $originalArgs[0];
$subscription = $subscriptionData['subscription'];
$order = $subscriptionData['order'];
$customer = $subscriptionData['customer'];
// $oldStatus = $subscriptionData['old_status'];
// $newStatus = $subscription->status;
$subscriberData = CartHelper::prepareSubscriberData($customer);
if (!is_email($subscriberData['email'])) {
return;
}
$willProcess = $this->isProcessable($funnel, $order, $subscriberData);
if (!$willProcess) {
return;
}
$subscriberData = wp_parse_args($subscriberData, $funnel->settings);
$subscriberData['status'] = $subscriberData['subscription_status'];
unset($subscriberData['subscription_status']);
(new \FluentCrm\App\Services\Funnel\FunnelProcessor())->startFunnelSequence($funnel, $subscriberData, [
'source_trigger_name' => $this->triggerName,
'source_ref_id' => $order->id, // optional
]);
}
public function isProcessable($funnel, $order, $subscriberData)
{
$conditions = Arr::get($funnel, 'conditions', []);
$isProcessable = $this->checkConditions($conditions, $order, $subscriberData);
if(!$isProcessable){
return false;
}
$subscriber = FunnelHelper::getSubscriber($subscriberData['email']);
// check run_only_one
if ($subscriber) {
$funnelSub = FunnelHelper::ifAlreadyInFunnel($funnel->id, $subscriber->id);
if ($funnelSub) {
$multipleRun = Arr::get($conditions, 'run_multiple') == 'yes';
if ($multipleRun) {
if ($funnelSub->source_ref_id == $order->id) {
return false;
}
FunnelHelper::removeSubscribersFromFunnel($funnel->id, [$subscriber->id]);
}
return $multipleRun;
}
}
return true;
}
private function checkConditions($conditions, $order, $subscriber)
{
$selectedProductIds = Arr::get($conditions, 'product_ids', []);
if (empty($selectedProductIds)) {
return true; // No specific products, process all
}
$orderItems = Arr::get($order, 'order_items', []);
// Post IDs of ordered products are the product IDs in FluentCart
$orderedProductIds = [];
foreach ($orderItems as $item) {
$productId = $item->post_id;
if ($productId) {
$orderedProductIds[] = $productId;
}
}
$productMatch = !empty(array_intersect($selectedProductIds, $orderedProductIds));
return $productMatch; // Return true if any of the selected products match the ordered products
}
}
@@ -0,0 +1,183 @@
<?php
namespace FluentCrm\App\Services\ExternalIntegrations\FluentCart\Triggers;
use FluentCrm\App\Services\Funnel\BaseTrigger;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\App\Services\ExternalIntegrations\FluentCart\CartHelper;
class SubscriptionRenewedTrigger extends BaseTrigger
{
public function __construct()
{
$this->triggerName = 'fluent_cart/subscription_renewed';
$this->priority = 20;
$this->actionArgNum = 1;
parent::__construct();
}
public function getTrigger()
{
return [
'ribbon' => 'subscription',
'category' => __('FluentCart', 'fluent-crm'),
'label' => __('Subscription Renewed', 'fluent-crm'),
'description' => __('This will start when a subscription is renewed', 'fluent-crm'),
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1"><g id="surface1"><path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 2.398438 0 L 21.601562 0 C 22.925781 0 24 1.074219 24 2.398438 L 24 21.601562 C 24 22.925781 22.925781 24 21.601562 24 L 2.398438 24 C 1.074219 24 0 22.925781 0 21.601562 L 0 2.398438 C 0 1.074219 1.074219 0 2.398438 0 Z M 2.398438 0 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 10.925781 16.476562 L 3.769531 16.476562 L 4.894531 13.878906 C 5.222656 13.117188 5.972656 12.625 6.804688 12.625 L 15.328125 12.625 L 14.746094 13.964844 C 14.085938 15.488281 12.585938 16.476562 10.925781 16.476562 Z M 10.925781 16.476562 "/><path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,62.352943%);fill-opacity:1;" d="M 16.851562 11.394531 L 6.789062 11.394531 L 7.367188 10.054688 C 8.027344 8.53125 9.53125 7.542969 11.191406 7.542969 L 19.886719 7.542969 L 18.761719 10.140625 C 18.433594 10.902344 17.683594 11.394531 16.851562 11.394531 Z M 16.851562 11.394531 "/></g></svg>'
];
}
public function getFunnelSettingsDefaults()
{
return [
'subscription_status' => 'subscribed',
];
}
public function getSettingsFields($funnel)
{
$statuses = fluentcrm_subscriber_editable_statuses(true);
return [
'title' => __('Subscription Renewed', 'fluent-crm'),
'sub_title' => __('This will start when a subscription is renewed', 'fluent-crm'),
'fields' => [
'subscription_status' => [
'type' => 'select',
'options' => $statuses,
'is_multiple' => false,
'label' => __('Subscription Status', 'fluent-crm'),
'placeholder' => __('Select Status', 'fluent-crm'),
],
'subscription_status_info' => [
'type' => 'html',
'info' => '<b>' . __('An Automated double-optin email will be sent for new subscribers', 'fluent-crm') . '</b>',
'dependency' => [
'depends_on' => 'subscription_status',
'operator' => '=',
'value' => 'pending',
],
],
],
];
}
public function getConditionFields($funnel)
{
return [
'product_ids' => [
'type' => 'rest_selector',
'label' => __('Target Products (Subscription Only)', 'fluent-crm'),
'option_key' => 'fluent_cart_subscription_products',
'is_multiple' => true,
'help' => __('Select the products you want to include in the automation.', 'fluent-crm'),
'inline_help' => __('You can select multiple products. If you want to run for all products, then leave it empty', 'fluent-crm'),
],
'run_multiple' => [
'type' => 'yes_no_check',
'label' => '',
'check_label' => __('Restart the Automation Multiple times for a contact for this event. (Only enable if you want to restart automation for the same contact)', 'fluent-crm'),
'inline_help' => __('If enabled, it will restart the automation for a contact if the contact is already in the automation. Otherwise, it will skip if it already exists', 'fluent-crm'),
],
];
}
public function getFunnelConditionDefaults($funnel)
{
return [
'product_ids' => [],
'run_multiple' => 'no'
];
}
public function handle($funnel, $originalArgs)
{
$subscriptionData = $originalArgs[0];
$subscription = $subscriptionData['subscription'];
$order = $subscriptionData['order'];
$customer = $subscriptionData['customer'];
$subscriberData = CartHelper::prepareSubscriberData($customer);
if (!is_email($subscriberData['email'])) {
return;
}
$willProcess = $this->isProcessable($funnel, $order, $subscriberData);
if (!$willProcess) {
return;
}
$subscriberData = wp_parse_args($subscriberData, $funnel->settings);
$subscriberData['status'] = $subscriberData['subscription_status'];
unset($subscriberData['subscription_status']);
(new \FluentCrm\App\Services\Funnel\FunnelProcessor())->startFunnelSequence($funnel, $subscriberData, [
'source_trigger_name' => $this->triggerName,
'source_ref_id' => $order->id, // optional
]);
}
public function isProcessable($funnel, $order, $subscriberData)
{
$conditions = Arr::get($funnel, 'conditions', []);
$isProcessable = $this->checkConditions($conditions, $order, $subscriberData);
if(!$isProcessable){
return false;
}
$subscriber = FunnelHelper::getSubscriber($subscriberData['email']);
// check run_only_one
if ($subscriber) {
$funnelSub = FunnelHelper::ifAlreadyInFunnel($funnel->id, $subscriber->id);
if ($funnelSub) {
$multipleRun = Arr::get($conditions, 'run_multiple') == 'yes';
if ($multipleRun) {
if ($funnelSub->source_ref_id == $order->id) {
return false;
}
FunnelHelper::removeSubscribersFromFunnel($funnel->id, [$subscriber->id]);
}
return $multipleRun;
}
}
return true;
}
private function checkConditions($conditions, $order, $subscriber)
{
$selectedProductIds = Arr::get($conditions, 'product_ids', []);
if (empty($selectedProductIds)) {
return true; // No specific products, process all
}
$orderItems = Arr::get($order, 'order_items', []);
// Post IDs of ordered products are the product IDs in FluentCart
$orderedProductIds = [];
foreach ($orderItems as $item) {
$productId = $item->post_id;
if ($productId) {
$orderedProductIds[] = $productId;
}
}
$productMatch = !empty(array_intersect($selectedProductIds, $orderedProductIds));
return $productMatch; // Return true if any of the selected products match the ordered products
}
}