403Webshell
Server IP : 52.25.153.185  /  Your IP : 216.73.217.131
Web Server : Apache
System : Linux ip-172-26-6-158 5.10.0-35-cloud-amd64 #1 SMP Debian 5.10.237-1 (2025-05-19) x86_64
User : daemon ( 1)
PHP Version : 8.1.10
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /bitnami/wordpress/wp-content/plugins/fluent-crm/app/Http/Controllers/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /bitnami/wordpress/wp-content/plugins/fluent-crm/app/Http/Controllers/FunnelController.php
<?php

namespace FluentCrm\App\Http\Controllers;

use FluentCrm\App\Hooks\Handlers\FunnelHandler;
use FluentCrm\App\Models\Funnel;
use FluentCrm\App\Models\FunnelCampaign;
use FluentCrm\App\Models\FunnelMetric;
use FluentCrm\App\Models\FunnelSequence;
use FluentCrm\App\Models\FunnelSubscriber;
use FluentCrm\App\Models\Label;
use FluentCrm\App\Models\Meta;
use FluentCrm\App\Models\Subscriber;
use FluentCrm\App\Models\TermRelation;
use FluentCrm\App\Services\Funnel\FunnelHelper;
use FluentCrm\App\Services\Funnel\FunnelProcessor;
use FluentCrm\App\Services\Funnel\ProFunnelItems;
use FluentCrm\App\Services\Helper;
use FluentCrm\App\Services\Libs\Parser\Parser;
use FluentCrm\App\Services\Reporting;
use FluentCrm\App\Services\Sanitize;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\Framework\Http\Request\Request;
use FluentCrm\Framework\Validator\ValidationException;

/**
 *  FunnelController - REST API Handler Class
 *
 *  REST API Handler
 *
 * @package FluentCrm\App\Http
 *
 * @version 1.0.0
 */
class FunnelController extends Controller
{
    public function funnels(Request $request)
    {

        $this->maybeMigrateDB();

        $orderBy = $request->getSafe('sort_by', 'sanitize_sql_orderby', 'id');
        $orderType = $request->getSafe('sort_type', 'sanitize_sql_orderby', 'DESC');

        $labelIds = $this->sanitizeFilterIds($request->get('labels')); // labels are id
        $tagIds = $this->sanitizeFilterIds($request->get('tags')); // tags are id
        $listIds = $this->sanitizeFilterIds($request->get('lists')); // lists are id
        $allowedStatuses = ['published', 'draft'];
        $statusFilter = array_intersect(
            (array) $request->get('statuses', []),
            $allowedStatuses
        );

        $funnelQuery = Funnel::withCount('subscribers')
            ->orderBy($orderBy, $orderType);

        if (!Helper::isEdd3()) {
            /*
             * EDD 2 is no longer supported, so hide existing EDD automations
             * without deleting stored funnel data from the database.
             */
            $funnelQuery->whereNotIn('trigger_name', [
                'edd_update_payment_status',
                'edd_sl_post_set_status',
                'edd_recurring_add_subscription_payment',
                'edd_subscription_status_change',
                'edd_fc_order_refunded_simulation'
            ]);
        }

        if ($search = $request->getSafe('search', 'sanitize_text_field')) {
            global $wpdb;
            $searchTerm = '%%' . $wpdb->esc_like($search) . '%%';
            $funnelQuery->where(function ($query) use ($searchTerm) {
                $query->where('title', 'LIKE', $searchTerm)
                    ->orWhere('trigger_name', 'LIKE', $searchTerm);
            });
        }

        if (!empty($labelIds)) {
            $funnelQuery->whereHas('labelsTerm', function ($query) use ($labelIds) {
                $query->whereIn('term_id', $labelIds);
            });
        }

        $segmentFilters = array_filter([
            'tags'  => $tagIds,
            'lists' => $listIds
        ]);

        if ($segmentFilters) {
            $matchingFunnelIds = $this->getFunnelIdsMatchingSegments($segmentFilters);
            foreach (array_keys($segmentFilters) as $segmentKey) {
                $funnelQuery->whereIn('id', $matchingFunnelIds[$segmentKey] ?: [0]);
            }
        }

        if (!empty($statusFilter)) {
            $funnelQuery->whereIn('status', $statusFilter);
        }

        $funnels = $funnelQuery->paginate();
        $with = $this->request->get('with', []);

        $funnelIds = $funnels->pluck('id')->toArray();
        $inProgressCounts = $this->getInProgressSubscriberCounts($funnelIds);

        // Batch fetch descriptions from fc_meta
        $descriptions = Meta::whereIn('object_id', $funnelIds)
            ->where('object_type', 'FluentCrm\App\Models\Funnel')
            ->where('key', 'description')
            ->get()
            ->keyBy('object_id');

        // Batch fetch labels via term_relations + labels
        $termRelations = TermRelation::whereIn('object_id', $funnelIds)
            ->where('object_type', 'FluentCrm\App\Models\Funnel')
            ->get()
            ->groupBy('object_id');

        $allLabelIds = $termRelations->flatten()->pluck('term_id')->unique()->toArray();
        $allLabels = !empty($allLabelIds) ? Label::whereIn('id', $allLabelIds)->get()->keyBy('id') : [];

        foreach ($funnels as $funnel) {
            $funnel->in_progress_subscribers_count = $inProgressCounts[(int) $funnel->id] ?? 0;

            $meta = $descriptions[$funnel->id] ?? null;
            $funnel->description = $meta ? $meta->value : '';

            $funnelTerms = $termRelations[$funnel->id] ?? [];
            $funnel->labels = [];
            $formattedLabels = [];
            foreach ($funnelTerms as $term) {
                $label = $allLabels[$term->term_id] ?? null;
                if ($label) {
                    $formattedLabels[] = [
                        'id'    => $label->id,
                        'slug'  => $label->slug,
                        'title' => $label->title,
                        'color' => $label->settings['color'] ?? ''
                    ];
                }
            }
            $funnel->labels = $formattedLabels;
        }

        $data = [
            'funnels' => $funnels
        ];

        if (in_array('triggers', $with)) {
            $data['triggers'] = $this->getTriggers();
        }

        return $data;
    }

    /**
     * Count contacts currently inside each automation.
     *
     * @param array $funnelIds Funnel IDs to count.
     * @return array
     */
    private function getInProgressSubscriberCounts($funnelIds)
    {
        $funnelIds = array_filter(array_map('intval', (array) $funnelIds));

        if (!$funnelIds) {
            return [];
        }

        $inProgressRows = FunnelSubscriber::select([
                'funnel_id',
                fluentCrmDb()->raw('COUNT(id) as total')
            ])
            ->whereIn('funnel_id', $funnelIds)
            ->whereIn('status', ['active', 'waiting'])
            ->groupBy('funnel_id')
            ->get();

        $counts = [];

        foreach ($inProgressRows as $row) {
            $counts[(int) $row->funnel_id] = (int) $row->total;
        }

        return $counts;
    }

    /**
     * Sanitize array request values that contain model IDs.
     *
     * @param mixed $ids Request value.
     * @return array
     */
    private function sanitizeFilterIds($ids)
    {
        return is_array($ids) ? array_unique(array_filter(array_map('intval', $ids))) : [];
    }

