HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/ai.naniguide.com/app/Http/Controllers/SubscriberController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Model\EmailVerificationServer;
use App\Model\MailList;
use App\Model\Segment;
use App\Model\Subscriber;
use App\Model\JobMonitor;
use App\Model\Setting;
use App\Library\Facades\Hook;
use App\Services\SubscriberService;
use Carbon\Carbon;
use App\Jobs\MoveSubscribers;
use App\Jobs\MoveToBlacklist;

class SubscriberController extends Controller
{
    /**
     * Search items.
     */
    public function search($list, $request)
    {
        $sortOrder = $request->sort_order ? $request->sort_order : 'created_at';
        $sortDirection = $request->sort_direction ? $request->sort_direction : 'asc';

        $subscribers = $list->subscribers()
            ->search($request->keyword, $list->fields)
            ->filter($request);

        if ($sortOrder == 'tag') {
            // sort by tag
        } else {
            $subscribers = $subscribers->orderBy($sortOrder, $sortDirection);
        }

        return $subscribers;
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {
        $list = MailList::findByUid($request->list_uid);

        if (!$list) {
            return redirect()->action('SubscriberController@noList');
        }

        return view('subscribers.index', [
            'list' => $list,
            'columns' => $request->user()->getSetting('subscribers_columns') ?? ['created_at', 'updated_at'],
        ]);
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function listing(Request $request)
    {
        $list = MailList::findByUid($request->list_uid);

        // authorize
        if (\Gate::denies('read', $list)) {
            return;
        }

        $subscribers = $this->search($list, $request);
        // $total = distinctCount($subscribers);
        $total = $subscribers->count();
        $subscribers->with(['mailList']);
        $subscribers = $subscribers->paginate($request->per_page ? $request->per_page : 50);
        $fields = $list->getFields->whereIn('uid', $request->columns);

        return view('subscribers._list', [
            'subscribers' => $subscribers,
            'total' => $total,
            'list' => $list,
            'fields' => $fields,
        ]);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create(Request $request)
    {
        $list = MailList::findByUid($request->list_uid);
        $subscriber = new \App\Model\Subscriber();
        $subscriber->mail_list_id = $list->id;

        // authorize
        if (\Gate::denies('read', $list)) {
            return redirect()->action('LegacyMailListController@index');
        }

        // Get old post values
        $values = [];
        if (null !== $request->old()) {
            foreach ($request->old() as $key => $value) {
                if (is_array($value)) {
                    $values[str_replace('[]', '', $key)] = implode(',', $value);
                } else {
                    $values[$key] = $value;
                }
            }
        }

        return view('subscribers.create', [
            'list' => $list,
            'subscriber' => $subscriber,
            'values' => $values,
        ]);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $list = MailList::findByUid($request->list_uid);
        $customer = $request->user()->customer;
        $subscriberService = app(SubscriberService::class);

        if (!$request->user()->can('addMoreSubscribers', [ $list, $more = 1 ])) {
            return $this->noMoreItem();
        }

        // Plan state: customer-wide + per-list subscriber quota
        if (!\App\Services\Plans\Gates\QuotaGate::allows($customer, \App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS, amount: 1)) {
            return $this->noMoreItem();
        }
        if (!\App\Services\Plans\Gates\QuotaGate::allows($customer, \App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS_PER_LIST, list: $list, amount: 1)) {
            return $this->noMoreItem();
        }

        // Validate & and create subscriber
        // Throw ValidationError exception in case of failure
        [$validator, $subscriber] = $subscriberService->addByCustomer($list, $request, $customer);

        // @IMPORTANT: do not use $validator->fails() again,
        // if validation runs again, it is now TRUE! subscriber's email inserted => no longer unique
        if (is_null($subscriber)) {
            return back()->withInput()->withErrors($validator);
        }

        // Redirect to my lists page
        $request->session()->flash('alert-success', trans('messages.subscriber.created'));
        return redirect()->action('SubscriberController@index', $list->uid);
    }

    /**
     * Display the specified resource.
     *
     * @param int $id
     *
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param int $id
     *
     * @return \Illuminate\Http\Response
     */
    public function edit(Request $request)
    {
        $list = MailList::findByUid($request->list_uid);
        $subscriber = \App\Model\Subscriber::find($request->id);

        // authorize
        if (\Gate::denies('update', $subscriber)) {
            return $this->notAuthorized();
        }

        // Get old post values
        $values = [];
        foreach ($list->getFields as $key => $field) {
            $values[$field->tag] = $subscriber->getValueByField($field);
        }
        if (null !== $request->old()) {
            foreach ($request->old() as $key => $value) {
                if (is_array($value)) {
                    $values[str_replace('[]', '', $key)] = implode(',', $value);
                } else {
                    $values[$key] = $value;
                }
            }
        }

        return view('subscribers.edit', [
            'list' => $list,
            'subscriber' => $subscriber,
            'values' => $values,
        ]);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param \Illuminate\Http\Request $request
     * @param int                      $id
     *
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $customer = $request->user()->customer;
        $list = MailList::findByUid($request->list_uid);
        $subscriber = \App\Model\Subscriber::find($request->id);

        // authorize
        if (\Gate::denies('update', $subscriber)) {
            return $this->notAuthorized();
        }

        // validate and save posted data
        if ($request->isMethod('patch')) {
            $this->validate($request, $subscriber->getRules());

            // Upload
            if ($request->hasFile('image')) {
                if ($request->file('image')->isValid()) {
                    // Remove old images
                    $subscriber->uploadImage($request->file('image'));
                }
            }
            // Remove image
            if ($request->_remove_image == 'true') {
                $subscriber->removeImage();
            }

            // Update field
            $subscriber->updateFields($request->all());

            event(new \App\Events\SubscriptionUpdated($subscriber));
            event(new \App\Events\MailListUpdated($subscriber->mailList));

            // Redirect to my lists page
            $request->session()->flash('alert-success', trans('messages.subscriber.updated'));

            return redirect()->action('SubscriberController@index', $list->uid);
        }
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function delete(Request $request)
    {
        $user = $request->user();
        $list = MailList::findByUid($request->list_uid);

        // Select-all path: legacy controller-only — uses the request's
        // search filters to select the full visible page server-side.
        // The agent surface always passes an explicit uid list, so the
        // P11 service entry point covers only that case.
        if ($request->select_tool == 'all_items') {
            $subscribers = $this->search($list, $request);
            foreach ($subscribers->get() as $subscriber) {
                if (\Gate::allows('delete', $subscriber)) {
                    $subscriber->delete();
                }
            }
        } else {
            $ids = $request->ids;
            if (!is_array($ids)) {
                $ids = explode(',', $ids);
            }
            // Resolve uid list from id list (legacy form posts `subscribers.id`).
            $uids = Subscriber::whereIn('id', $ids)->pluck('uid')->all();
            app(SubscriberService::class)->deleteForUser($user, $list, $uids);
        }

        // Redirect to my lists page
        return response()->json([
            "status" => 'success',
            "message" => trans('messages.subscribers.deleted'),
        ]);
    }

    /**
     * Subscribe subscriber.
     *
     * @param int $id
     *
     * @return \Illuminate\Http\Response
     */
    public function subscribe(Request $request)
    {
        $list = MailList::findByUid($request->list_uid);
        $customer = $request->user()->customer;
        $ids = $request->ids;

        if (!is_array($request->ids)) {
            $ids = explode(',', $request->ids);
        }
        $subscribers = \App\Model\Subscriber::whereIn('id', $ids);
        $list = MailList::findByUid($request->list_uid);

        // Select all items
        if ($request->select_tool == 'all_items') {
            $subscribers = $this->search($list, $request);
        }

        foreach ($subscribers->get() as $subscriber) {
            // authorize
            if (\Gate::allows('subscribe', $subscriber)) {
                $subscriber->subscribe([
                    'message_id' => null,
                    'user_agent' => array_key_exists('HTTP_USER_AGENT', $_SERVER) ? $_SERVER['HTTP_USER_AGENT'] : '#unknown',
                ]);

                // Timeline record
                \App\Model\Timeline::recordSubscribedByCustomer($subscriber, $customer);
            }
        }

        // Redirect to my lists page
        echo trans('messages.subscribers.subscribed');
    }

    /**
     * Unsubscribe subscriber.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function unsubscribe(Request $request)
    {
        $list = MailList::findByUid($request->list_uid);
        $customer = $request->user()->customer;
        $ids = $request->ids;

        if (!is_array($request->ids)) {
            $ids = explode(',', $request->ids);
        }
        $subscribers = \App\Model\Subscriber::whereIn('id', $ids);
        $list = MailList::findByUid($request->list_uid);

        // Select all items
        if ($request->select_tool == 'all_items') {
            $subscribers = $this->search($list, $request);
        }

        foreach ($subscribers->get() as $subscriber) {
            // authorize
            if (\Gate::allows('unsubscribe', $subscriber)) {
                $subscriber->unsubscribe([
                    'message_id' => null,
                    'user_agent' => array_key_exists('HTTP_USER_AGENT', $_SERVER) ? $_SERVER['HTTP_USER_AGENT'] : '#unknown',
                ]);

                // Timeline record
                \App\Model\Timeline::recordUnsubscribedByCustomer($subscriber, $customer);
            }
        }

        // Redirect to my lists page
        echo trans('messages.subscribers.unsubscribed');
    }

    /**
     * Import from file.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function import(Request $request)
    {
        $customer = $request->user()->customer;
        $list = MailList::findByUid($request->list_uid);
        $currentJob = $list->importJobs()->first();

        // authorize
        if (\Gate::denies('import', $list)) {
            return $this->notAuthorized();
        }

        $importNotifications = Hook::collect('list_import_notifications');

        // GET, has a current job
        if ($currentJob) {
            return view('subscribers.import', [
                'list' => $list,
                'currentJobUid' => $currentJob->uid,
                'progressCheckUrl' => action('SubscriberController@importProgress', ['job_uid' => $currentJob->uid, 'list_uid' => $list->uid]),
                'cancelUrl' => action('SubscriberController@cancelImport', ['job_uid' => $currentJob->uid]),
                'logDownloadUrl' => action('SubscriberController@downloadImportLog', ['job_uid' => $currentJob->uid]),
                'importNotifications' => $importNotifications,
            ]);
            // GET, do not have any job
        } else {
            return view('subscribers.import', [
                'list' => $list,
                'importNotifications' => $importNotifications,
            ]);
        }
    }

    public function dispatchImportJob(Request $request)
    {
        // Get the list
        $list = MailList::findByUid($request->list_uid);

        // Upload to server
        // Example of outcome: /home/acelle/storage/app/tmp/import-000000.csv
        $filepath = $list->uploadCsv($request->file('file'));

        // Use the default way if there is no other plugin
        Hook::setIfEmpty('dispatch_list_import_job', function ($list, $filepath) use ($request) {
            return $list->dispatchImportJob($filepath, $map = $request->input('mapping'));
        });

        // Dispatch the import job
        $currentJob = Hook::perform('dispatch_list_import_job', [$list, $filepath]);

        // Return the job information
        return response()->json([
            'currentJobUid' => $currentJob->uid,
            'progressCheckUrl' => action('SubscriberController@importProgress', ['job_uid' => $currentJob->uid, 'list_uid' => $list->uid]),
            'cancelUrl' => action('SubscriberController@cancelImport', ['job_uid' => $currentJob->uid]),
            'logDownloadUrl' => action('SubscriberController@downloadImportLog', ['job_uid' => $currentJob->uid]),
        ]);
    }

    /**
     * Import from file.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function cancelImport(Request $request)
    {
        $job = JobMonitor::findByUid($request->job_uid);

        try {
            if ($job) {
                $job->cancel();
            }

            return response()->json(['status' => 'done']);
        } catch (\Exception $ex) {
            $job->delete(); // delete anyway if already done or failed, to make it simple to user
            return response()->json(['status' => '']);
        }
    }

    /**
     * Import from file.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function cancelExport(Request $request)
    {
        $job = JobMonitor::findByUid($request->job_uid);

        try {
            $job->cancel();
            return response()->json(['status' => 'done']);
        } catch (\Exception $ex) {
            $job->delete(); // delete anyway if already done or failed, to make it simple to user
            return response()->json(['status' => '']);
        }
    }

    public function downloadImportLog(Request $request)
    {
        $job = JobMonitor::findByUid($request->job_uid);

        // Only available if job has moved out of queued status
        return response()->download($job->getJsonData()['logfile']);
    }

    public function downloadExportedFile(Request $request)
    {
        $job = JobMonitor::findByUid($request->job_uid);

        // Only available if job has moved out of queued status
        return response()->download($job->getJsonData()['filepath']);
    }

    /**
     * Check import proccessing.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function importProgress(Request $request)
    {
        $list = MailList::findByUid($request->list_uid);
        $job = $list->importJobs()->first();

        $progress = $list->getImportProgress($job);

        return response()->json($progress->toArray());
    }

    /**
     * Export to csv.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function export(Request $request)
    {
        $list = MailList::findByUid($request->list_uid);

        // authorize
        if (\Gate::denies('export', $list)) {
            return $this->notAuthorized();
        }

        $currentJob = $list->exportJobs()->first();

        // GET, has a current job
        if ($currentJob) {
            return view('subscribers.export', [
                'list' => $list,
                'currentJobUid' => $currentJob->uid,
                'progressCheckUrl' => action('SubscriberController@exportProgress', ['job_uid' => $currentJob->uid]),
                'cancelUrl' => action('SubscriberController@cancelExport', ['job_uid' => $currentJob->uid]),
                'downloadUrl' => action('SubscriberController@downloadExportedFile', ['job_uid' => $currentJob->uid]),
            ]);
            // GET, do not have any job
        } else {
            return view('subscribers.export', [
                'list' => $list
            ]);
        }
    }

    public function dispatchExportJob(Request $request)
    {
        // Get the list
        $list = MailList::findByUid($request->list_uid);

        // Get segment if any
        $segmentUid = $request->input('segment_uid');
        $segment = (is_null($segmentUid)) ? null : Segment::findByUid($segmentUid);

        // Dispatch import job
        $currentJob = $list->dispatchExportJob($segment);

        // Return the job information
        return response()->json([
            'currentJobUid' => $currentJob->uid,
            'progressCheckUrl' => action('SubscriberController@exportProgress', ['job_uid' => $currentJob->uid]),
            'cancelUrl' => action('SubscriberController@cancelExport', ['job_uid' => $currentJob->uid]),
            'downloadUrl' => action('SubscriberController@downloadExportedFile', ['job_uid' => $currentJob->uid]),
        ]);
    }

    /**
     * Check export proccessing.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function exportProgress(Request $request)
    {
        $job = JobMonitor::findByUid($request->job_uid);

        // Get progress updated by the import process and status of the final job monitor
        $progress = $job->getJsonData();
        $progress['status'] = $job->status;
        $progress['error'] = $job->error;

        return response()->json($progress);
    }

    /**
     * Copy subscribers to lists.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function copy(Request $request)
    {
        $from_list = MailList::findByUid($request->from_uid);
        $to_list = MailList::findByUid($request->to_uid);

        $ids = $request->ids;

        if (!is_array($request->ids)) {
            $ids = explode(',', $request->ids);
        }
        $subscribers = \App\Model\Subscriber::whereIn('id', $ids);

        // Select all items
        if ($request->select_tool == 'all_items') {
            $subscribers = $this->search($from_list, $request);
        }

        foreach ($subscribers->get() as $subscriber) {
            // authorize
            if (\Gate::allows('update', $to_list)) {
                $subscriber->copy($to_list);
            }
        }

        // Trigger updating related campaigns cache
        event(new \App\Events\MailListUpdated($to_list));

        // Redirect to my lists page
        echo trans('messages.subscribers.copied');
    }

    /**
     * Move subscribers to lists.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function move(Request $request)
    {
        $from_list = MailList::findByUid($request->from_uid);
        $to_list = MailList::findByUid($request->to_uid);

        // Select all items
        if ($request->select_tool == 'all_items') {
            $subscribers = $from_list->subscribers()->search($request->keyword, $from_list->fields)->filter($request);
        } else {
            $ids = $request->ids;
            if (!is_array($request->ids)) {
                $ids = explode(',', $request->ids);
            } else {
                // what goes here?
            }

            $subscribers = \App\Model\Subscriber::whereIn('id', $ids);
        }

        $job = new MoveSubscribers($subscribers->get(), $from_list, $to_list);
        $queue = ACM_QUEUE_TYPE_DEFAULT;
        $job->onQueue($queue);

        safe_dispatch($job);

        // Redirect to my lists page
        echo trans('messages.subscribers.moved');
    }

    /**
     * Copy Move subscribers form.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function copyMoveForm(Request $request)
    {
        $from_list = MailList::findByUid($request->from_uid);

        $ids = $request->ids;

        if (!is_array($request->ids)) {
            $ids = explode(',', $request->ids);
        }
        $subscribers = \App\Model\Subscriber::whereIn('id', $ids);

        // Select all items
        if ($request->select_tool == 'all_items') {
            $subscribers = $this->search($from_list, $request);
        }

        return view('subscribers.copy_move_form', [
            'subscribers' => $subscribers,
            'from_list' => $from_list
        ]);
    }

    /**
     * Start the verification process
     *
     */
    public function startVerification(Request $request)
    {
        $subscriber = Subscriber::find($request->id);
        $server = EmailVerificationServer::findByUid($request->email_verification_server_id);
        try {
            $subscriber->verify($server);

            // success message
            $request->session()->flash('alert-success', trans('messages.verification.finish'));

            // update MailList cache
            event(new \App\Events\MailListUpdated($subscriber->mailList));

            if (request()->ajax()) {
                return response()->json([
                    'status' => 'success',
                    'message' => trans('messages.verification.finish'),
                ], 200);
            }

            return redirect()->action('SubscriberController@edit', ['list_uid' => $request->list_uid, 'id' => $subscriber->id]);
        } catch (\Exception $e) {
            if (request()->ajax()) {
                return response()->json([
                    'status' => 'error',
                    'message' => $e->getMessage(),
                ], 400);
            } else {
                return view('somethingWentWrong', ['message' => sprintf("Something went wrong while verifying %s (%s). Error message: %s", $subscriber->email, $subscriber->id, $e->getMessage())]);
            }
        }
    }

    /**
     * Reset the verification data
     *
     */
    public function resetVerification(Request $request)
    {
        $subscriber = Subscriber::find($request->id);

        try {
            $subscriber->resetVerification();
            // success message
            $request->session()->flash('alert-success', trans('messages.verification.reset'));

            // check if the request is from ajax
            if (request()->ajax()) {
                return response()->json([
                    'status' => 'success',
                    'message' => trans('messages.verification.reset'),
                ], 200);
            }

            return redirect()->action('SubscriberController@edit', ['list_uid' => $request->list_uid, 'id' => $subscriber->id]);
        } catch (\Exception $e) {
            if (request()->ajax()) {
                return response()->json([
                    'status' => 'error',
                    'message' => $e->getMessage(),
                ], 400);
            }

            return view('somethingWentWrong', ['message' => sprintf("Something went wrong while cleaning up verification data for %s (%s). Error message: %s", $subscriber->email, $subscriber->id, $e->getMessage())]);
        }
    }

    /**
     * Render customer image.
     */
    public function avatar(Request $request)
    {
        $subscriber = \App\Model\Subscriber::find($request->id);

        if ($subscriber && is_file($subscriber->getImagePath())) {
            $img = \Image::make($subscriber->getImagePath());
        } else {
            $img = \Image::make(public_path('images/subscriber-icon.jpg'));
        }

        return $img->response();
    }

    public function avatarOrigin(Request $request)
    {
        // Get current customer
        $subscriber = \App\Model\Subscriber::find($request->id);

        if (is_file($subscriber->getImageOriginPath())) {
            $img = \Image::make($subscriber->getImageOriginPath());
        } else {
            if (is_file($subscriber->getImagePath())) {
                $img = \Image::make($subscriber->getImagePath());
            } else {
                $img = \Image::make(public_path('images/subscriber-icon.jpg'));
            }
        }

        return $img->response();
    }

    /**
     * Resend confirmation email.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function resendConfirmationEmail(Request $request)
    {
        $ids = $request->ids;

        if (!is_array($request->ids)) {
            $ids = explode(',', $request->ids);
        }
        $subscribers = \App\Model\Subscriber::whereIn('id', $ids);
        $list = MailList::findByUid($request->list_uid);

        // Select all items
        if ($request->select_tool == 'all_items') {
            $subscribers = $this->search($list, $request);
        }

        $list = MailList::findByUid($request->list_uid);

        // Launch re-sending job
        $job = new \App\Jobs\SendConfirmationEmailJob($subscribers->get(), $list);
        $job->onQueue(ACM_QUEUE_TYPE_HIGH);
        dispatch($job);

        // Redirect to my lists page
        echo trans('messages.subscribers.resend_confirmation_email.being_sent');
    }

    /**
     * Update tags.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function updateTags(Request $request, $list_uid, $id)
    {
        $list = MailList::findByUid($list_uid);
        $subscriber = Subscriber::find($id);

        // authorize
        if (\Gate::denies('update', $subscriber)) {
            return $this->notAuthorized();
        }

        // saving
        if ($request->isMethod('post')) {
            $subscriber->updateTags($request->tags ?? []);

            return response()->json([
                'status' => 'success',
                'message' => trans('messages.subscriber.tagged', [
                    'subscriber' => $subscriber->getFullName(),
                ]),
            ], 201);
        }

        return view('subscribers.updateTags', [
            'list' => $list,
            'subscriber' => $subscriber,
        ]);
    }

    /**
     * Automation remove contact tag.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function removeTag(Request $request, $list_uid, $id)
    {
        $list = MailList::findByUid($list_uid);
        $subscriber = Subscriber::find($id);

        // authorize
        if (\Gate::denies('delete', $subscriber)) {
            return $this->notAuthorized();
        }

        $subscriber->removeTags([$request->tag]);

        return response()->json([
            'status' => 'success',
            'message' => trans('messages.automation.contact.tag.removed', [
                'tag' => $request->tag,
            ]),
        ], 201);
    }

    public function bulkDelete(Request $request)
    {
        // init
        $list = MailList::findByUid($request->list_uid);

        // validate and save posted data
        if ($request->isMethod('post')) {
            // make validator
            $validator = \Validator::make($request->all(), ['emails' => 'required']);

            // redirect if fails
            if ($validator->fails()) {
                return response()->view('subscribers.bulkDelete', [
                    'list' => $list,
                    'errors' => $validator->errors(),
                ], 400);
            }

            // get all emails
            $emails = array_unique(preg_split("/[\s,\r\n]+/", $request->emails));
            $subscribers = $list->subscribers()->whereIn('email', $emails)->get();

            //
            return view('subscribers.bulkDeleteConfirm', [
                'list' => $list,
                'emails' => $emails,
                'subscribers' => $subscribers,
            ]);
        }

        return view('subscribers.bulkDelete', [
            'list' => $list,
        ]);
    }

    public function import2Wizard(Request $request)
    {
        $list = \App\Model\MailList::findByUid($request->list_uid);

        // authorize
        if (\Gate::denies('import', $list)) {
            return $this->notAuthorized();
        }

        return view('subscribers.import2.upload', [
            'list' => $list,
        ]);
    }

    public function bulkDeleteConfirm(Request $request)
    {
        // init
        $list = MailList::findByUid($request->list_uid);

        // validate and save posted data
        if ($request->isMethod('post')) {
            // make validator
            $validator = \Validator::make($request->all(), ['emails' => 'required']);

            // redirect if fails
            if ($validator->fails()) {
                return response()->view('subscribers.bulkDelete', [
                    'list' => $list,
                    'errors' => $validator->errors(),
                ], 400);
            }

            // get all emails
            $emails = preg_split("/[\s,\r\n]+/", $request->emails);

            //
            return view('subscribers.bulkDeleteConfirm', [
                'list' => $list,
                'emails' => $emails,
            ]);
        }

        return view('subscribers.bulkDelete', [
            'list' => $list,
        ]);
    }

    public function import2(Request $request)
    {
        $list = \App\Model\MailList::findByUid($request->list_uid);
        $currentJob = $list->importJobs()->first();

        // authorize
        if (\Gate::denies('import', $list)) {
            return $this->notAuthorized();
        }

        return view('subscribers.import2', [
            'list' => $list,
            'currentJob' => $currentJob,
        ]);
    }

    public function import2Upload(Request $request)
    {
        $list = \App\Model\MailList::findByUid($request->list_uid);

        // authorize
        if (\Gate::denies('import', $list)) {
            return $this->notAuthorized();
        }

        $uploaded = $request->file('file');
        if (!$uploaded) {
            return response()->json([
                'status'  => 'error',
                'message' => 'No file uploaded',
            ], 400);
        }

        // Allowlist by extension — MIME-based validation is unreliable here
        // (browsers often send application/octet-stream for CSV/XLSX uploads).
        // DriverRegistry is the single source of truth for supported extensions.
        $ext = strtolower($uploaded->getClientOriginalExtension());
        if (!\App\Library\Import\DriverRegistry::has($ext)) {
            return response()->json([
                'status'  => 'error',
                'message' => 'Unsupported file extension: .'.$ext.'. Allowed: '
                    .implode(', ', array_map(fn ($e) => '.'.$e, array_keys(\App\Library\Import\DriverRegistry::all()))),
            ], 400);
        }

        $filepath = $list->uploadCsv($uploaded);

        // Redirect to my lists page
        return response()->json([
            'status' => 'success',
            'message' => trans('messages.subscriber.import.csv_uploaded'),
            'mappingUrl' => action('SubscriberController@import2Mapping', [
                'list_uid' => $list->uid,
                'filepath' => $filepath,
            ]),
        ]);
    }

    public function import2Mapping(Request $request)
    {
        $list = \App\Model\MailList::findByUid($request->list_uid);
        $filepath = $request->filepath;

        // authorize
        if (\Gate::denies('import', $list)) {
            return $this->notAuthorized();
        }

        try {
            list($headers, $total, $results) = \App\Library\Import\DriverRegistry::for($filepath)->read($filepath);
        } catch (\Throwable $e) {
            return response()->json([
                'message' => $e->getMessage(),
            ], 404);
        }

        return view('subscribers.import2.mapping', [
            'list' => $list,
            'headers' => $headers,
            'filepath' => $filepath,
        ]);
    }

    public function import2Validate(Request $request)
    {
        $list = \App\Model\MailList::findByUid($request->list_uid);

        try {
            \App\Library\MailListFieldMapping::parse($request->mapping, $list);
            return response()->json([
                'message' => 'success',
            ], 200);
        } catch (\Throwable $e) {
            return response()->json([
                'message' => $e->getMessage(),
            ], 401);
        }
    }

    /**
     * Wizard step 3 — Duplicate-handling settings.
     *
     * Detects whether the uploaded CSV has `status` / `tags` columns (case-insensitive)
     * so the view can disable the corresponding sub-checkbox when the column is absent.
     */
    public function import2Settings(Request $request)
    {
        $list = \App\Model\MailList::findByUid($request->list_uid);

        if (\Gate::denies('import', $list)) {
            return $this->notAuthorized();
        }

        $filepath = $request->filepath;

        try {
            list($headers, $total, $results) = \App\Library\Import\DriverRegistry::for($filepath)->read($filepath);
        } catch (\Throwable $e) {
            return response()->json([
                'message' => $e->getMessage(),
            ], 404);
        }

        return view('refactor.pages.lists.import.settings', [
            'list'             => $list,
            'filepath'         => $filepath,
            'hasStatusColumn'  => \App\Library\MailListFieldMapping::hasPreservedFieldColumn($headers, 'status'),
            'hasTagsColumn'    => \App\Library\MailListFieldMapping::hasPreservedFieldColumn($headers, 'tags'),
        ]);
    }

    /**
     * Import mapping fields.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function import2Run(Request $request)
    {
        $list = \App\Model\MailList::findByUid($request->list_uid);

        // authorize — JSON 403 (this endpoint is XHR-only from the wizard).
        if (\Gate::denies('import', $list)) {
            return response()->json([
                'message' => trans('messages.not_authorized_message'),
            ], 403);
        }

        // Duplicate-handling settings (wizard step 3). Validate explicitly — fall-through
        // on a bogus mode would silently take the wrong branch downstream.
        $mode = $request->input('mode', 'overwrite');
        if (!in_array($mode, ['overwrite', 'skip'], true)) {
            return response()->json([
                'message' => 'Invalid duplicate-handling mode: ' . $mode,
            ], 422);
        }

        $options = [
            'mode'            => $mode,
            'overwriteStatus' => $request->boolean('overwrite_status'),
            'overwriteTags'   => $request->boolean('overwrite_tags'),
        ];

        $job = $list->dispatchImportJob($request->filepath, $request->mapping, $options);

        return response()->json([
            // 'list_uid' => $list->uid,
            'job_uid' => $job->uid,
            'progressUrl' => action('SubscriberController@import2Progress', [
                'list_uid' => $list->uid,
                'job_uid' => $job->uid,
            ]),
            // 'progressCheckUrl' => action('SubscriberController@importProgress', [
            //     'job_uid' => $job->uid,
            //     'list_uid' => $list->uid
            // ]),
            // 'cancelUrl' => action('SubscriberController@cancelImport', [
            //     'job_uid' => $job->uid
            // ]),
            // 'logDownloadUrl' => action('SubscriberController@downloadImportLog', [
            //     'job_uid' => $job->uid
            // ])
        ]);
    }

    public function import2Progress(Request $request)
    {
        $list = \App\Model\MailList::findByUid($request->list_uid);
        $customer = \Auth::user()->customer;

        // authorize
        if (\Gate::denies('import', $list)) {
            return $this->notAuthorized();
        }

        // If the mail list dropdown is not populated
        // Then it is very likely a problem with the cronjob
        // Show this warning to user
        $lastExecutedTimeUtc = Carbon::createFromTimestamp(Setting::get('cronjob_last_execution') ?? 0);
        $now = Carbon::now();
        $lastExecutedTimeDiffInMinutes = $now->diffInMinutes($lastExecutedTimeUtc, $abs = true);
        $threshold = 20; // 15 minutes

        if ($lastExecutedTimeDiffInMinutes > $threshold) {
            $lastExecutedTime = $lastExecutedTimeUtc->timezone($customer->getTimezone());
            // If there is no data populated, then it is very likely that cronjob has not been set up correctly
            // Pass the last executed time to the view to show up with the warning
            $cronjobWarning = $lastExecutedTime;
        } else {
            $cronjobWarning = null;
        }

        return view('subscribers.import2.progress', [
            'list' => $list,
            'job_uid' => $request->job_uid,
            'cronjobWarning' => $cronjobWarning,
        ]);
    }

    public function import2ProgressContent(Request $request)
    {
        $list = \App\Model\MailList::findByUid($request->list_uid);
        $currentJob = $list->importJobs()->first();
        $progress = $list->getImportProgress($currentJob);

        // authorize
        if (\Gate::denies('import', $list)) {
            return $this->notAuthorized();
        }

        return view('subscribers.import2.progressContent', [
            'list' => $list,
            'currentJob' => $currentJob,
            'progress' => $progress->toArray(),
        ]);
    }

    public function assignValues(Request $request, $list_uid)
    {
        // init
        $list = MailList::findByUid($request->list_uid);

        $ids = $request->ids;

        if (!is_array($request->ids)) {
            $ids = explode(',', $request->ids);
        }
        $subscribers = \App\Model\Subscriber::whereIn('id', $ids);
        $list = MailList::findByUid($request->list_uid);

        // Select all items
        if ($request->select_tool == 'all_items') {
            $subscribers = $this->search($list, $request);
        }

        // validate and save posted data
        if ($request->isMethod('post')) {
            $validator = \App\Model\Subscriber::assginValues($subscribers, $request);

            // redirect if fails
            if ($validator->fails()) {
                return response()->view('subscribers.assignValues', [
                    'list' => $list,
                    'subscribers' => $subscribers,
                    'errors' => $validator->errors(),
                ], 400);
            }

            return response()->json([
                'status' => 'success',
                'message' => trans('messages.subscribers.values_assigned'),
            ]);
        }

        return view('subscribers.assignValues', [
            'list' => $list,
            'subscribers' => $subscribers,
        ]);
    }

    public function noList()
    {
        return view('subscribers.noList');
    }

    public function addTag(Request $request, $list_uid)
    {
        // init
        $list = MailList::findByUid($request->list_uid);
        $ids = $request->ids;

        if (!is_array($request->ids)) {
            $ids = explode(',', $request->ids);
        }
        $subscribers = \App\Model\Subscriber::whereIn('id', $ids);
        $list = MailList::findByUid($request->list_uid);

        // Select all items
        if ($request->select_tool == 'all_items') {
            $subscribers = $this->search($list, $request);
        }

        // validate and save posted data
        if ($request->isMethod('post')) {
            $validator = \App\Model\Subscriber::addSubscribersTags($subscribers, $request->tags);

            // redirect if fails
            if ($validator->fails()) {
                return response()->view('subscribers.addTag', [
                    'list' => $list,
                    'subscribers' => $subscribers,
                    'errors' => $validator->errors(),
                ], 400);
            }

            return response()->json([
                'status' => 'success',
                'message' => trans('messages.subscribers.tag_added'),
            ]);
        }

        return view('subscribers.addTag', [
            'list' => $list,
            'subscribers' => $subscribers,
        ]);
    }

    public function timeline(Request $request)
    {
        $subscriber = \App\Model\Subscriber::find($request->id);

        return view('subscribers.timeline', [
            'list' => $subscriber->mailList,
            'subscriber' => $subscriber,
        ]);
    }

    public function timelineList(Request $request)
    {
        $subscriber = \App\Model\Subscriber::find($request->id);

        $timelines = $subscriber->timelines();

        // filter by type
        if ($request->type) {
            if ($request->type == 'automation') {
                $timelines = $timelines->whereNotNull('activity');
            } else {
                $timelines = $timelines->whereType($request->type);
            }
        }

        $timelines = $timelines->orderBy('created_at', 'desc')
            ->paginate($request->per_page ? $request->per_page : 10);

        return view('subscribers.timelineList', [
            'timelines' => $timelines,
        ]);
    }

    public function saveColumnsSettings(Request $request)
    {
        $request->user()->writeSetting('subscribers_columns', $request->columns);
    }

    public function bulkRemoveTag(Request $request, $list_uid)
    {
        // init
        $list = MailList::findByUid($request->list_uid);
        $ids = $request->ids;

        if (!is_array($request->ids)) {
            $ids = explode(',', $request->ids);
        }
        $subscribers = \App\Model\Subscriber::whereIn('id', $ids);
        $list = MailList::findByUid($request->list_uid);

        // Select all items
        if ($request->select_tool == 'all_items') {
            $subscribers = $this->search($list, $request);
        }

        // validate and save posted data
        if ($request->isMethod('post')) {
            $validator = \App\Model\Subscriber::removeSubscribersTags($subscribers, $request->tags);

            // redirect if fails
            if ($validator->fails()) {
                return response()->view('subscribers.bulkRemoveTag', [
                    'list' => $list,
                    'subscribers' => $subscribers,
                    'errors' => $validator->errors(),
                ], 400);
            }

            return response()->json([
                'status' => 'success',
                'message' => trans('messages.subscribers.tag_removed'),
            ]);
        }

        return view('subscribers.bulkRemoveTag', [
            'list' => $list,
            'subscribers' => $subscribers,
        ]);
    }

    public function moveToBlacklist(Request $request)
    {
        $list = MailList::findByUid($request->list_uid);
        $customer = $request->user()->customer;
        $ids = $request->ids;

        if (!is_array($request->ids)) {
            $ids = explode(',', $request->ids);
        }
        $subscribers = \App\Model\Subscriber::whereIn('id', $ids);
        $list = MailList::findByUid($request->list_uid);

        // Select all items
        if ($request->select_tool == 'all_items') {
            $subscribers = $this->search($list, $request);
        }

        // Trick, limit to 100 to prevent memory issue
        $subscribers = $subscribers->limit(100);

        $job = new moveToBlacklist($subscribers->get());
        $queue = ACM_QUEUE_TYPE_DEFAULT;
        $job->onQueue($queue);

        safe_dispatch($job);

        // Redirect to my lists page
        echo trans('messages.subscribers.moved_to_blacklist');
    }
}