Initial commit
This commit is contained in:
+136
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+220
@@ -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;
|
||||
}
|
||||
}
|
||||
+332
@@ -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
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
+211
@@ -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;
|
||||
}
|
||||
}
|
||||
+508
@@ -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;
|
||||
}
|
||||
}
|
||||
+93
@@ -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;
|
||||
}
|
||||
}
|
||||
+297
@@ -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();
|
||||
}
|
||||
}
|
||||
+98
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+218
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+218
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+218
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+218
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+218
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+223
@@ -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;
|
||||
}
|
||||
}
|
||||
+183
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
+183
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+183
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
+184
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
+183
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user