    /**
     * Get automation IDs that reference selected tags/lists in one trigger/sequence scan.
     *
     * @param array $segmentFilters Selected tag/list IDs keyed by settings name.
     * @return array
     */
    private function getFunnelIdsMatchingSegments($segmentFilters)
    {
        $triggerMap = [
            'tags'  => ['fluentcrm_contact_added_to_tags', 'fluentcrm_contact_removed_from_tags'],
            'lists' => ['fluentcrm_contact_added_to_lists', 'fluentcrm_contact_removed_from_lists']
        ];
        $sequenceMap = [
            'tags'  => ['add_contact_to_tag', 'detach_contact_from_tag', 'fluentcrm_contact_added_to_tags', 'fluentcrm_contact_removed_from_tags'],
            'lists' => ['add_contact_to_list', 'detach_contact_from_list', 'fluentcrm_contact_added_to_lists', 'fluentcrm_contact_removed_from_lists']
        ];
        $funnelIds = array_fill_keys(array_keys($segmentFilters), []);
        $triggerNames = [];
        $sequenceActionNames = [];

        foreach (array_keys($segmentFilters) as $segmentKey) {
            $triggerNames = array_merge($triggerNames, $triggerMap[$segmentKey]);
            $sequenceActionNames = array_merge($sequenceActionNames, $sequenceMap[$segmentKey]);
        }

        // Match automation triggers first, keeping results grouped by segment so combined filters can be intersected later.
        $triggerFunnels = Funnel::whereIn('trigger_name', array_values(array_unique($triggerNames)))
            ->get(['id', 'trigger_name', 'settings']);

        foreach ($triggerFunnels as $funnel) {
            foreach ($segmentFilters as $segmentKey => $selectedIds) {
                if (!in_array($funnel->trigger_name, $triggerMap[$segmentKey], true)) {
                    continue;
                }

                if ($this->hasMatchingSegmentSettings($funnel->settings, $segmentKey, $selectedIds)) {
                    $funnelIds[$segmentKey][] = (int) $funnel->id;
                }
            }
        }

        // Match related automation actions and benchmarks in one chunked scan instead of scanning once per segment.
        FunnelSequence::whereIn('action_name', array_values(array_unique($sequenceActionNames)))
            ->select(['id', 'funnel_id', 'action_name', 'settings'])
            ->chunkById(500, function ($chunk) use (&$funnelIds, $segmentFilters, $sequenceMap) {
                foreach ($chunk as $sequence) {
                    foreach ($segmentFilters as $segmentKey => $selectedIds) {
                        if (!in_array($sequence->action_name, $sequenceMap[$segmentKey], true)) {
                            continue;
                        }

                        if ($this->hasMatchingSegmentSettings($sequence->settings, $segmentKey, $selectedIds)) {
                            $funnelIds[$segmentKey][] = (int) $sequence->funnel_id;
                        }
                    }
                }
            });

        foreach ($funnelIds as $segmentKey => $ids) {
            $funnelIds[$segmentKey] = array_values(array_unique($ids));
        }

        return $funnelIds;
    }

    /**
     * Check if serialized funnel settings contain any selected tag/list IDs.
     *
     * @param array  $settings Funnel or sequence settings.
     * @param string $settingsKey Settings key to read.
     * @param array  $selectedIds Selected tag/list IDs.
     * @return bool
     */
    private function hasMatchingSegmentSettings($settings, $settingsKey, $selectedIds)
    {
        $settingsIds = Arr::get((array) $settings, $settingsKey, []);

        if (!is_array($settingsIds) || !$settingsIds) {
            return false;
        }

        $settingsIds = array_filter(array_map(function ($item) {
            if (is_array($item)) {
                return (int) Arr::get($item, 'id');
            }

            return (int) $item;
        }, $settingsIds));

        return (bool) array_intersect($settingsIds, $selectedIds);
    }

    public function getFunnel(Request $request, $funnelId)
    {
        $with = $request->get('with', []);
        $funnel = Funnel::findOrFail($funnelId);

        if (defined('MEPR_PLUGIN_NAME')) {
            // Maybe trigger name changed
            $migrationMaps = [
                'recurring-transaction-expired' => 'mepr-event-transaction-expired'
            ];

            if (isset($migrationMaps[$funnel->trigger_name])) {
                $funnel->trigger_name = $migrationMaps[$funnel->trigger_name];
                $funnel->save();
            }
        }

        $triggers = $this->getTriggers();
        if (isset($triggers[$funnel->trigger_name])) {
            $funnel->trigger = $triggers[$funnel->trigger_name];
        }

        /**
         * Determine the funnel editor details based on the funnel trigger name.
         *
         * The dynamic portion of the hook name, `$funnel->trigger_name`, refers to the trigger name of the funnel.
         *
         * @param object $funnel The funnel object containing the editor details.
         * @since 1.0.0
         *
         */
        $funnel = apply_filters('fluentcrm_funnel_editor_details_' . $funnel->trigger_name, $funnel);

        $funnel->description = $funnel->getMeta('description');
        $inProgressCounts = $this->getInProgressSubscriberCounts([$funnel->id]);
        $funnel->in_progress_subscribers_count = $inProgressCounts[(int) $funnel->id] ?? 0;

        if (!$funnel->settings) {
            $funnel->settings = (object)[];
        }

        $data = [
            'funnel' => $funnel
        ];

        if (in_array('blocks', $with)) {
            $data['blocks'] = $this->getBlocks($funnel);
        }

        if (in_array('block_fields', $with)) {
            $data['block_fields'] = $this->getBlockFields($funnel);
            /**
             * Determine the smart codes for a funnel based on the context.
             *
             * This filter allows modification of the context smart codes used in a funnel based on the funnel's trigger name.
             *
             * @param array  An array of context smart codes.
             * @param string $funnel ->trigger_name   The name of the funnel trigger.
             * @param object $funnel The funnel object.
             * @since 2.5.7
             *
             */
            $data['composer_context_codes'] = apply_filters('fluent_crm_funnel_context_smart_codes', [], $funnel->trigger_name, $funnel);
        }

        if (in_array('funnel_sequences', $with)) {
            FunnelHelper::maybeMigrateConditions($funnel->id);
            $data['funnel_sequences'] = $this->getFunnelSequences($funnel, true);
        }

        return $data;
    }

    public function create(Request $request)
    {
        try {
            $funnel = $this->validate($request->get('funnel'), [
                'trigger_name' => 'required'
            ]);

            $description = sanitize_textarea_field(Arr::get($funnel, 'description'));

            $funnelData = Arr::only($funnel, ['title', 'trigger_name']);

            $funnelData['title'] = sanitize_text_field($funnelData['title']);


            if (empty($funnelData['title'])) {
                $allTriggers = $this->getTriggers();
                $label = Arr::get($allTriggers, $funnelData['trigger_name'] . '.label', 'Unknown Automation');
                $funnelData['title'] = $label . ' (Created at ' . gmdate('Y-m-d') . ')';
            }

            $funnelData['status'] = 'draft';
            $funnelData['settings'] = [];
            $funnelData['conditions'] = [];
            $funnelData['created_by'] = get_current_user_id();

            $funnelData = Sanitize::funnel($funnelData);
            $funnel = Funnel::create($funnelData);

            if ($description) {
                $funnel->updateMeta('description', $description);
            }

            return [
                'funnel'  => $funnel,
                'message' => __('Automation has been created. Please configure now', 'fluent-crm')
            ];
        } catch (ValidationException $e) {
            return $this->validationErrors($e);
        }
    }

