Initial commit
This commit is contained in:
+124
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrm\App\Services\ExternalIntegrations;
|
||||
|
||||
|
||||
class BricksBuilderIntegration
|
||||
{
|
||||
|
||||
public function register()
|
||||
{
|
||||
add_filter('bricks/conditions/groups', [$this, 'addConditionGroup']);
|
||||
add_filter('bricks/conditions/options', [$this, 'addConditionOptions']);
|
||||
add_filter('bricks/conditions/result', [$this, 'checkCondition'], 10, 3);
|
||||
}
|
||||
|
||||
public function addConditionGroup($groups)
|
||||
{
|
||||
// Ensure your group name is unique (best to prefix it)
|
||||
$groups[] = [
|
||||
'name' => 'fluent_crm',
|
||||
'label' => esc_html__('FluentCRM', 'fluent-crm'),
|
||||
];
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
public function addConditionOptions($options)
|
||||
{
|
||||
// Ensure key is unique, and that group exists
|
||||
$tags = \FluentCrm\App\Models\Tag::select(['id', 'title'])->orderBy('title', 'ASC')->get();
|
||||
$lists = \FluentCrm\App\Models\Lists::select(['id', 'title'])->orderBy('title', 'ASC')->get();
|
||||
|
||||
$formattedTags = [];
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$formattedTags[$tag->id] = $tag->title;
|
||||
}
|
||||
|
||||
$formattedLists = [];
|
||||
|
||||
foreach ($lists as $list) {
|
||||
$formattedLists[$list->id] = $list->title;
|
||||
}
|
||||
|
||||
$options[] = [
|
||||
'key' => 'fluent_crm_tags',
|
||||
'label' => esc_html__('FluentCRM Tags', 'fluent-crm'),
|
||||
'group' => 'fluent_crm',
|
||||
'compare' => [
|
||||
'type' => 'select',
|
||||
'options' => [
|
||||
'==' => esc_html__('includes in', 'fluent-crm'),
|
||||
'!=' => esc_html__('not includes', 'fluent-crm'),
|
||||
],
|
||||
'placeholder' => esc_html__('is', 'fluent-crm'),
|
||||
],
|
||||
'value' => [
|
||||
'type' => 'select',
|
||||
'multiple' => true,
|
||||
'options' => $formattedTags,
|
||||
'placeholder' => esc_html__('Select Tags', 'fluent-crm'),
|
||||
],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'key' => 'fluent_crm_lists',
|
||||
'label' => esc_html__('FluentCRM Lists', 'fluent-crm'),
|
||||
'group' => 'fluent_crm',
|
||||
'compare' => [
|
||||
'type' => 'select',
|
||||
'options' => [
|
||||
'==' => esc_html__('includes in', 'fluent-crm'),
|
||||
'!=' => esc_html__('not includes', 'fluent-crm'),
|
||||
],
|
||||
'placeholder' => esc_html__('is', 'fluent-crm'),
|
||||
],
|
||||
'value' => [
|
||||
'type' => 'select',
|
||||
'multiple' => true,
|
||||
'options' => $formattedLists,
|
||||
'placeholder' => esc_html__('Select Lists', 'fluent-crm'),
|
||||
],
|
||||
];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function checkCondition($result, $condition_key, $condition)
|
||||
{
|
||||
$acceptedKeys = ['fluent_crm_tags', 'fluent_crm_lists'];
|
||||
if (!in_array($condition_key, $acceptedKeys, true)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// In my example, if compare is empty, we set it to '==' as default
|
||||
$compare = \FluentCrm\Framework\Support\Arr::get($condition, 'compare', '==');
|
||||
|
||||
$targetIds = \FluentCrm\Framework\Support\Arr::get($condition, 'value', []);
|
||||
$targetIds = array_filter(array_map('intval', $targetIds));
|
||||
|
||||
if (!$compare || !$targetIds) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$currentContact = fluentcrm_get_current_contact();
|
||||
|
||||
if (!$currentContact) {
|
||||
return $compare != '==';
|
||||
}
|
||||
|
||||
if ($condition_key == 'fluent_crm_tags') {
|
||||
$result = $currentContact->hasAnyTagId($targetIds);
|
||||
} else {
|
||||
$result = $currentContact->hasAnyListId($targetIds);
|
||||
}
|
||||
|
||||
if ($compare == '==') {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return !$result;
|
||||
}
|
||||
|
||||
}
|
||||
+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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+659
@@ -0,0 +1,659 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrm\App\Services\ExternalIntegrations\FluentForm;
|
||||
|
||||
use FluentCrm\App\Models\CustomContactField;
|
||||
use FluentCrm\App\Models\Lists;
|
||||
use FluentCrm\App\Models\Subscriber;
|
||||
use FluentCrm\App\Models\Tag;
|
||||
use FluentCrm\App\Services\Funnel\FunnelHelper;
|
||||
use FluentCrm\Framework\Support\Arr;
|
||||
use FluentForm\App\Http\Controllers\IntegrationManagerController;
|
||||
use FluentForm\App\Modules\Form\FormFieldsParser;
|
||||
use FluentForm\App\Services\FormBuilder\ShortCodeParser;
|
||||
use FluentForm\App\Services\Integrations\GlobalNotificationService;
|
||||
use FluentForm\Framework\Helpers\ArrayHelper;
|
||||
|
||||
class Bootstrap extends IntegrationManagerController
|
||||
{
|
||||
public $hasGlobalMenu = false;
|
||||
|
||||
public $disableGlobalSettings = 'yes';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
null,
|
||||
__('FluentCRM', 'fluent-crm'),
|
||||
'fluentcrm',
|
||||
'_fluentform_fluentcrm_settings',
|
||||
'fluentcrm_feeds',
|
||||
10
|
||||
);
|
||||
|
||||
$this->logo = FLUENTCRM_PLUGIN_URL . 'assets/images/fluentcrm-logo.svg';
|
||||
|
||||
$this->description = __('Connect FluentCRM with WP Fluent Forms and subscribe a contact when a form is submitted.', 'fluent-crm');
|
||||
|
||||
$this->registerAdminHooks();
|
||||
|
||||
add_filter('fluentform/notifying_async_fluentcrm', '__return_false');
|
||||
|
||||
$this->registerPaymentEvents();
|
||||
|
||||
}
|
||||
|
||||
public function pushIntegration($integrations, $formId)
|
||||
{
|
||||
$integrations[$this->integrationKey] = [
|
||||
'title' => $this->title . ' Integration',
|
||||
'logo' => $this->logo,
|
||||
'is_active' => $this->isConfigured(),
|
||||
'configure_title' => __('Configuration required!', 'fluent-crm'),
|
||||
'global_configure_url' => '#',
|
||||
'configure_message' => __('FluentCRM is not configured yet! Please configure your FluentCRM API first', 'fluent-crm'),
|
||||
'configure_button_text' => __('Set FluentCRM', 'fluent-crm')
|
||||
];
|
||||
return $integrations;
|
||||
}
|
||||
|
||||
public function getIntegrationDefaults($settings, $formId)
|
||||
{
|
||||
return [
|
||||
'name' => '',
|
||||
'first_name' => '',
|
||||
'last_name' => '',
|
||||
'full_name' => '',
|
||||
'email' => '',
|
||||
'other_fields' => [
|
||||
[
|
||||
'item_value' => '',
|
||||
'label' => ''
|
||||
]
|
||||
],
|
||||
'list_id' => '',
|
||||
'tag_ids' => [],
|
||||
'tag_ids_selection_type' => 'simple',
|
||||
'tag_routers' => [],
|
||||
'skip_if_exists' => false,
|
||||
'double_opt_in' => false,
|
||||
'force_subscribe' => false,
|
||||
'skip_primary_data' => false,
|
||||
'conditionals' => [
|
||||
'conditions' => [],
|
||||
'status' => false,
|
||||
'type' => 'all'
|
||||
],
|
||||
'run_events_only' => [],
|
||||
'remove_tags' => [],
|
||||
'enabled' => true
|
||||
];
|
||||
}
|
||||
|
||||
public function getSettingsFields($settings, $formId)
|
||||
{
|
||||
$form = fluentFormApi('forms')->find($formId);
|
||||
$paymentFields = FormFieldsParser::getPaymentFields($form, ['element']);
|
||||
|
||||
$fieldOptions = [];
|
||||
|
||||
foreach (Subscriber::mappables() as $key => $column) {
|
||||
$fieldOptions[$key] = $column;
|
||||
}
|
||||
|
||||
foreach ((new CustomContactField)->getGlobalFields()['fields'] as $field) {
|
||||
$fieldOptions[$field['slug']] = $field['label'];
|
||||
}
|
||||
|
||||
$fieldOptions['avatar'] = 'Profile Photo';
|
||||
|
||||
unset($fieldOptions['email']);
|
||||
unset($fieldOptions['first_name']);
|
||||
unset($fieldOptions['last_name']);
|
||||
|
||||
$fields = [
|
||||
[
|
||||
'key' => 'name',
|
||||
'label' => __('Feed Name', 'fluent-crm'),
|
||||
'required' => true,
|
||||
'placeholder' => __('Your Feed Name', 'fluent-crm'),
|
||||
'component' => 'text'
|
||||
],
|
||||
[
|
||||
'key' => 'list_id',
|
||||
'label' => __('FluentCRM List', 'fluent-crm'),
|
||||
'placeholder' => __('Select FluentCRM List', 'fluent-crm'),
|
||||
'tips' => __('Select the FluentCRM List you would like to add your contacts to.', 'fluent-crm'),
|
||||
'component' => 'select',
|
||||
'required' => false,
|
||||
'options' => $this->getLists(),
|
||||
],
|
||||
[
|
||||
'key' => 'CustomFields',
|
||||
'require_list' => false,
|
||||
'label' => __('Primary Fields', 'fluent-crm'),
|
||||
'tips' => __('Associate your FluentCRM merge tags to the appropriate Fluent Form fields by selecting the appropriate form field from the list.', 'fluent-crm'),
|
||||
'component' => 'map_fields',
|
||||
'field_label_remote' => __('FluentCRM Field', 'fluent-crm'),
|
||||
'field_label_local' => __('Form Field', 'fluent-crm'),
|
||||
'primary_fileds' => [
|
||||
[
|
||||
'key' => 'email',
|
||||
'label' => __('Email Address', 'fluent-crm'),
|
||||
'required' => true,
|
||||
'input_options' => 'emails'
|
||||
],
|
||||
[
|
||||
'key' => 'first_name',
|
||||
'label' => __('First Name', 'fluent-crm')
|
||||
],
|
||||
[
|
||||
'key' => 'last_name',
|
||||
'label' => __('Last Name', 'fluent-crm')
|
||||
],
|
||||
[
|
||||
'key' => 'full_name',
|
||||
'label' => __('Full Name', 'fluent-crm'),
|
||||
'help_text' => __('If First Name & Last Name is not available full name will be used to get first name and last name', 'fluent-crm')
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'other_fields',
|
||||
'require_list' => false,
|
||||
'label' => __('Other Fields', 'fluent-crm'),
|
||||
'tips' => __('Select which Fluent Form fields pair with their<br /> respective FluentCRM fields.', 'fluent-crm'),
|
||||
'component' => 'dropdown_many_fields',
|
||||
'field_label_remote' => __('FluentCRM Field', 'fluent-crm'),
|
||||
'field_label_local' => __('Form Field', 'fluent-crm'),
|
||||
'options' => $fieldOptions
|
||||
],
|
||||
[
|
||||
'key' => 'tag_ids',
|
||||
'require_list' => false,
|
||||
'label' => __('Contact Tags', 'fluent-crm'),
|
||||
'placeholder' => __('Select Tags', 'fluent-crm'),
|
||||
'component' => 'selection_routing',
|
||||
'simple_component' => 'select',
|
||||
'routing_input_type' => 'select',
|
||||
'routing_key' => 'tag_ids_selection_type',
|
||||
'settings_key' => 'tag_routers',
|
||||
'is_multiple' => true,
|
||||
'labels' => [
|
||||
'choice_label' => __('Enable Dynamic Tag Selection', 'fluent-crm'),
|
||||
'input_label' => '',
|
||||
'input_placeholder' => __('Set Tag', 'fluent-crm')
|
||||
],
|
||||
'options' => $this->getTags()
|
||||
],
|
||||
[
|
||||
'key' => 'skip_if_exists',
|
||||
'require_list' => false,
|
||||
'checkbox_label' => __('Skip if contact already exists in FluentCRM', 'fluent-crm'),
|
||||
'component' => 'checkbox-single'
|
||||
],
|
||||
[
|
||||
'key' => 'skip_primary_data',
|
||||
'require_list' => false,
|
||||
'checkbox_label' => __('Skip name update if an existing contact has old data (per primary field)', 'fluent-crm'),
|
||||
'component' => 'checkbox-single'
|
||||
],
|
||||
[
|
||||
'key' => 'double_opt_in',
|
||||
'require_list' => false,
|
||||
'checkbox_label' => __('Enable Double opt-in for new contacts', 'fluent-crm'),
|
||||
'component' => 'checkbox-single'
|
||||
],
|
||||
[
|
||||
'key' => 'force_subscribe',
|
||||
'require_list' => false,
|
||||
'checkbox_label' => __('Enable Force Subscribe if contact is not in subscribed status (Existing contact only)', 'fluent-crm'),
|
||||
'component' => 'checkbox-single',
|
||||
'inline_tip' => __('If you enable this, the contact will be forcefully subscribed regardless of the contact\'s current status', 'fluent-crm')
|
||||
],
|
||||
[
|
||||
'require_list' => false,
|
||||
'key' => 'conditionals',
|
||||
'label' => __('Conditional Logics', 'fluent-crm'),
|
||||
'tips' => __('Allow FluentCRM integration conditionally based on your submission values', 'fluent-crm'),
|
||||
'component' => 'conditional_block'
|
||||
]
|
||||
];
|
||||
|
||||
if ($paymentFields) {
|
||||
$hasSubscriptionFields = !!FormFieldsParser::getInputsByElementTypes($form, ['subscription_payment_component']);
|
||||
|
||||
$options = [
|
||||
'fluentform/payment_refunded' => __('On Payment Refund', 'fluent-crm')
|
||||
];
|
||||
|
||||
if ($hasSubscriptionFields) {
|
||||
$options = [
|
||||
'fluentform/subscription_payment_active' => __('On Subscription Active', 'fluent-crm'),
|
||||
'fluentform/subscription_payment_canceled' => __('On Subscription Cancel', 'fluent-crm'),
|
||||
'fluentform/payment_refunded' => __('On Payment Refund', 'fluent-crm')
|
||||
];
|
||||
}
|
||||
|
||||
$fields[] = [
|
||||
'require_list' => false,
|
||||
'key' => 'run_events_only',
|
||||
'label' => __('Run only on events', 'fluent-crm'),
|
||||
'component' => 'checkbox-multiple-text',
|
||||
'options' => $options,
|
||||
'tips' => __('If you check any of the events, then this feed will only run for the selected events', 'fluent-crm')
|
||||
];
|
||||
}
|
||||
|
||||
$fields[] = [
|
||||
'require_list' => false,
|
||||
'key' => 'remove_tags',
|
||||
'label' => __('Remove Contact Tags', 'fluent-crm'),
|
||||
'placeholder' => __('Select Tags (remove from contact)', 'fluent-crm'),
|
||||
'tips' => __('(Optional) The selected tags will be removed from the contact (if it exists)', 'fluent-crm'),
|
||||
'component' => 'select',
|
||||
'is_multiple' => true,
|
||||
'required' => false,
|
||||
'options' => $this->getTags(),
|
||||
];
|
||||
|
||||
$fields[] = [
|
||||
'require_list' => false,
|
||||
'key' => 'enabled',
|
||||
'label' => __('Status', 'fluent-crm'),
|
||||
'component' => 'checkbox-single',
|
||||
'checkbox_label' => __('Enable This feed', 'fluent-crm')
|
||||
];
|
||||
|
||||
return [
|
||||
'fields' => $fields,
|
||||
'button_require_list' => false,
|
||||
'integration_title' => $this->title
|
||||
];
|
||||
}
|
||||
|
||||
public function getMergeFields($list, $listId, $formId)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getLists()
|
||||
{
|
||||
$lists = Lists::orderBy('title', 'ASC')->get();
|
||||
$formattedLists = [];
|
||||
foreach ($lists as $list) {
|
||||
$formattedLists[$list->id] = $list->title;
|
||||
}
|
||||
return $formattedLists;
|
||||
}
|
||||
|
||||
protected function getTags()
|
||||
{
|
||||
$tags = Tag::orderBy('title', 'ASC')->get();
|
||||
$formattedTags = [];
|
||||
foreach ($tags as $tag) {
|
||||
$formattedTags[strval($tag->id)] = $tag->title;
|
||||
}
|
||||
return $formattedTags;
|
||||
}
|
||||
|
||||
/*
|
||||
* Form Submission Hooks Here
|
||||
*/
|
||||
public function notify($feed, $formData, $entry, $form)
|
||||
{
|
||||
// check if only on payment event
|
||||
if (Arr::get($feed, 'settings.run_events_only')) {
|
||||
// We have running events selected. So we may not run this feed.
|
||||
$paymentFields = FormFieldsParser::getPaymentFields($form, ['element']);
|
||||
if ($paymentFields) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->runFeed($feed, $formData, $entry, $form);
|
||||
}
|
||||
|
||||
|
||||
private function runFeed($feed, $formData, $entry, $form)
|
||||
{
|
||||
$data = $feed['processedValues'];
|
||||
$contact = Arr::only($data, ['first_name', 'last_name', 'email']);
|
||||
|
||||
if (!is_email($contact['email'])) {
|
||||
$contact['email'] = ArrayHelper::get($formData, $contact['email']);
|
||||
}
|
||||
|
||||
if (!$contact['first_name'] && !$contact['last_name']) {
|
||||
$fullName = Arr::get($data, 'full_name');
|
||||
if ($fullName) {
|
||||
$nameArray = explode(' ', $fullName);
|
||||
if (count($nameArray) > 1) {
|
||||
$contact['last_name'] = array_pop($nameArray);
|
||||
$contact['first_name'] = implode(' ', $nameArray);
|
||||
} else {
|
||||
$contact['first_name'] = $fullName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Arr::get($data, 'other_fields') as $field) {
|
||||
if ($field['item_value']) {
|
||||
$contact[$field['label']] = str_replace('<br />', ' ', $field['item_value']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($entry->ip) {
|
||||
$contact['ip'] = $entry->ip;
|
||||
}
|
||||
|
||||
if (!is_email($contact['email'])) {
|
||||
$this->addLog(
|
||||
$feed['settings']['name'],
|
||||
'failed',
|
||||
__('FluentCRM API called skipped because no valid email available', 'fluent-crm'),
|
||||
$form->id,
|
||||
$entry->id
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($contact['country'])) {
|
||||
$country = FunnelHelper::getCountryShortName($contact['country']);
|
||||
if ($country) {
|
||||
$contact['country'] = $country;
|
||||
} else {
|
||||
unset($contact['country']);
|
||||
}
|
||||
}
|
||||
|
||||
$subscriber = Subscriber::where('email', $contact['email'])->first();
|
||||
|
||||
if ($subscriber && Arr::isTrue($data, 'skip_if_exists')) {
|
||||
$this->addLog(
|
||||
$feed['settings']['name'],
|
||||
'info',
|
||||
__('Contact creation has been skipped because the contact already exists in the database', 'fluent-crm'),
|
||||
$form->id,
|
||||
$entry->id
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!empty($contact['avatar'])) {
|
||||
// validate the avatar photo
|
||||
$validUrl = '';
|
||||
if (filter_var($contact['avatar'], FILTER_VALIDATE_URL) !== FALSE) {
|
||||
$url = $contact['avatar'];
|
||||
$dots = explode('.', $url);
|
||||
$ext = strtolower(end($dots));
|
||||
|
||||
if (in_array($ext, ['png', 'jpg', 'jpeg', 'webp', 'gif'])) {
|
||||
$validUrl = $contact['avatar'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$validUrl) {
|
||||
unset($contact['avatar']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($subscriber) {
|
||||
if ($subscriber->ip && isset($contact['ip'])) {
|
||||
unset($contact['ip']);
|
||||
}
|
||||
|
||||
if (Arr::isTrue($data, 'skip_primary_data')) {
|
||||
if ($subscriber->first_name) {
|
||||
unset($contact['first_name']);
|
||||
unset($contact['last_name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$user = get_user_by('email', $contact['email']);
|
||||
if ($user) {
|
||||
$contact['user_id'] = $user->ID;
|
||||
}
|
||||
|
||||
$tags = $this->getSelectedTagIds($data, $formData, 'tag_ids');
|
||||
if ($tags) {
|
||||
$contact['tags'] = $tags;
|
||||
}
|
||||
|
||||
if (!$subscriber) {
|
||||
if (empty($contact['source'])) {
|
||||
$contact['source'] = 'FluentForms';
|
||||
}
|
||||
|
||||
if (Arr::isTrue($data, 'double_opt_in')) {
|
||||
$contact['status'] = 'pending';
|
||||
} else {
|
||||
$contact['status'] = 'subscribed';
|
||||
}
|
||||
|
||||
if ($listId = Arr::get($data, 'list_id')) {
|
||||
$contact['lists'] = [$listId];
|
||||
}
|
||||
|
||||
$subscriber = FluentCrmApi('contacts')->createOrUpdate($contact, false, false);
|
||||
|
||||
if (!$subscriber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($entry->status == 'confirmed' && $subscriber->status != 'subscribed') {
|
||||
$subscriber = $subscriber->updateStatus('subscribed');
|
||||
}
|
||||
|
||||
if ($subscriber->status == 'pending') {
|
||||
$subscriber->sendDoubleOptinEmail();
|
||||
}
|
||||
|
||||
$this->addLog(
|
||||
$feed['settings']['name'],
|
||||
'success',
|
||||
__('Contact has been created in FluentCRM. Contact ID: ', 'fluent-crm') . $subscriber->id,
|
||||
$form->id,
|
||||
$entry->id
|
||||
);
|
||||
|
||||
do_action('fluent_crm/contact_added_by_fluentform', $subscriber, $entry, $form, $feed);
|
||||
|
||||
} else {
|
||||
|
||||
if ($listId = Arr::get($data, 'list_id')) {
|
||||
$contact['lists'] = [$listId];
|
||||
}
|
||||
|
||||
$hasDouBleOptIn = Arr::isTrue($data, 'double_opt_in');
|
||||
|
||||
$forceSubscribed = !$hasDouBleOptIn && ($subscriber->status != 'subscribed');
|
||||
|
||||
if (!$forceSubscribed) {
|
||||
$forceSubscribed = Arr::isTrue($data, 'force_subscribe');
|
||||
}
|
||||
|
||||
if ($forceSubscribed) {
|
||||
$contact['status'] = 'subscribed';
|
||||
}
|
||||
|
||||
$originalFields = FluentCrmApi('contacts')->getContact($contact['email'])->getOriginal();
|
||||
|
||||
$currentCustomValues = $subscriber->custom_fields();
|
||||
|
||||
$subscriber = FluentCrmApi('contacts')->createOrUpdate($contact, $forceSubscribed, false);
|
||||
|
||||
if (!$subscriber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($entry->status == 'confirmed' && $subscriber->status != 'subscribed') {
|
||||
$subscriber = $subscriber->updateStatus('subscribed');
|
||||
}
|
||||
|
||||
if ($hasDouBleOptIn && ($subscriber->status == 'pending' || $subscriber->status == 'unsubscribed')) {
|
||||
$subscriber->sendDoubleOptinEmail();
|
||||
}
|
||||
|
||||
do_action('fluent_crm/contact_updated_by_fluentform', $subscriber, $entry, $form, $feed);
|
||||
|
||||
if ($removeTags = Arr::get($feed, 'settings.remove_tags', [])) {
|
||||
$subscriber->detachTags($removeTags);
|
||||
}
|
||||
|
||||
$dirtyFields = array_merge($feed['processedValues'], ['dirty_custom_fields' => $currentCustomValues]);
|
||||
do_action('fluent_crm/contact_updated_with_changes', $subscriber, $dirtyFields, $originalFields, ['source' => 'fluentform', 'formId' => $form->id]);
|
||||
|
||||
$this->addLog(
|
||||
$feed['settings']['name'],
|
||||
'success',
|
||||
__('Contact has been updated in FluentCRM. Contact ID: ', 'fluent-crm') . $subscriber->id,
|
||||
$form->id,
|
||||
$entry->id
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function isConfigured()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isEnabled()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function addLog($title, $status, $description, $formId, $entryId)
|
||||
{
|
||||
do_action('ff_log_data', [
|
||||
'title' => $title,
|
||||
'status' => $status,
|
||||
'description' => $description,
|
||||
'parent_source_id' => $formId,
|
||||
'source_id' => $entryId,
|
||||
'component' => $this->integrationKey,
|
||||
'source_type' => 'submission_item'
|
||||
]);
|
||||
}
|
||||
|
||||
/*
|
||||
* We will remove this in future
|
||||
*/
|
||||
protected function getSelectedTagIds($data, $inputData, $simpleKey = 'tag_ids', $routingId = 'tag_ids_selection_type', $routersKey = 'tag_routers')
|
||||
{
|
||||
$routing = ArrayHelper::get($data, $routingId, 'simple');
|
||||
if (!$routing || $routing == 'simple') {
|
||||
return ArrayHelper::get($data, $simpleKey, []);
|
||||
}
|
||||
|
||||
$routers = ArrayHelper::get($data, $routersKey);
|
||||
if (empty($routers)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->evaluateRoutings($routers, $inputData);
|
||||
}
|
||||
|
||||
/*
|
||||
* We will remove this in future
|
||||
*/
|
||||
protected function evaluateRoutings($routings, $inputData)
|
||||
{
|
||||
$validInputs = [];
|
||||
foreach ($routings as $routing) {
|
||||
$inputValue = ArrayHelper::get($routing, 'input_value');
|
||||
if (!$inputValue) {
|
||||
continue;
|
||||
}
|
||||
$condition = [
|
||||
'conditionals' => [
|
||||
'status' => true,
|
||||
'is_test' => true,
|
||||
'type' => 'any',
|
||||
'conditions' => [
|
||||
$routing
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
if (\FluentForm\App\Services\ConditionAssesor::evaluate($condition, $inputData)) {
|
||||
$validInputs[] = $inputValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $validInputs;
|
||||
}
|
||||
|
||||
private function registerPaymentEvents()
|
||||
{
|
||||
add_action('fluentform/subscription_payment_active', function ($subscription, $submission) {
|
||||
$this->handlePaymentEvent($submission, 'fluentform/subscription_payment_active');
|
||||
}, 10, 2);
|
||||
add_action('fluentform/subscription_payment_canceled', function ($subscription, $submission) {
|
||||
$this->handlePaymentEvent($submission, 'fluentform/subscription_payment_canceled');
|
||||
}, 10, 2);
|
||||
|
||||
add_action('fluentform/payment_refunded', function ($refund, $transaction, $submission) {
|
||||
$this->handlePaymentEvent($submission, 'fluentform/payment_refunded');
|
||||
}, 10, 3);
|
||||
}
|
||||
|
||||
private function handlePaymentEvent($submission, $event)
|
||||
{
|
||||
// Get Fluent Forms Feeds
|
||||
$feeds = fluentCrmDb()->table('fluentform_form_meta')
|
||||
->where('form_id', $submission->form_id)
|
||||
->where('meta_key', 'fluentcrm_feeds')
|
||||
->orderBy('id', 'ASC')
|
||||
->get();
|
||||
|
||||
if ($feeds->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!is_array($submission->response)) {
|
||||
$formData = json_decode($submission->response, true);
|
||||
} else {
|
||||
$formData = $submission->response;
|
||||
}
|
||||
|
||||
$form = fluentFormApi('forms')->find($submission->form_id);
|
||||
|
||||
$notificationService = new GlobalNotificationService();
|
||||
|
||||
foreach ($feeds as $feed) {
|
||||
$parsedValue = json_decode($feed->value, true);
|
||||
if ($parsedValue && ArrayHelper::isTrue($parsedValue, 'enabled')) {
|
||||
|
||||
$runEvents = ArrayHelper::get($parsedValue, 'run_events_only', []);
|
||||
|
||||
// check if this is our event or not
|
||||
if (!$runEvents || !in_array($event, $runEvents)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now check if conditions matched or not
|
||||
$isConditionMatched = $notificationService->checkCondition($parsedValue, $formData, $submission->id);
|
||||
if ($isConditionMatched) {
|
||||
$item = [
|
||||
'id' => $feed->id,
|
||||
'meta_key' => $feed->meta_key,
|
||||
'settings' => $parsedValue
|
||||
];
|
||||
|
||||
$processedValues = $item['settings'];
|
||||
unset($processedValues['conditionals']);
|
||||
|
||||
$item['processedValues'] = ShortCodeParser::parse($processedValues, $submission->id, $formData, $form, false, $feed->meta_key);
|
||||
|
||||
$this->runFeed($item, $formData, $submission, $form);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrm\App\Services\ExternalIntegrations\FluentForm;
|
||||
|
||||
use FluentCrm\Framework\Support\Arr;
|
||||
use FluentForm\App\Helpers\Helper;
|
||||
use FluentFormPro\Payments\PaymentHelper;
|
||||
|
||||
class FluentFormInit
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
if (defined('FLUENTFORM_FRAMEWORK_UPGRADE')) {
|
||||
new \FluentCrm\App\Services\ExternalIntegrations\FluentForm\Bootstrap();
|
||||
}
|
||||
|
||||
add_filter('fluentform/submissions_widgets', array($this, 'pushContactWidget'), 10, 3);
|
||||
if (defined('FLUENTFORMPRO')) {
|
||||
add_filter('fluent_crm/subscriber_info_widgets', array($this, 'pushSubscriberInfoWidget'), 10, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public function pushContactWidget($widgets, $resources, $submission)
|
||||
{
|
||||
$userId = $submission->user_id;
|
||||
|
||||
if (!$userId) {
|
||||
$userInputs = json_decode($submission->response, true);
|
||||
|
||||
if (!$userInputs) {
|
||||
return $widgets;
|
||||
}
|
||||
|
||||
$maybeEmail = Arr::get($userInputs, 'email');
|
||||
|
||||
if (!$maybeEmail) {
|
||||
$emailField = Helper::getFormMeta($submission->form_id, '_primary_email_field');
|
||||
if (!$emailField) {
|
||||
return $widgets;
|
||||
}
|
||||
$maybeEmail = Arr::get($userInputs, $emailField);
|
||||
}
|
||||
} else {
|
||||
$maybeEmail = $userId;
|
||||
}
|
||||
|
||||
if (!$maybeEmail) {
|
||||
return $widgets;
|
||||
}
|
||||
|
||||
$profileHtml = fluentcrm_get_crm_profile_html($maybeEmail, true);
|
||||
if (!$profileHtml) {
|
||||
return $widgets;
|
||||
}
|
||||
|
||||
$widgets['fluent_crm'] = [
|
||||
'title' => __('FluentCRM Profile', 'fluent-crm'),
|
||||
'content' => $profileHtml
|
||||
];
|
||||
return $widgets;
|
||||
}
|
||||
|
||||
public function pushSubscriberInfoWidget($widgets, $subscriber)
|
||||
{
|
||||
if(!$subscriber->email) {
|
||||
return $widgets;
|
||||
}
|
||||
|
||||
$subscriptions = fluentCrmDb()->table('fluentform_subscriptions')
|
||||
->join('fluentform_transactions', 'fluentform_subscriptions.submission_id', '=', 'fluentform_transactions.submission_id')
|
||||
->where('fluentform_transactions.payer_email', $subscriber->email)
|
||||
->select('fluentform_subscriptions.*', 'fluentform_transactions.currency')
|
||||
->orderBy('fluentform_subscriptions.created_at', 'desc')
|
||||
->get();
|
||||
|
||||
if ($subscriptions->isEmpty()) {
|
||||
return $widgets;
|
||||
}
|
||||
|
||||
$html = '<ul class="fc_full_listed fc_memberpress_subscription_lists">';
|
||||
foreach ($subscriptions as $subscription) {
|
||||
$html .= $this->generateSubscriptionHtml($subscription);
|
||||
}
|
||||
|
||||
$html .= '</ul>';
|
||||
|
||||
$widgets[] = [
|
||||
'title' => __('FluentForm Subscriptions', 'fluent-crm'),
|
||||
'content' => $html
|
||||
];
|
||||
|
||||
return $widgets;
|
||||
}
|
||||
private function generateSubscriptionHtml($subscription)
|
||||
{
|
||||
$subscription->formatted_recurring_amount = PaymentHelper::formatMoney($subscription->recurring_amount, $subscription->currency);
|
||||
|
||||
if ($subscription->status == 'active') {
|
||||
if ($subscription->bill_times) {
|
||||
/* translators: 1: total number of payments, 2: current payment count, 3: total number of payments */
|
||||
$billingText = sprintf(esc_html__('Will be cancelled after %1$d payments. (%2$d/%3$d)', 'fluent-crm'), $subscription->bill_times, $subscription->bill_count, $subscription->bill_times);
|
||||
} else {
|
||||
$billingText = __('Will be billed until cancelled', 'fluent-crm');
|
||||
}
|
||||
}
|
||||
|
||||
$formatted_date = date_i18n(get_option('date_format'), strtotime($subscription->created_at));
|
||||
$permalink = esc_url(admin_url('admin.php?page=fluent_forms&form_id='.$subscription->form_id.'&route=entries#/entries/'.$subscription->submission_id.'?sort_by=DESC¤t_page=1&pos=0&type='));
|
||||
|
||||
$html = '<li>';
|
||||
$html .= '<span class="fc_mepr_subscription_header">';
|
||||
$html .= '<span class="fc_mepr_subscription_status ' . esc_attr($subscription->status) . '">' . esc_html($subscription->status) . '</span>';
|
||||
$html .= '<span class="fc_mepr_subscription_price">' . $subscription->formatted_recurring_amount . '<small>/'.$subscription->billing_interval.'</small></span>';
|
||||
$html .= '</span>';
|
||||
$html .= '<a href="' . $permalink . '" target="_blank" class="fc_mepr_subscription_title">';
|
||||
$html .= '<b>' . esc_html($subscription->item_name) . '<span class="fc_dash_external dashicons dashicons-external"></span></b>';
|
||||
$html .= '</a>';
|
||||
$html .= '<span class="fc_date">' . __('Start Date: ', 'fluent-crm') . $formatted_date . '</span>';
|
||||
if ($subscription->status == 'active') {
|
||||
$html .= sprintf('<span class="fc_date period_date">%s%s</span>', __('Expiry Date: ', 'fluent-crm'), $billingText);
|
||||
} else {
|
||||
$html .= sprintf('<span class="fc_date period_date">%s%s</span>', __('Cancelled Date: ', 'fluent-crm'), date_i18n(get_option('date_format'), strtotime($subscription->updated_at)));
|
||||
}
|
||||
$html .= '</li>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
}
|
||||
+648
@@ -0,0 +1,648 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrm\App\Services\ExternalIntegrations\MailComplaince;
|
||||
|
||||
|
||||
use FluentCrm\App\Hooks\Handlers\ExternalPages;
|
||||
use FluentCrm\App\Services\Helper;
|
||||
use FluentCrm\Framework\Support\Arr;
|
||||
|
||||
class Webhook
|
||||
{
|
||||
/**
|
||||
* @param $serviceName
|
||||
* @param $request \FluentCrm\Framework\Http\Request\Request
|
||||
*/
|
||||
public function handle($serviceName, $request)
|
||||
{
|
||||
$method = 'handle' . ucfirst(strtolower($serviceName));
|
||||
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}($request);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $request \FluentCrm\Framework\Http\Request\Request
|
||||
*/
|
||||
private function handleMailgun($request)
|
||||
{
|
||||
$payload = $this->resolvePayload($request, []);
|
||||
|
||||
$eventData = Arr::get($payload, 'event-data', []);
|
||||
|
||||
if (!$eventData) {
|
||||
// Fallback: try reading from request params directly
|
||||
$eventData = $request->get('event-data', []);
|
||||
}
|
||||
|
||||
if (!$eventData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$event = Arr::get($eventData, 'event');
|
||||
|
||||
$catchEvents = ['failed', 'unsubscribed', 'complained'];
|
||||
|
||||
if (!in_array($event, $catchEvents)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$recipientEmail = Arr::get($eventData, 'recipient');
|
||||
if (!$recipientEmail) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$externalPages = new ExternalPages();
|
||||
|
||||
// For failed events, check severity to distinguish soft vs hard bounce
|
||||
if ($event == 'failed') {
|
||||
$severity = Arr::get($eventData, 'severity', 'permanent');
|
||||
if ($severity == 'temporary') {
|
||||
$description = Arr::get($eventData, 'delivery-status.message', '');
|
||||
if (!$description) {
|
||||
$description = Arr::get($eventData, 'delivery-status.description', '');
|
||||
}
|
||||
return $externalPages->recordSoftBounce([
|
||||
'email' => $recipientEmail,
|
||||
'reason' => __('Soft bounce was set by Mailgun webhook API.', 'fluent-crm') . ' ' . $description . __(' Recorded at: ', 'fluent-crm') . current_time('mysql')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$newStatus = 'bounced';
|
||||
if ($event == 'complained') {
|
||||
$newStatus = 'complained';
|
||||
} else if ($event == 'unsubscribed') {
|
||||
$newStatus = 'unsubscribed';
|
||||
}
|
||||
|
||||
$unsubscribeData = [
|
||||
'email' => $recipientEmail,
|
||||
'reason' => $newStatus . __(' was set by Mailgun webhook API with event name: ', 'fluent-crm') . $event . __(' at ', 'fluent-crm') . current_time('mysql'),
|
||||
'status' => $newStatus
|
||||
];
|
||||
|
||||
return $externalPages->recordUnsubscribe($unsubscribeData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $request \FluentCrm\Framework\Http\Request\Request
|
||||
* @return boolean
|
||||
*/
|
||||
private function handleSendgrid($request)
|
||||
{
|
||||
$events = $this->resolvePayload($request, []);
|
||||
|
||||
if (!$events || !count($events)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$externalPages = new ExternalPages();
|
||||
$processed = false;
|
||||
|
||||
foreach ($events as $event) {
|
||||
if (!is_array($event)) {
|
||||
continue;
|
||||
}
|
||||
$eventName = Arr::get($event, 'event');
|
||||
$email = Arr::get($event, 'email');
|
||||
|
||||
if (!$email || !in_array($eventName, ['dropped', 'bounce', 'spamreport', 'unsubscribe'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($eventName == 'unsubscribe') {
|
||||
$externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => __('unsubscribed status was set from SendGrid Webhook API.', 'fluent-crm') . __(' Recorded at: ', 'fluent-crm') . current_time('mysql'),
|
||||
'status' => 'unsubscribed'
|
||||
]);
|
||||
$processed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($eventName == 'spamreport') {
|
||||
$externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => __('complained status was set from SendGrid Webhook API.', 'fluent-crm') . __(' Recorded at: ', 'fluent-crm') . current_time('mysql'),
|
||||
'status' => 'complained'
|
||||
]);
|
||||
$processed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// bounce or dropped — check type field for soft vs hard bounce
|
||||
$bounceType = Arr::get($event, 'type', 'bounce');
|
||||
$reason = Arr::get($event, 'reason', '');
|
||||
if ($bounceType == 'blocked') {
|
||||
$externalPages->recordSoftBounce([
|
||||
'email' => $email,
|
||||
'reason' => __('Soft bounce from SendGrid Webhook API. Reason: ', 'fluent-crm') . $reason . __(' Recorded at: ', 'fluent-crm') . current_time('mysql')
|
||||
]);
|
||||
} else {
|
||||
$externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => __('bounced status was set from SendGrid Webhook API. Reason: ', 'fluent-crm') . $reason . __(' Recorded at: ', 'fluent-crm') . current_time('mysql'),
|
||||
'status' => 'bounced'
|
||||
]);
|
||||
}
|
||||
$processed = true;
|
||||
}
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $request \FluentCrm\Framework\Http\Request\Request
|
||||
* @return boolean
|
||||
*/
|
||||
private function handlePepipost($request)
|
||||
{
|
||||
$events = $this->resolvePayload($request, []);
|
||||
|
||||
if (!$events || !count($events)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$externalPages = new ExternalPages();
|
||||
$processed = false;
|
||||
|
||||
foreach ($events as $event) {
|
||||
if (!is_array($event)) {
|
||||
continue;
|
||||
}
|
||||
$eventName = Arr::get($event, 'EVENT');
|
||||
|
||||
if (!in_array($eventName, ['bounced', 'invalid', 'spam', 'unsubscribed'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newStatus = 'bounced';
|
||||
if ($eventName == 'unsubscribed') {
|
||||
$newStatus = 'unsubscribed';
|
||||
} else if ($eventName == 'spam') {
|
||||
$newStatus = 'complained';
|
||||
}
|
||||
|
||||
$reason = $newStatus . __(' status was set from Pepipost Webhook API. Reason: ', 'fluent-crm') . Arr::get($event, 'BOUNCE_TYPE') . __(' Recorded at: ', 'fluent-crm') . current_time('mysql');
|
||||
|
||||
if ($sourceResponse = Arr::get($event, 'RESPONSE')) {
|
||||
$reason = $sourceResponse;
|
||||
}
|
||||
|
||||
$email = Arr::get($event, 'EMAIL');
|
||||
if ($email) {
|
||||
$externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => $reason,
|
||||
'status' => $newStatus
|
||||
]);
|
||||
$processed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $request \FluentCrm\Framework\Http\Request\Request
|
||||
* @return boolean
|
||||
*/
|
||||
private function handleSparkpost($request)
|
||||
{
|
||||
$events = $this->resolvePayload($request, []);
|
||||
|
||||
if (!$events || !count($events)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$externalPages = new ExternalPages();
|
||||
$processed = false;
|
||||
|
||||
// SparkPost hard bounce classes: 10 (Invalid Recipient), 25 (Admin Failure),
|
||||
// 30 (Generic Bounce: No RCPT), 90 (Unsubscribe)
|
||||
$hardBounceClasses = [10, 25, 30, 90];
|
||||
|
||||
foreach ($events as $eventWrapper) {
|
||||
if (!is_array($eventWrapper)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// SparkPost wraps events in msys.message_event or msys.unsubscribe_event
|
||||
$event = Arr::get($eventWrapper, 'msys.message_event');
|
||||
if (!$event || !is_array($event)) {
|
||||
$event = Arr::get($eventWrapper, 'msys.unsubscribe_event');
|
||||
}
|
||||
if (!$event || !is_array($event)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$eventName = Arr::get($event, 'type');
|
||||
if (!in_array($eventName, ['bounce', 'out_of_band', 'spam_complaint', 'link_unsubscribe', 'list_unsubscribe'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$email = Arr::get($event, 'rcpt_to');
|
||||
if (!$email) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$reason = Arr::get($event, 'raw_reason', '');
|
||||
if (!$reason) {
|
||||
$reason = $eventName . __(' status was set from SparkPost Webhook API.', 'fluent-crm') . __(' Recorded at: ', 'fluent-crm') . current_time('mysql');
|
||||
}
|
||||
|
||||
// Both bounce and out_of_band events carry bounce_class
|
||||
if (in_array($eventName, ['bounce', 'out_of_band'])) {
|
||||
$bounceClass = (int)Arr::get($event, 'bounce_class', 0);
|
||||
if (!in_array($bounceClass, $hardBounceClasses)) {
|
||||
$externalPages->recordSoftBounce([
|
||||
'email' => $email,
|
||||
'reason' => $reason
|
||||
]);
|
||||
$processed = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$newStatus = 'bounced';
|
||||
if (in_array($eventName, ['link_unsubscribe', 'list_unsubscribe'])) {
|
||||
$newStatus = 'unsubscribed';
|
||||
} else if ($eventName == 'spam_complaint') {
|
||||
$newStatus = 'complained';
|
||||
}
|
||||
|
||||
$externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => $reason,
|
||||
'status' => $newStatus
|
||||
]);
|
||||
$processed = true;
|
||||
}
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $request \FluentCrm\Framework\Http\Request\Request
|
||||
* @return boolean
|
||||
*/
|
||||
private function handlePostmark($request)
|
||||
{
|
||||
$event = $this->resolvePayload($request, []);
|
||||
|
||||
if (!$event || !is_array($event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$eventName = Arr::get($event, 'RecordType');
|
||||
if (!in_array($eventName, ['Bounce', 'SpamComplaint'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$email = Arr::get($event, 'Email');
|
||||
if (!$email) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$reason = Arr::get($event, 'Description', '');
|
||||
if (!$reason) {
|
||||
$reason = $eventName . __(' status was set from Postmark Webhook API.', 'fluent-crm') . __(' Recorded at: ', 'fluent-crm') . current_time('mysql');
|
||||
}
|
||||
|
||||
$externalPages = new ExternalPages();
|
||||
|
||||
if ($eventName == 'SpamComplaint') {
|
||||
return $externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => $reason,
|
||||
'status' => 'complained'
|
||||
]);
|
||||
}
|
||||
|
||||
// For Bounce events, check Type to distinguish soft vs hard
|
||||
$bounceType = Arr::get($event, 'Type', '');
|
||||
if ($bounceType == 'SoftBounce' || $bounceType == 'Transient') {
|
||||
return $externalPages->recordSoftBounce([
|
||||
'email' => $email,
|
||||
'reason' => $reason
|
||||
]);
|
||||
}
|
||||
|
||||
return $externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => $reason,
|
||||
'status' => 'bounced'
|
||||
]);
|
||||
}
|
||||
|
||||
private function handleElasticemail($request)
|
||||
{
|
||||
$status = strtolower($request->get('status'));
|
||||
|
||||
$processStatuses = [
|
||||
'error',
|
||||
'abusereport',
|
||||
'unsubscribed'
|
||||
];
|
||||
|
||||
if (!in_array($status, $processStatuses)) {
|
||||
return [
|
||||
'message' => 'unknown_status'
|
||||
];
|
||||
}
|
||||
|
||||
$email = $request->get('to');
|
||||
if (!$email) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$externalPages = new ExternalPages();
|
||||
|
||||
$softBounceCategories = [
|
||||
'AccountProblem',
|
||||
'Throttled',
|
||||
'SPFProblem',
|
||||
'Timeout',
|
||||
'ConnectionProblem',
|
||||
'GreyListed',
|
||||
'WhitelistingProblem',
|
||||
'CodeError'
|
||||
];
|
||||
|
||||
$category = $request->get('category', 'unknown');
|
||||
|
||||
if ($status == 'error') {
|
||||
if (in_array($category, $softBounceCategories)) {
|
||||
return $externalPages->recordSoftBounce([
|
||||
'email' => $email,
|
||||
'reason' => __('Soft bounce from ElasticEmail Webhook API. Category: ', 'fluent-crm') . $category . __(' Recorded at: ', 'fluent-crm') . current_time('mysql')
|
||||
]);
|
||||
}
|
||||
|
||||
return $externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => __('bounced status was set from ElasticEmail Webhook API. Category: ', 'fluent-crm') . $category . __(' Recorded at: ', 'fluent-crm') . current_time('mysql'),
|
||||
'status' => 'bounced'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($status == 'abusereport') {
|
||||
return $externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => __('complained status was set from ElasticEmail Webhook API.', 'fluent-crm') . __(' Recorded at: ', 'fluent-crm') . current_time('mysql'),
|
||||
'status' => 'complained'
|
||||
]);
|
||||
}
|
||||
|
||||
// unsubscribed
|
||||
return $externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => __('unsubscribed status was set from ElasticEmail Webhook API.', 'fluent-crm') . __(' Recorded at: ', 'fluent-crm') . current_time('mysql'),
|
||||
'status' => 'unsubscribed'
|
||||
]);
|
||||
}
|
||||
|
||||
private function handlePostalserver($request)
|
||||
{
|
||||
$event = strtolower($request->get('event'));
|
||||
|
||||
$processStatuses = [
|
||||
'messagebounced',
|
||||
'messagedeliveryfailed',
|
||||
'messagedelayed'
|
||||
];
|
||||
|
||||
if (!in_array($event, $processStatuses)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$payload = $request->get('payload');
|
||||
|
||||
if (!$payload || !is_array($payload)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$externalPages = new ExternalPages();
|
||||
|
||||
if ($event == 'messagedeliveryfailed') {
|
||||
$payloadStatus = Arr::get($payload, 'status');
|
||||
$toEmail = Arr::get($payload, 'message.to');
|
||||
$reason = Arr::get($payload, 'details', 'Unknown Reason');
|
||||
|
||||
if (!$toEmail || !is_email($toEmail)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// SoftFail → record as soft bounce
|
||||
if ($payloadStatus != 'HardFail') {
|
||||
return $externalPages->recordSoftBounce([
|
||||
'email' => $toEmail,
|
||||
'reason' => __('Soft bounce from PostalServer. Reason: ', 'fluent-crm') . $reason . __(' Recorded at: ', 'fluent-crm') . current_time('mysql')
|
||||
]);
|
||||
}
|
||||
|
||||
return $externalPages->recordUnsubscribe([
|
||||
'email' => $toEmail,
|
||||
'reason' => $reason,
|
||||
'status' => 'bounced'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($event == 'messagedelayed') {
|
||||
$toEmail = Arr::get($payload, 'message.to');
|
||||
$reason = Arr::get($payload, 'details', 'Unknown Reason');
|
||||
|
||||
if (!$toEmail || !is_email($toEmail)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $externalPages->recordSoftBounce([
|
||||
'email' => $toEmail,
|
||||
'reason' => __('Soft bounce (delayed) from PostalServer. Reason: ', 'fluent-crm') . $reason . __(' Recorded at: ', 'fluent-crm') . current_time('mysql')
|
||||
]);
|
||||
}
|
||||
|
||||
// messagebounced — use original_message.to (not bounce.to which is the return-path)
|
||||
$toEmail = Arr::get($payload, 'original_message.to');
|
||||
if (!$toEmail || !is_email($toEmail)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$reason = __('Bounce notification received from PostalServer.', 'fluent-crm') . __(' Recorded at: ', 'fluent-crm') . current_time('mysql');
|
||||
|
||||
return $externalPages->recordUnsubscribe([
|
||||
'email' => $toEmail,
|
||||
'reason' => $reason,
|
||||
'status' => 'bounced'
|
||||
]);
|
||||
}
|
||||
|
||||
private function handleSmtp2go($request)
|
||||
{
|
||||
$event = strtolower($request->get('event'));
|
||||
|
||||
$processStatuses = [
|
||||
'bounce',
|
||||
'spam',
|
||||
'unsubscribe'
|
||||
];
|
||||
|
||||
if (!in_array($event, $processStatuses)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$toEmail = $request->get('rcpt');
|
||||
if (!$toEmail || !is_email($toEmail)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$reason = sanitize_textarea_field($request->get('message', 'Unknown Reason'));
|
||||
$externalPages = new ExternalPages();
|
||||
|
||||
if ($event == 'bounce') {
|
||||
$bounceType = $request->get('bounce');
|
||||
if ($bounceType == 'soft') {
|
||||
return $externalPages->recordSoftBounce([
|
||||
'email' => $toEmail,
|
||||
'reason' => __('Soft bounce from SMTP2GO Webhook API. Reason: ', 'fluent-crm') . $reason . __(' Recorded at: ', 'fluent-crm') . current_time('mysql')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$newStatus = 'bounced';
|
||||
if ($event == 'unsubscribe') {
|
||||
$newStatus = 'unsubscribed';
|
||||
} else if ($event == 'spam') {
|
||||
$newStatus = 'complained';
|
||||
}
|
||||
|
||||
return $externalPages->recordUnsubscribe([
|
||||
'email' => $toEmail,
|
||||
'reason' => $reason,
|
||||
'status' => $newStatus
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $request \FluentCrm\Framework\Http\Request\Request
|
||||
* @return boolean
|
||||
*/
|
||||
private function handleBrevo($request)
|
||||
{
|
||||
$event = $this->resolvePayload($request, []);
|
||||
|
||||
if (!$event || !count($event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$eventName = Arr::get($event, 'event');
|
||||
if (!in_array($eventName, ['soft_bounce', 'hard_bounce', 'invalid', 'invalid_email', 'spam', 'error', 'blocked', 'deferred', 'unsubscribe', 'unsubscribed'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$email = Arr::get($event, 'email');
|
||||
if (!$email) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$externalPages = new ExternalPages();
|
||||
$reason = Arr::get($event, 'reason', '');
|
||||
|
||||
// Soft bounces and deferred deliveries should be tracked separately
|
||||
if (in_array($eventName, ['soft_bounce', 'deferred'])) {
|
||||
$reasonPrefix = $eventName === 'deferred'
|
||||
? __('Deferred delivery from Brevo Webhook API. Reason: ', 'fluent-crm')
|
||||
: __('Soft bounce from Brevo Webhook API. Reason: ', 'fluent-crm');
|
||||
return $externalPages->recordSoftBounce([
|
||||
'email' => $email,
|
||||
'reason' => $reasonPrefix . $reason . __(' Recorded at: ', 'fluent-crm') . current_time('mysql')
|
||||
]);
|
||||
}
|
||||
|
||||
$newStatus = 'bounced';
|
||||
if (in_array($eventName, ['unsubscribe', 'unsubscribed'])) {
|
||||
$newStatus = 'unsubscribed';
|
||||
} else if ($eventName == 'spam') {
|
||||
$newStatus = 'complained';
|
||||
}
|
||||
// hard_bounce, invalid, invalid_email, error, blocked → bounced
|
||||
|
||||
return $externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => $newStatus . __(' status was set from Brevo Webhook API. Reason: ', 'fluent-crm') . $reason . __(' Recorded at: ', 'fluent-crm') . current_time('mysql'),
|
||||
'status' => $newStatus
|
||||
]);
|
||||
}
|
||||
|
||||
private function handleTosend($request)
|
||||
{
|
||||
$event = $this->resolvePayload($request, []);
|
||||
|
||||
if (!$event || !is_array($event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$eventType = Arr::get($event, 'type');
|
||||
if (!in_array($eventType, ['bounce', 'complaint'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$email = Arr::get($event, 'email');
|
||||
if (!$email) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$externalPages = new ExternalPages();
|
||||
|
||||
if ($eventType == 'complaint') {
|
||||
return $externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => __('Complaint received from ToSend Webhook API.', 'fluent-crm') . __(' Recorded at: ', 'fluent-crm') . current_time('mysql'),
|
||||
'status' => 'complained'
|
||||
]);
|
||||
}
|
||||
|
||||
// Bounce event — check is_hard_bounce flag
|
||||
$reason = Arr::get($event, 'reason', 'Unknown');
|
||||
$isHardBounce = Arr::get($event, 'is_hard_bounce', false);
|
||||
|
||||
// Sender-fault bounces (our content/message, not the recipient's mailbox) —
|
||||
// do NOT penalise the subscriber. Prefer the explicit `sender_fault` flag;
|
||||
// fall back to `bounce_sub_type` for older payloads.
|
||||
$senderFaultSubTypes = ['MessageTooLarge', 'ContentRejected', 'AttachmentRejected'];
|
||||
$isSenderFault = (bool) Arr::get($event, 'sender_fault', false)
|
||||
|| (!$isHardBounce && in_array(Arr::get($event, 'bounce_sub_type'), $senderFaultSubTypes, true));
|
||||
|
||||
if ($isSenderFault) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$isHardBounce) {
|
||||
return $externalPages->recordSoftBounce([
|
||||
'email' => $email,
|
||||
'reason' => __('Soft bounce from ToSend Webhook API. Reason: ', 'fluent-crm') . $reason . __(' Recorded at: ', 'fluent-crm') . current_time('mysql')
|
||||
]);
|
||||
}
|
||||
|
||||
return $externalPages->recordUnsubscribe([
|
||||
'email' => $email,
|
||||
'reason' => __('Hard bounce from ToSend Webhook API. Reason: ', 'fluent-crm') . $reason . __(' Recorded at: ', 'fluent-crm') . current_time('mysql'),
|
||||
'status' => 'bounced'
|
||||
]);
|
||||
}
|
||||
|
||||
private function resolvePayload($request, $default = [])
|
||||
{
|
||||
$contentPayload = Helper::parseArrayOrJson($request->getContent(), []);
|
||||
|
||||
if ($contentPayload) {
|
||||
return $contentPayload;
|
||||
}
|
||||
|
||||
return Helper::parseArrayOrJson($request->get(), $default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrm\App\Services\ExternalIntegrations;
|
||||
|
||||
class Maintenance
|
||||
{
|
||||
public function maybeProcessData()
|
||||
{
|
||||
if (!$this->isAllowed() || !$this->timeMatched()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = wp_remote_post($this->getApiUrl(), [
|
||||
'body' => [
|
||||
'payload' => $this->getData()
|
||||
],
|
||||
'sslverify' => false,
|
||||
'cookies' => []
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
update_option('_fluent_last_m_run', time());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getData()
|
||||
{
|
||||
global $wp_version;
|
||||
return [
|
||||
'plugin_version' => FLUENTCRM_PLUGIN_VERSION,
|
||||
'php_version' => (defined('PHP_VERSION')) ? PHP_VERSION : phpversion(),
|
||||
'wp_version' => $wp_version,
|
||||
'plugins' => (array)get_option('active_plugins'),
|
||||
'site_lang' => get_bloginfo('language'),
|
||||
'site_url' => site_url('/'),
|
||||
'theme' => wp_get_theme()->get('Name'),
|
||||
'admin_email' => get_bloginfo('admin_email'),
|
||||
'site_title' => get_bloginfo('name')
|
||||
];
|
||||
}
|
||||
|
||||
private function isAllowed()
|
||||
{
|
||||
/**
|
||||
* Filter to allow sharing essential data in FluentCRM.
|
||||
*
|
||||
* This filter allows you to control whether essential data sharing is enabled in FluentCRM.
|
||||
*
|
||||
* @since 2.7.0
|
||||
*
|
||||
* @param bool Whether to allow sharing essential data. Default is false.
|
||||
*/
|
||||
return apply_filters('fluent_crm/allow_share_essential', fluentcrm_get_option('_fluentcrm_share_essential', 'no') == 'yes');
|
||||
}
|
||||
|
||||
private function timeMatched()
|
||||
{
|
||||
$prevValue = get_option('_fluent_last_m_run');
|
||||
if (!$prevValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (time() - $prevValue) > 518400; // 6 days match
|
||||
}
|
||||
|
||||
private function getApiUrl()
|
||||
{
|
||||
return 'https://apiv2.wpmanageninja.com/plugin-maintenance';
|
||||
}
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrm\App\Services\ExternalIntegrations\Oxygen;
|
||||
|
||||
use FluentCrm\App\Models\Tag;
|
||||
|
||||
class ConditionBuilder
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
$tags = Tag::get();
|
||||
$formattedTags = [];
|
||||
foreach ($tags as $tag) {
|
||||
$formattedTags[] = $tag->slug;
|
||||
}
|
||||
|
||||
oxygen_vsb_register_condition(
|
||||
'FluentCRM - Contact Tag',
|
||||
array(
|
||||
'options' => $formattedTags,
|
||||
'custom' => false
|
||||
),
|
||||
array('exist', 'not exist'),
|
||||
'fcrmOxyCheckTagCondition',
|
||||
'FluentCRM'
|
||||
);
|
||||
|
||||
oxygen_vsb_register_condition(
|
||||
'FluentCRM - Contact Status',
|
||||
array(
|
||||
'options' => ['subscribed', 'pending', 'unsubscribed'],
|
||||
'custom' => false
|
||||
),
|
||||
array('=', '!='),
|
||||
'fcrmOxyCheckStatusCondition',
|
||||
'FluentCRM'
|
||||
);
|
||||
|
||||
oxygen_vsb_register_condition(
|
||||
'FluentCRM - Contact Exist',
|
||||
array(
|
||||
'options' => ['yes', 'no'],
|
||||
'custom' => false
|
||||
),
|
||||
array('='),
|
||||
'fcrmOxyCheckContactExistCondition',
|
||||
'FluentCRM'
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
(new \FluentCrm\App\Services\ExternalIntegrations\Oxygen\ConditionBuilder())->init();
|
||||
|
||||
function fcrmOxyCheckTagCondition($value, $operator)
|
||||
{
|
||||
$contactApi = FluentCrmApi('contacts');
|
||||
$contact = $contactApi->getCurrentContact(true, true);
|
||||
|
||||
if(!$contact) {
|
||||
if($operator == 'exist') {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$contactTags = $contact->tags;
|
||||
|
||||
if($operator == 'exist') {
|
||||
foreach ($contactTags as $tag) {
|
||||
if($tag->slug == $value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($contactTags as $tag) {
|
||||
if($tag->slug == $value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function fcrmOxyCheckStatusCondition($value, $operator)
|
||||
{
|
||||
$contactApi = FluentCrmApi('contacts');
|
||||
$contact = $contactApi->getCurrentContact(true, true);
|
||||
|
||||
if(!$contact) {
|
||||
if($operator == '=') {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if($operator == '=') {
|
||||
return $contact->status == $value;
|
||||
}
|
||||
|
||||
return $contact->status != $value;
|
||||
|
||||
}
|
||||
|
||||
function fcrmOxyCheckContactExistCondition($value, $operator)
|
||||
{
|
||||
$contactApi = FluentCrmApi('contacts');
|
||||
$contact = $contactApi->getCurrentContact(true, true);
|
||||
|
||||
if($value == 'yes') {
|
||||
return !!$contact;
|
||||
}
|
||||
|
||||
return !$contact;
|
||||
}
|
||||
Reference in New Issue
Block a user