    public function delete(Request $request, $funnelId)
    {
        $funnel = Funnel::findOrFail($funnelId);

        $sequences = FunnelSequence::where('funnel_id', $funnelId)->get();
        foreach ($sequences as $deletingSequence) {
            do_action('fluentcrm_funnel_sequence_deleting_' . $deletingSequence->action_name, $deletingSequence, $funnel);
            $deletingSequence->delete();
        }

        $labelIds = TermRelation::where('object_id', $funnel->id)
            ->where('object_type', Funnel::class)
            ->pluck('term_id')
            ->toArray();
        if (!empty($labelIds)) {
            $funnel->detachLabels($labelIds);
        }

        FunnelSubscriber::where('funnel_id', $funnelId)->delete();
        FunnelMetric::where('funnel_id', $funnelId)->delete();

        $funnel->deleteMeta('description');
        $funnel->deleteMeta('funnel_label');
        $funnel->delete();

        (new FunnelHandler())->resetFunnelIndexes();

        return [
            'message' => __('Automation has been deleted', 'fluent-crm')
        ];
    }

    public function getTriggersRest()
    {
        return [
            'triggers' => $this->getTriggers()
        ];
    }

    public function changeTrigger(Request $request, $funnelId)
    {
        $data = $request->only(['title', 'trigger_name']);

        $this->validate($data, [
            'trigger_name' => 'required',
            'title'        => 'required'
        ]);

        $funnel = Funnel::findOrFail($funnelId);

        if ($funnel->trigger_name == $data['trigger_name']) {
            return $this->sendError([
                'message' => __('Trigger name is same', 'fluent-crm')
            ]);
        }

        $funnel->trigger_name = sanitize_text_field($data['trigger_name']);
        $funnel->title = sanitize_text_field($data['title']);

        $funnel->settings = [];
        $funnel->conditions = [];
        $funnel->save();

        /**
         * Determine the funnel editor details based on the funnel's trigger name in FluentCRM.
         *
         * The dynamic portion of the hook name, `$funnel->trigger_name`, refers to the trigger name of the funnel.
         *
         * @param object $funnel The funnel object containing the editor details.
         * @since 2.3.1
         *
         */
        $funnel = apply_filters('fluentcrm_funnel_editor_details_' . $funnel->trigger_name, $funnel);

        return [
            'message' => __('Automation trigger has been successfully updated', 'fluent-crm'),
            'funnel'  => $funnel
        ];

    }

    private function getTriggers()
    {
        /**
         * Determine the list of funnel triggers in FluentCRM.
         *
         * This filter allows you to modify the array of funnel triggers.
         *
         * @param array An array of funnel triggers.
         * @since 1.0.0
         *
         */
        return apply_filters('fluentcrm_funnel_triggers', []);
    }

    private function getBlocks($funnel)
    {
        /**
         * Determine the funnel blocks.
         *
         * This filter allows modification of the funnel blocks.
         *
         * @param array An array of funnel blocks.
         * @param mixed $funnel The funnel object or data.
         * @since 1.0.0
         *
         */
        return apply_filters('fluentcrm_funnel_blocks', [], $funnel);
    }

    private function getBlockFields($funnel)
    {
        /**
         * Determine the funnel block fields.
         *
         * This filter allows modification of the funnel block fields.
         *
         * @param array  The current funnel block fields.
         * @param object $funnel The funnel object.
         * @since 1.0.0
         *
         */
        return apply_filters('fluentcrm_funnel_block_fields', [], $funnel);
    }

    public function getFunnelSequences($funnel, $isFiltered = false)
    {
        $sequences = FunnelHelper::getFunnelSequences($funnel, $isFiltered);
        $formattedSequences = [];
        $childs = [];

        foreach ($sequences as $sequence) {
            if ($sequence['type'] == 'conditional') {
                $sequence['children'] = [
                    'yes' => [],
                    'no'  => []
                ];
            } else if ($sequence['type'] == 'benchmark') {
                //  @todo: we may delete this mid 2023
                if (empty($sequence['settings']['can_enter'])) {
                    $sequence['settings']['can_enter'] = 'yes';
                }
            }

            if ($parentId = Arr::get($sequence, 'parent_id')) {
                if (!isset($childs[$parentId]['yes'])) {
                    $childs[$parentId]['yes'] = [];
                }
                if (!isset($childs[$parentId]['no'])) {
                    $childs[$parentId]['no'] = [];
                }
                $childs[$parentId][$sequence['condition_type']][] = $sequence;
            } else {
                $formattedSequences[$sequence['id']] = $sequence;
            }
        }

        if ($childs) {
            foreach ($childs as $sequenceId => $children) {
                if (isset($formattedSequences[$sequenceId])) {
                    $formattedSequences[$sequenceId]['children'] = $children;
                }
            }
        }

        return array_values($formattedSequences);
    }

    public function saveSequencesFallback(Request $request)
    {
        $funnelId = intval($request->get('funnel_id'));
        return $this->saveSequences($request, $funnelId);
    }

    public function saveSequences(Request $request, $funnelId)
    {
        $data = $request->all();

        $funnel = FunnelHelper::saveFunnelSequence($funnelId, $data);

        return [
            'sequences' => $this->getFunnelSequences($funnel, true),
            'message'   => __('Sequence successfully updated', 'fluent-crm')
        ];
    }

    public function getSubscribers(Request $request, $funnelId)
    {

        $funnel = Funnel::findOrFail($funnelId);

        $search = $request->getSafe('search', 'sanitize_text_field', '');
        $status = $request->getSafe('status', 'sanitize_text_field', '');

        $funnelSubscribersQuery = FunnelSubscriber::with([
            'subscriber',
            'last_sequence',
            'next_sequence_item',
            'metrics' => function ($query) use ($funnelId) {
                $query->where('funnel_id', $funnelId);
            }
        ])
            ->orderBy('id', 'DESC')
            ->where('funnel_id', $funnelId);

        if ($search) {
            $funnelSubscribersQuery->whereHas('subscriber', function ($q) use ($search) {
                $q->searchBy($search);
            });
        }

        $sequenceId = (int)$request->get('sequence_id');
        if ($sequenceId) {
            $funnelSubscribersQuery->whereHas('metrics', function ($q) use ($sequenceId) {
                $q->where('sequence_id', $sequenceId);
            });
        }

        if ($status && $status !== 'all') {
            $funnelSubscribersQuery->where('status', $status);
        }

        $funnelSubscribers = $funnelSubscribersQuery->paginate();

        $data = [
            'funnel_subscribers' => $funnelSubscribers
        ];

        $with = $request->get('with', []);

        if (in_array('funnel', $with)) {
            $data['funnel'] = $funnel;
        }

        if (in_array('sequences', $with)) {
            $sequences = FunnelSequence::where('funnel_id', $funnelId)
                ->orderBy('sequence', 'ASC')
                ->get();
            $formattedSequences = [];
            foreach ($sequences as $sequence) {
                $formattedSequences[] = $sequence;
            }
            $data['sequences'] = $formattedSequences;
        }

        return $data;
    }

    public function getSubscriberReporting(Request $request, $funnelId, $contactId)
    {
        Funnel::findOrFail($funnelId);
        Subscriber::findOrFail($contactId);

        $funnelSubscriber = FunnelSubscriber::with([
            'last_sequence',
            'next_sequence_item',
            'metrics' => function ($query) use ($funnelId) {
                $query->where('funnel_id', $funnelId);
            }
        ])
            ->where('funnel_id', $funnelId)
            ->where('subscriber_id', $contactId)
            ->first();

        $sequences = FunnelSequence::where('funnel_id', $funnelId)
            ->orderBy('sequence', 'ASC')
            ->get();

        $formattedSequences = [];
        foreach ($sequences as $sequence) {
            $formattedSequences[] = $sequence;
        }

        return [
            'funnel_subscriber' => $funnelSubscriber,
            'sequences'         => $formattedSequences
        ];
    }

    public function getAllActivities(Request $request)
    {
        $search = $request->getSafe('search', 'sanitize_text_field', '');
        $status = $request->getSafe('status', 'sanitize_text_field', '');

        $funnelSubscribersQuery = FunnelSubscriber::with([
            'subscriber',
            'last_sequence',
            'next_sequence_item',
            'funnel.actions' => function ($query) {
                $query->orderBy('sequence', 'ASC');
            }
        ])
            ->orderBy('id', 'DESC');

        if ($search) {
            $funnelSubscribersQuery->whereHas('subscriber', function ($q) use ($search) {
                $q->searchBy($search);
            });
        }

        if ($status) {
            $funnelSubscribersQuery->where('status', $status);
        }

        $funnelSubscribers = $funnelSubscribersQuery->paginate();

        $funnelIds = $funnelSubscribers->pluck('funnel_id')->unique()->values()->toArray();
        $subscriberIds = $funnelSubscribers->pluck('subscriber_id')->unique()->values()->toArray();

        $allMetrics = FunnelMetric::whereIn('funnel_id', $funnelIds)
            ->whereIn('subscriber_id', $subscriberIds)
            ->get()
            ->groupBy(function ($metric) {
                return $metric->funnel_id . '_' . $metric->subscriber_id;
            });

        foreach ($funnelSubscribers as $funnelSubscriber) {
            $key = $funnelSubscriber->funnel_id . '_' . $funnelSubscriber->subscriber_id;
            $funnelSubscriber->metrics = $allMetrics[$key] ?? [];
        }

        return [
            'activities' => $funnelSubscribers
        ];
    }

    public function removeBulkSubscribers(Request $request)
    {
        $funnel_subscriber_ids = $request->get('funnel_subscriber_ids', []);

        $funnel_subscriber_ids = array_map('intval', $funnel_subscriber_ids);

        if (!$funnel_subscriber_ids) {
            return $this->sendError([
                'message' => __('Please provide automation subscriber IDs', 'fluent-crm')
            ]);
        }

        $items = FunnelSubscriber::whereIn('id', $funnel_subscriber_ids)->get();

        foreach ($items as $item) {
            FunnelMetric::where('funnel_id', $item->funnel_id)
                ->where('subscriber_id', $item->subscriber_id)
                ->delete();
        }

        FunnelSubscriber::whereIn('id', $funnel_subscriber_ids)->delete();

        return [
            'message' => __('Selected subscribers have been removed from this automation', 'fluent-crm')
        ];
    }

    public function report(Request $request, Reporting $reporting, $funnelId)
    {
        return [
            'stats' => $reporting->funnelStat($funnelId)
        ];
    }

    public function updateFunnelProperty(Request $request, $funnelId)
    {
        $funnel = Funnel::findOrFail($funnelId);
        $newStatus = $request->getSafe('status', 'sanitize_text_field');

        $allowedStatuses = ['draft', 'published'];
        if (!in_array($newStatus, $allowedStatuses, true)) {
            return $this->sendError([
                'message' => __('Invalid status value', 'fluent-crm')
            ]);
        }

        if ($funnel->status == $newStatus) {
            return $this->sendError([
                'message' => __('Automation already has the same status', 'fluent-crm')
            ]);
        }

        $funnel->status = $newStatus;
        $funnel->save();

        return [
            /* translators: %s: subscription status */
            'message' => sprintf(esc_html__('Status has been updated to %s', 'fluent-crm'), $newStatus)
        ];
    }

    public function handleBulkAction(Request $request)
    {
        $actionName = $request->getSafe('action_name', 'sanitize_text_field', '');

        $funnelIds = array_map('intval', (array)$request->get('funnel_ids', []));

        $funnelIds = array_unique(array_filter($funnelIds));

        if (!$funnelIds) {
            return $this->sendError([
                'message' => __('Please provide automation IDs', 'fluent-crm')
            ]);
        }

        if ($actionName == 'change_funnel_status') {
            $newStatus = sanitize_text_field($request->get('status', ''));
            if (!$newStatus) {
                return $this->sendError([
                    'message' => __('Please select status', 'fluent-crm')
                ]);
            }

            $funnels = Funnel::whereIn('id', $funnelIds)->get();

            foreach ($funnels as $funnel) {
                $oldStatus = $funnel->status;
                if ($oldStatus != $newStatus) {
                    $funnel->status = $newStatus;
                    $funnel->save();
                }
            }

            (new FunnelHandler())->resetFunnelIndexes();

            return [
                'message' => __('Status has been changed for the selected automations', 'fluent-crm')
            ];
        }

        if ($actionName == 'delete_funnels') {

            $funnels = Funnel::whereIn('id', $funnelIds)->get();

            foreach ($funnels as $funnel) {
                $sequences = FunnelSequence::where('funnel_id', $funnel->id)->get();

                $labelIds = TermRelation::where('object_id', $funnel->id)
                    ->where('object_type', Funnel::class)
                    ->pluck('term_id')
                    ->toArray();
                if (!empty($labelIds)) {
                    $funnel->detachLabels($labelIds);
                }

                foreach ($sequences as $deletingSequence) {
                    do_action('fluentcrm_funnel_sequence_deleting_' . $deletingSequence->action_name, $deletingSequence, $funnel);
                    $deletingSequence->delete();
                }
                FunnelSubscriber::where('funnel_id', $funnel->id)->delete();
                FunnelMetric::where('funnel_id', $funnel->id)->delete();

                $funnel->deleteMeta('funnel_label');
                $funnel->deleteMeta('description');
                $funnel->delete();
            }

            (new FunnelHandler())->resetFunnelIndexes();

            return [
                'message' => __('Selected automations have been deleted permanently', 'fluent-crm'),
            ];

        }

        if ($actionName == 'apply_labels') {
            $newLabelIds = $request->get('labels'); // labels are id 
            $newLabelIds = is_array($newLabelIds) ? array_map('intval', $newLabelIds) : [];

            $newLabelIds = array_unique(array_filter($newLabelIds));

            if (!$newLabelIds) {
                return $this->sendError([
                    'message' => __('Please provide labels', 'fluent-crm')
                ]);
            }

            $funnels = Funnel::whereIn('id', $funnelIds)->get();

            foreach ($funnels as $funnel) {
                $funnel->attachLabels($newLabelIds);
            }

            return [
                'message' => __('Labels has been applied successfully', 'fluent-crm'),
            ];
        }

        return $this->sendError([
            'message' => __('invalid bulk action', 'fluent-crm')
        ]);
    }

    public function cloneFunnel(Request $request, $funnelId)
    {
        $oldFunnel = Funnel::findOrFail($funnelId);

        $newFunnelData = [
            'title'        => __('[Copy] ', 'fluent-crm') . $oldFunnel->title,
            'trigger_name' => $oldFunnel->trigger_name,
            'status'       => 'draft',
            'conditions'   => $oldFunnel->conditions,
            'settings'     => $oldFunnel->settings,
            'created_by'   => get_current_user_id()
        ];
        $labelIds = $oldFunnel->getFormattedLabels()->pluck('id')->toArray();

        $funnel = Funnel::create($newFunnelData);
        $funnel->attachLabels($labelIds);

        $sequences = FunnelHelper::getFunnelSequences($oldFunnel, true);

        $sequenceIds = [];
        $cDelay = 0;
        $delay = 0;

        $childs = [];
        $oldNewMaps = [];

        foreach ($sequences as $index => $sequence) {
            $oldId = $sequence['id'];
            unset($sequence['id']);
            unset($sequence['created_at']);
            unset($sequence['updated_at']);

            // it's creatable
            $sequence['funnel_id'] = $funnel->id;
            $sequence['status'] = 'published';
            $sequence['conditions'] = [];
            $sequence['sequence'] = $index + 1;
            $sequence['c_delay'] = $cDelay;
            $sequence['delay'] = $delay;
            $delay = 0;

            $actionName = $sequence['action_name'];

            if ($actionName == 'fluentcrm_wait_times') {
                $delay = FunnelHelper::getDelayInSecond($sequence['settings']);
                $cDelay += $delay;
            }

            /**
             * Determine the funnel sequence before saving.
             *
             * This filter allows modification of the funnel sequence before it is saved.
             *
             * @param array $sequence The sequence data to be saved.
             * @param array $funnel The funnel data associated with the sequence.
             *
             * @return array The modified sequence data.
             * @since 1.1.4
             *
             */
            $sequence = apply_filters('fluentcrm_funnel_sequence_saving_' . $sequence['action_name'], $sequence, $funnel);
            if (Arr::get($sequence, 'type') == 'benchmark') {
                $delay = $sequence['delay'];
            }

            $sequence['created_by'] = get_current_user_id();

            $parentId = Arr::get($sequence, 'parent_id');

            if ($parentId) {
                $childs[$parentId][] = $sequence;
            } else {
                $createdSequence = FunnelSequence::create($sequence);
                do_action('fluent_crm/sequence_created_' . $createdSequence->action_name, $createdSequence);
                $sequenceIds[] = $createdSequence->id;
                $oldNewMaps[$oldId] = $createdSequence->id;
            }
        }

        if ($childs) {
            foreach ($childs as $oldParentId => $childBlocks) {
                foreach ($childBlocks as $childBlock) {
                    $newParentId = Arr::get($oldNewMaps, $oldParentId);
                    if ($newParentId) {
                        $childBlock['parent_id'] = $newParentId;
                        $createdSequence = FunnelSequence::create($childBlock);
                        $sequenceIds[] = $createdSequence->id;
                    }
                }
            }
        }

        FunnelHelper::maybeMigrateConditions($funnel->id);
        (new FunnelHandler())->resetFunnelIndexes();

        return [
            'message' => __('Automation has been successfully cloned', 'fluent-crm'),
            'funnel'  => $funnel
        ];
    }

    public function importFunnel(Request $request)
    {
        $funnelArray = $request->get('funnel');
        $sequences = Helper::parseArrayOrJson($request->get('sequences'));

        if (!is_array($funnelArray) || empty($funnelArray['trigger_name'])) {
            return $this->sendError([
                'message' => __('Invalid automation data. Please provide a valid automation with a trigger name.', 'fluent-crm')
            ]);
        }

        if (!is_array($sequences)) {
            $sequences = [];
        }

        $funnel = $this->createFunnelFromData($funnelArray, $sequences);

        return [
            'message' => __('Automation has been successfully imported', 'fluent-crm'),
            'funnel'  => $funnel
        ];

    }

    public function deleteSubscribers(Request $request, $funnelId)
    {
        $funnel = Funnel::findOrFail($funnelId);
        $ids = $request->get('subscriber_ids');
        $ids = is_array($ids) ? array_map('intval', $ids) : [];
        $ids = array_unique(array_filter($ids));

        if (!$ids) {
            return $this->sendError([
                'message' => __('subscriber_ids parameter is required', 'fluent-crm')
            ]);
        }

        FunnelHelper::removeSubscribersFromFunnel($funnelId, $ids);

        return [
            'message' => __('Subscriber has been removed from this automation', 'fluent-crm')
        ];
    }

    public function subscriberAutomations(Request $request, $subscriberId)
    {
        $automations = FunnelSubscriber::where('subscriber_id', $subscriberId)
            ->with([
                'funnel',
                'last_sequence',
                'next_sequence_item'
            ])
            ->orderBy('id', 'DESC')
            ->paginate();

        return [
            'automations' => $automations
        ];
    }

    public function updateSubscriptionStatus(Request $request, $funnelId, $subscriberId)
    {
        $status = $request->getSafe('status', 'sanitize_text_field');

        $allowedStatuses = ['active', 'completed', 'cancelled'];
        if (!$status || !in_array($status, $allowedStatuses, true)) {
            return $this->sendError([
                'message' => __('Invalid subscription status', 'fluent-crm')
            ]);
        }

        $funnelSubscriber = FunnelSubscriber::where('funnel_id', $funnelId)
            ->where('subscriber_id', $subscriberId)
            ->first();

        if (!$funnelSubscriber) {
            return $this->sendError([
                'message' => __('No Corresponding report found', 'fluent-crm')
            ]);
        }

        if ($funnelSubscriber->status == 'completed') {
            return $this->sendError([
                'message' => __('The status already completed state', 'fluent-crm')
            ]);
        }

        $funnelSubscriber->status = $status;

        if ($status == 'active' && !$funnelSubscriber->next_execution_time) {
            $funnelSubscriber->next_execution_time = gmdate('Y-m-d H:i:s', current_time('timestamp') + 60);
        }

        $funnelSubscriber->save();

        return [
            /* translators: %s: subscription status */
            'message' => sprintf(esc_html__('Status has been updated to %s', 'fluent-crm'), $status)
        ];
    }

    public function forceAdvanceSubscriber(Request $request, $funnelId, $subscriberId)
    {
        $funnelSubscriber = FunnelSubscriber::where('funnel_id', intval($funnelId))
            ->where('subscriber_id', intval($subscriberId))
            ->first();

        if (!$funnelSubscriber) {
            return $this->sendError([
                'message' => __('No corresponding subscriber found in this automation', 'fluent-crm')
            ]);
        }

        if (in_array($funnelSubscriber->status, ['completed', 'cancelled', 'pending'])) {
            return $this->sendError([
                'message' => sprintf(
                    /* translators: %s: subscriber status */
                    esc_html__('Cannot advance a subscriber with status: %s', 'fluent-crm'),
                    $funnelSubscriber->status
                )
            ]);
        }

        $targetSequenceId = intval($request->get('sequence_id'));
        $targetSequence = FunnelSequence::where('id', $targetSequenceId)
            ->where('funnel_id', intval($funnelId))
            ->first();

        if (!$targetSequence) {
            return $this->sendError([
                'message' => __('Target sequence not found', 'fluent-crm')
            ]);
        }

        $processor = new FunnelProcessor();

        // If waiting on benchmark, record skip metric for the current benchmark
        if ($funnelSubscriber->status === 'waiting') {
            $benchmarkSeq = FunnelSequence::find($funnelSubscriber->next_sequence_id);
            if ($benchmarkSeq) {
                FunnelMetric::updateOrCreate(
                    [
                        'funnel_id'     => intval($funnelId),
                        'sequence_id'   => $benchmarkSeq->id,
                        'subscriber_id' => intval($subscriberId),
                    ],
                    [
                        'benchmark_value'    => 0,
                        'benchmark_currency' => 'USD',
                        'status'             => 'skipped',
                        'notes'              => __('Manually skipped by admin', 'fluent-crm'),
                    ]
                );
                FunnelHelper::changeFunnelSubSequenceStatus($funnelSubscriber->id, $benchmarkSeq->id, 'skipped');
            }
        }

        // Advance to the target sequence
        // Find the sequence just before the target so SequencePoints includes the target in its query
        $prevSequence = FunnelSequence::where('funnel_id', intval($funnelId))
            ->where('sequence', '<', $targetSequence->sequence)
            ->orderBy('sequence', 'DESC')
            ->first();

        $funnelSubscriber->last_sequence_id = $prevSequence ? $prevSequence->id : 0;
        $funnelSubscriber->next_sequence_id = $targetSequence->id;
        $funnelSubscriber->next_sequence = $targetSequence->sequence;
        $funnelSubscriber->status = 'active';
        $funnelSubscriber->next_execution_time = current_time('mysql');
        $funnelSubscriber->save();

        $processor->processFunnelAction($funnelSubscriber);

        $funnelSubscriber = FunnelSubscriber::where('id', $funnelSubscriber->id)
            ->with(['last_sequence', 'next_sequence_item'])
            ->first();

        return [
            'message'           => __('Subscriber has been advanced', 'fluent-crm'),
            'funnel_subscriber' => $funnelSubscriber
        ];
    }

    private function maybeMigrateDB()
    {
        // Temp
        Funnel::whereNull('trigger_name')
            ->where('status', 'draft')
            ->whereNull('created_by')
            ->delete();


        $sequence = \FluentCrm\App\Models\FunnelSequence::first();
        $isMigrated = false;
        global $wpdb;
        if ($sequence) {
            $attributes = $sequence->getAttributes();
            if (isset($attributes['parent_id'])) {
                $isMigrated = true;
            }
        } else {
            $isMigrated = $wpdb->get_col($wpdb->prepare("SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND COLUMN_NAME='parent_id' AND TABLE_NAME=%s", $wpdb->prefix . 'fc_funnel_sequences'));
        }

        if (!$isMigrated) {
            $sequenceTable = $wpdb->prefix . 'fc_funnel_sequences';
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared 
            $wpdb->query("ALTER TABLE {$sequenceTable} ADD COLUMN `parent_id` bigint NOT NULL DEFAULT '0', ADD `condition_type` varchar(192) NULL AFTER `parent_id`");
        }
    }

    public function getEmailReports(Request $request, $funnelId)
    {
        $funnel = Funnel::findOrFail($funnelId);
        $emailSequences = FunnelSequence::where('funnel_id', $funnel->id)
            ->orderBy('sequence', 'ASC')
            ->where('action_name', 'send_custom_email')
            ->get();

        $campaignIds = [];
        foreach ($emailSequences as $emailSequence) {
            $refId = Arr::get($emailSequence->settings, 'reference_campaign');
            if ($refId) {
                $campaignIds[] = $refId;
            }
        }

        $campaigns = FunnelCampaign::whereIn('id', array_unique($campaignIds))->get()->keyBy('id');

        foreach ($emailSequences as $emailSequence) {
            $refId = Arr::get($emailSequence->settings, 'reference_campaign');
            $campaign = $refId ? ($campaigns[$refId] ?? null) : null;

            if ($campaign) {
                $emailSequence->campaign = [
                    'subject'               => $campaign->email_subject,
                    'id'                    => $campaign->id,
                    'stats'                 => $campaign->stats(),
                    'status'                => $campaign->status,
                    'open_tracking_status'  => $campaign->getOpenTrackingStatus(),
                    'click_tracking_status' => $campaign->getClickTrackingStatus()
                ];
            } else {
                $emailSequence->campaign = null;
            }
        }

        return [
            'email_sequences' => $emailSequences
        ];
    }

    public function saveEmailActionFallback(Request $request)
    {
        $funnelId = intval($request->get('funnel_id'));
        return $this->saveEmailAction($request, $funnelId);
    }

    public function saveEmailAction(Request $request, $funnelId)
    {
        $funnel = Funnel::findOrFail($funnelId);

        $settings = Helper::parseArrayOrJson($request->get('action_data'));
        $settings['action_name'] = 'send_custom_email';

        $funnelCampaign = Arr::get($settings, 'campaign', []);

        $funnelCampaignId = Arr::get($funnelCampaign, 'id');

        $data = Arr::only($funnelCampaign, array_keys(FunnelCampaign::getMock()));
        $data['settings']['mailer_settings'] = Arr::get($settings, 'mailer_settings', []);

        $type = 'created';

        if ($funnelCampaignId && $funnel->id == Arr::get($data, 'parent_id')) {
            // We have this campaign
            $data['settings'] = \maybe_serialize($data['settings']);
            $data['type'] = 'funnel_email_campaign';
            $data['title'] = $funnel->title . ' (' . $funnel->id . ')';
            FunnelCampaign::where('id', $funnelCampaignId)->update($data);
            $type = 'updated';
        } else {
            $data['parent_id'] = $funnel->id;
            $data['type'] = 'funnel_email_campaign';
            $data['title'] = $funnel->title . ' (' . $funnel->id . ')';
            $campaign = FunnelCampaign::create($data);
            $funnelCampaignId = $campaign->id;
        }

        if (Arr::get($funnelCampaign, 'design_template') == 'visual_builder') {
            $design = Arr::get($funnelCampaign, '_visual_builder_design', []);
            fluentcrm_update_campaign_meta($funnelCampaignId, '_visual_builder_design', $design);
        } else {
            fluentcrm_delete_campaign_meta($funnelCampaignId, '_visual_builder_design');
        }

        $refCampaign = FunnelCampaign::find($funnelCampaignId);

        return [
            'type'               => $type,
            'reference_campaign' => $funnelCampaignId,
            'campaign'           => Arr::only($refCampaign->toArray(), array_keys(FunnelCampaign::getMock()))
        ];
    }

    public function getSyncableContactCounts(Request $request, $funnelId)
    {
        $funnel = Funnel::findOrFail($funnelId);
        $latestAction = \FluentCrm\App\Models\FunnelSequence::where('funnel_id', $funnelId)
            ->orderBy('sequence', 'DESC')
            ->first();

        if (!$latestAction) {
            return [
                'syncable_count' => 0
            ];
        }

        $count = \FluentCrm\App\Models\FunnelSubscriber::where('funnel_id', $funnel->id)
            ->with(['subscriber'])
            ->where('status', 'completed')
            ->whereHas('subscriber', function ($q) {
                $q->where('status', 'subscribed');
            })
            ->whereHas('last_sequence', function ($q) use ($latestAction) {
                $q->where('action_name', '!=', 'end_this_funnel')
                    ->where('id', '!=', $latestAction->id)
                    ->where('sequence', '<', $latestAction->sequence);
            })->count();

        return [
            'syncable_count' => $count
        ];
    }

    public function syncNewSteps(Request $request, $funnelId)
    {
        $funnel = Funnel::findOrFail($funnelId);

        if ($funnel->status != 'published') {
            return $this->sendError([
                'message' => __('Automation status needs to be published', 'fluent-crm')
            ]);
        }

        if (!defined('FLUENTCAMPAIGN_DIR_FILE')) {
            return $this->sendError([
                'message' => __('This feature requires the latest version of FluentCRM Pro', 'fluent-crm')
            ]);
        }

        $cleanup = new \FluentCampaign\App\Hooks\Handlers\Cleanup();

        if (!method_exists($cleanup, 'syncAutomationSteps')) {
            return $this->sendError([
                'message' => __('This feature requires the latest version of FluentCRM Pro', 'fluent-crm')
            ]);
        }

        $result = $cleanup->syncAutomationSteps($funnel);

        if (is_wp_error($result)) {
            return $this->sendError($result->get_error_messages());
        }

        return [
            'message' => __('Synced successfully', 'fluent-crm')
        ];
    }

    public function getTemplates()
    {
        $templates = fluentCrmPersistentCache('funnel_remote_templates', function () {
            return $this->getDynamicTemplates();
        }, 60 * 60 * 24); // 24 hours

        $allowedTemplates = $this->filterTemplates($templates);

        return [
            'templates' => $allowedTemplates,
            'all'       => $templates,
            'cats'      => $this->allowedCategories()
        ];
    }

    public function filterTemplates($templates)
    {
        $allowedCategories = $this->allowedCategories();
        $filteredTemplates = [];

        foreach ($templates as $template) {
            if (empty($template['dependencies'])) {
                $filteredTemplates[] = $template;
                continue;
            }
            $diff = array_diff($template['dependencies'], $allowedCategories);
            if (!$diff) {
                $filteredTemplates[] = $template;
            }
        }

        return $filteredTemplates;
    }

    public function allowedCategories()
    {
        $categories = [];

        if (defined('FLUENTFORM')) {
            $categories[] = 'fluentforms';
        }

        if (defined('MEPR_PLUGIN_NAME')) {
            $categories[] = 'memberpress';
        }

        if (defined('FLUENT_BOARDS')) {
            $categories[] = 'fluent-boards';
        }

        if (defined('FLUENT_SUPPORT')) {
            $categories[] = 'fluent-support';
        }

        if (defined('FLUENT_BOOKING_VERSION')) {
            $categories[] = 'fluent-booking';
        }

        if (defined('WC_PLUGIN_FILE')) {
            $categories[] = 'woocommerce';
        }

        if (defined('WCS_INIT_TIMESTAMP')) {
            $categories[] = 'wcs';
        }

        if (Helper::isEdd3()) {
            $categories[] = 'edd';
        }

        if (defined('LIFTERLMS_VERSION')) {
            $categories[] = 'lifterlms';
        }

        if (defined('TUTOR_VERSION')) {
            $categories[] = 'tutor';
        }

        if (defined('LEARNDASH_VERSION')) {
            $categories[] = 'learndash';
        }

        if (defined('SURECART_PLUGIN_FILE')) {
            $categories[] = 'surecart';
        }

        if (Helper::isExperimentalEnabled('abandoned_cart')) {
            $categories[] = 'woo_abandon_carts';
        }

        if (defined('FLUENTCAMPAIGN_DIR_FILE')) {
            $categories[] = 'fluentcrm_pro';
        }

        return $categories;

    }

    public function createFromTemplate(Request $request)
    {
        $template = $request->get('template');

        $templateData = $this->getFunnelData($template['content']);

        if (empty($templateData) || !isset($templateData['sequences'])) {
            return $this->sendError([
                'message' => __('Could not load template data. The template URL may be unavailable or not allowed.', 'fluent-crm')
            ]);
        }

        $funnelArray = $templateData;
        $sequences = $templateData['sequences'];

        $funnel = $this->createFunnelFromData($funnelArray, $sequences);

        return [
            'funnel'  => $funnel,
            'message' => __('Automation has been created from template', 'fluent-crm')
        ];

    }

    private function createFunnelFromData($funnelArray, $sequences)
    {
        $funnelArray = Sanitize::funnel($funnelArray);

        $newFunnelData = [
            'title'        => Arr::get($funnelArray, 'title'),
            'trigger_name' => Arr::get($funnelArray, 'trigger_name'),
            'status'       => 'draft',
            'conditions'   => Arr::get($funnelArray, 'conditions', []),
            'settings'     => Arr::get($funnelArray, 'settings'),
            'created_by'   => get_current_user_id()
        ];

        $funnel = Funnel::create($newFunnelData);

        $funnelLabels = Arr::get($funnelArray, 'labels', []);
        if (isset($funnelLabels) && !empty($funnelLabels)) {
            $newLabels = [];
            foreach ($funnelLabels as $key => $funnelLabel) {
                // Validate required fields
                if (!isset($funnelLabel['slug'], $funnelLabel['title'])) {
                    continue; // Skip invalid labels
                }

                $existLabel = Label::where('taxonomy_name', 'global_label')->where('slug', $funnelLabel['slug'])->first();
                if (!$existLabel) {
                    $labelData = [
                        'slug'  => sanitize_text_field($funnelLabel['slug']),
                        'title' => sanitize_text_field($funnelLabel['title']),
                    ];
                    $color = sanitize_hex_color($funnelLabel['color']);

                    $labelData['settings'] = [
                        'color' => $color
                    ];

                    $existLabel = Label::create($labelData);
                }

                $newLabels[] = $existLabel->id;
            }
            $funnel->attachLabels($newLabels);
        }

        $sequenceIds = [];
        $cDelay = 0;
        $delay = 0;

        $childs = [];
        $oldNewMaps = [];

        foreach ($sequences as $index => $sequence) {
            $oldId = $sequence['id'];
            unset($sequence['id']);
            unset($sequence['created_at']);
            unset($sequence['updated_at']);
            // it's creatable
            $sequence['funnel_id'] = $funnel->id;
            $sequence['status'] = 'published';
            $sequence['conditions'] = [];
            $sequence['sequence'] = $index + 1;
            $sequence['c_delay'] = $cDelay;
            $sequence['delay'] = $delay;
            $delay = 0;

            $actionName = $sequence['action_name'];

            if ($actionName == 'fluentcrm_wait_times') {
                $delay = FunnelHelper::getDelayInSecond($sequence['settings']);
                $cDelay += $delay;
            }
            /**
             * Determine the funnel sequence before saving.
             *
             * This filter allows modification of the funnel sequence before it is saved.
             *
             * @param array $sequence The sequence data to be saved.
             * @param array $funnel The funnel data associated with the sequence.
             *
             * @return array The modified sequence data.
             * @since 2.9.20
             *
             */
            $sequence = apply_filters('fluentcrm_funnel_sequence_saving_' . $sequence['action_name'], $sequence, $funnel);

            if (Arr::get($sequence, 'type') == 'benchmark') {
                $delay = $sequence['delay'];
            }

            $sequence['created_by'] = get_current_user_id();

            $parentId = Arr::get($sequence, 'parent_id');

            if ($parentId) {
                $childs[$parentId][] = $sequence;
            } else {
                $createdSequence = FunnelSequence::create($sequence);
                do_action('fluent_crm/sequence_created_' . $createdSequence->action_name, $createdSequence);

                $sequenceIds[] = $createdSequence->id;
                $oldNewMaps[$oldId] = $createdSequence->id;
            }
        }

        if ($childs) {
            foreach ($childs as $oldParentId => $childBlocks) {
                foreach ($childBlocks as $childBlock) {
                    $newParentId = Arr::get($oldNewMaps, $oldParentId);
                    if ($newParentId) {
                        $childBlock['parent_id'] = $newParentId;
                        $createdSequence = FunnelSequence::create($childBlock);
                        $sequenceIds[] = $createdSequence->id;
                    }
                }
            }
        }

        (new FunnelHandler())->resetFunnelIndexes();
        FunnelHelper::maybeMigrateConditions($funnel->id);

        return $funnel;
    }

    public function temporaryStaticTemplates()
    {
        return \FluentCrm\App\Services\Funnel\StaticTemplates::get();
    }

    public function getDynamicTemplates()
    {
        $restBase = defined('FC_TEMPLATE_API_DOMAIN') ? FC_TEMPLATE_API_DOMAIN : 'https://fluentcrm.com';
        $restApi = $restBase . '/wp-json/wp/v2/automation-templates?per_page=50';

        $response = wp_remote_get($restApi, [
            'sslverify' => true,
        ]);

        if (is_wp_error($response)) {
            // Handle error
            error_log($response->get_error_message());
            return [];
        }

        $body = wp_remote_retrieve_body($response);
        $templateLists = json_decode($body, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            // Handle JSON error
            error_log('JSON Decode Error: ' . json_last_error_msg());
            return [];
        }

        if (!is_array($templateLists)) {
            return [];
        }

        $formattedTemplates = [];
        foreach ($templateLists as $template) {

            if (!$template['template_json']) {
                // Skip if no template json
                continue;
            }
            $formattedTemplates[] = [
                'id'                => $template['id'],
                'title'             => $template['title']['rendered'],
//                'description'  => $template['excerpt']['rendered'],
                'short_description' => $template['short_description'],
                'type'              => $template['template_type'],
                'dependencies'      => $template['plugin_dependencies'],
                'content'           => $template['template_json'],
                'link'              => $template['link'],
                'media'             => $template['_links']['wp:attachment'][0]['href'] ?? '',
                'status'            => $template['status'],
            ];
        }

        return $formattedTemplates;
    }

    public function getFunnelData($jsonUrl)
    {
        $allowedHosts = ['fluentcrm.com', 'www.fluentcrm.com', 'wpmanageninja.com', 'www.wpmanageninja.com'];
        if (defined('FC_TEMPLATE_API_DOMAIN')) {
            $configuredHost = wp_parse_url(FC_TEMPLATE_API_DOMAIN, PHP_URL_HOST);
            if ($configuredHost) {
                $allowedHosts[] = $configuredHost;
            }
        }
        $parsedUrl = wp_parse_url($jsonUrl);
        $host = isset($parsedUrl['host']) ? strtolower($parsedUrl['host']) : '';

        if (!in_array($host, $allowedHosts, true)) {
            return [];
        }

        $request = wp_remote_get($jsonUrl, [
            'sslverify' => true,
        ]);

        if (is_wp_error($request)) {
            return [];
        }
        return json_decode(wp_remote_retrieve_body($request), true);
    }

    public function sendTestWebhook(Request $request)
    {

        $this->validate($request->all(), [
            'data.remote_url' => 'required|url',
        ], [
            'data.remote_url.required' => __('Remote URL is required', 'fluent-crm')
        ]);

        $payloadData = $request->get('data', []);

        $bodyDataType = sanitize_text_field(Arr::get($payloadData, 'body_data_type', ''));
        $bodyDataValues = Arr::get($payloadData, 'body_data_values', []);
        $headerType = sanitize_text_field(Arr::get($payloadData, 'header_type', ''));
        $headerData = Arr::get($payloadData, 'header_data', []);
        $sendingMethod = sanitize_text_field(Arr::get($payloadData, 'sending_method', ''));
        $requestFormat = sanitize_text_field(Arr::get($payloadData, 'request_format', ''));
        $remoteUrl = sanitize_text_field(Arr::get($payloadData, 'remote_url', ''));

        if (!is_array($bodyDataValues)) {
            $bodyDataValues = [];
        }

        if (!is_array($headerData)) {
            $headerData = [];
        }


        $user = get_user_by('ID', get_current_user_id());
        $email = $user->user_email;

        $subscriber = Subscriber::where('email', $email)
            ->orWhere('status', 'subscribed')
            ->first();

        if (!$subscriber) {
            return $this->sendError([
                'message' => __('No subscriber found to send test webhook. Please add at least one contact with subscribed status.', 'fluent-crm')
            ]);
        }

        $headers = $this->prepareHeaders($headerType, $headerData, $subscriber);
        $body = $this->prepareBody($bodyDataType, $bodyDataValues, $subscriber);

        $isJson = 'no';
        if ($requestFormat == 'json' && $sendingMethod == 'POST') {
            $isJson = 'yes';
            $headers['Content-Type'] = 'application/json; charset=utf-8';
        }

        if ($sendingMethod == 'GET') {
            $remoteUrl = add_query_arg($body, $remoteUrl);
        }

        $data = [
            'payload'    => [
                'body'      => ($sendingMethod == 'POST') ? $body : null,
                'method'    => $sendingMethod,
                'headers'   => $headers,
                /**
                 * Determine whether to verify SSL for FluentCRM webhook requests.
                 *
                 * This filter allows you to control whether SSL verification should be performed
                 * when making webhook requests in FluentCRM.
                 *
                 * @param bool Whether to verify SSL. Default false.
                 * @since 2.9.25
                 *
                 */
                'sslverify' => apply_filters('fluent_crm/webhook_ssl_verify', true)
            ],
            'remote_url' => $remoteUrl,
            'is_json'    => $isJson
        ];

        if ($data['is_json'] == 'yes') {
            $data['payload']['body'] = json_encode($data['payload']['body']);
        }

        $response = wp_remote_request($data['remote_url'], $data['payload']);

        if (is_wp_error($response)) {
            return $this->sendError([
                'message' => __('Test Webhook failed to send', 'fluent-crm') . ': ' . $response->get_error_message()
            ]);
        }

        return [
            'message' => __('Test Webhook has been sent successfully', 'fluent-crm')
        ];
    }

    private function prepareHeaders($headerType, $headerData, $subscriber)
    {
        $headers = [];
        if ($headerType === 'with_headers') {
            foreach ($headerData as $item) {
                $dataKey = sanitize_text_field(Arr::get($item, 'data_key', ''));
                $dataValue = sanitize_text_field(Arr::get($item, 'data_value', ''));

                if (empty($dataKey) || empty($dataValue)) {
                    continue;
                }
                $dataKey = str_replace(' ', '-', $dataKey);
                $headers[$dataKey] = Parser::parse($dataValue, $subscriber);
            }
        }
        return $headers;
    }

    private function prepareBody($bodyDataType, $bodyDataValues, $subscriber)
    {
        $body = [];
        if ($bodyDataType === 'subscriber_data') {
            $body = $subscriber->toArray();
            $body['custom_field'] = $subscriber->custom_fields();
        } else {
            foreach ($bodyDataValues as $item) {
                $dataKey = sanitize_text_field(Arr::get($item, 'data_key', ''));
                $dataValue = sanitize_text_field(Arr::get($item, 'data_value', ''));

                if (empty($dataKey) || empty($dataValue)) {
                    continue;
                }
                $body[$dataKey] = Parser::parse($dataValue, $subscriber);
            }
        }
        return $body;
    }

    public function updateFunnelTitle(Request $request, $funnelId)
    {
        $funnel = Funnel::findOrFail($funnelId);
        $newTitle = $request->getSafe('title', 'sanitize_text_field');

        if ($funnel->title == $newTitle) {
            return $this->sendError([
                'message' => __('Automation already has the same title', 'fluent-crm')
            ]);
        }

        $funnel->title = $newTitle;
        $funnel->save();

        return [
            /* translators: %s: the new funnel title */
            'message' => sprintf(esc_html__('Title has been updated to %s', 'fluent-crm'), $newTitle)
        ];
    }

    public function updateLabels(Request $request, $funnel_id)
    {
        $funnel = Funnel::findOrFail($funnel_id);
        $action = $request->getSafe('action', 'sanitize_text_field');
        $labelIds = $request->get('label_ids');

        if (!is_array($labelIds)) {
            $labelIds = [$labelIds];
        }

        $labelIds = is_array($labelIds) ? array_map('intval', $labelIds) : [];
        $labelIds = array_unique(array_filter($labelIds));

        if ($action == 'sync') {
            $funnel->syncLabels($labelIds);
        } elseif ($action == 'attach') {
            $funnel->attachLabels($labelIds);
        } else {
            $funnel->detachLabels($labelIds);
        }

        return [
            'message' => __('Labels has been updated', 'fluent-crm')
        ];

    }


}

Youez - 2016 - github.com/yon3zu
LinuXploit