shell bypass 403
<?php namespace App\Http\Controllers\Customer; use App\Exceptions\GeneralException; use App\Http\Requests\Contacts\ImportContact; use App\Http\Requests\Contacts\NewContactGroup; use App\Http\Requests\Contacts\StoreContact; use App\Http\Requests\Contacts\UpdateContact; use App\Http\Requests\Contacts\UpdateContactGroup; use App\Http\Requests\Contacts\UpdateContactGroupMessage; use App\Jobs\ImportContacts; use App\Jobs\ReplicateContacts; use App\Library\Tool; use App\Models\ContactGroups; use App\Models\ContactGroupsOptinKeywords; use App\Models\ContactGroupsOptoutKeywords; use App\Models\Contacts; use App\Models\ContactsCustomField; use App\Models\CsvData; use App\Models\ImportJobHistory; use App\Models\Keywords; use App\Models\PhoneNumbers; use App\Models\PlansCoverageCountries; use App\Models\PlansSendingServer; use App\Models\Senderid; use App\Models\SendingServer; use App\Models\TemplateTags; use App\Models\User; use App\Repositories\Contracts\ContactsRepository; use App\Rules\Phone; use Arcanedev\NoCaptcha\Rules\CaptchaRule; use Box\Spout\Common\Exception\InvalidArgumentException; use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Exception\UnsupportedTypeException; use Box\Spout\Writer\Exception\WriterNotOpenedException; use Generator; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Bus\Batch; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\View; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Validator; use JetBrains\PhpStorm\NoReturn; use Maatwebsite\Excel\Facades\Excel; use Rap2hpoutre\FastExcel\FastExcel; use stdClass; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Throwable; class ContactsController extends CustomerBaseController { /** * @var ContactsRepository $contactGroups */ protected ContactsRepository $contactGroups; public function __construct(ContactsRepository $contactGroups) { $this->contactGroups = $contactGroups; } /** * view all contact list * * @return Application|Factory|View * @throws AuthorizationException */ public function index(): View|Factory|Application { $this->authorize('view_contact_group'); $breadcrumbs = [ ['link' => url('dashboard'), 'name' => __('locale.menu.Dashboard')], ['link' => url('dashboard'), 'name' => __('locale.menu.Contacts')], ['name' => __('locale.contacts.contact_groups')], ]; return view('customer.contactGroups.index', compact('breadcrumbs')); } /** * search contact groups with given data * * @param Request $request * * @throws AuthorizationException */ #[NoReturn] public function search(Request $request): void { $this->authorize('view_contact_group'); $columns = [ 0 => 'responsive_id', 1 => 'uid', 2 => 'uid', 3 => 'name', 4 => 'contacts', 5 => 'created_at', 6 => 'status', 7 => 'action', ]; $totalData = ContactGroups::where('customer_id', auth()->user()->id)->count(); $totalFiltered = $totalData; $limit = $request->input('length'); $start = $request->input('start'); $order = $columns[$request->input('order.0.column')]; $dir = $request->input('order.0.dir'); if (empty($request->input('search.value'))) { $contact_groups = ContactGroups::where('customer_id', auth()->user()->id)->offset($start) ->limit($limit) ->orderBy($order, $dir) ->get(); } else { $search = $request->input('search.value'); $contact_groups = ContactGroups::where('customer_id', auth()->user()->id)->whereLike(['uid', 'name', 'status', 'created_at'], $search) ->offset($start) ->limit($limit) ->orderBy($order, $dir) ->get(); $totalFiltered = ContactGroups::where('customer_id', auth()->user()->id)->whereLike(['uid', 'name', 'status', 'created_at'], $search)->count(); } $data = []; if ( ! empty($contact_groups)) { foreach ($contact_groups as $group) { if ($group->status === true) { $status = 'checked'; } else { $status = ''; } $nestedData['responsive_id'] = ''; $nestedData['uid'] = $group->uid; $nestedData['name'] = $group->name; $nestedData['contacts'] = Tool::number_with_delimiter($group->subscribersCount($group->cache)); $nestedData['created_at'] = Tool::formatHumanTime($group->created_at); $nestedData['status'] = "<div class='form-check form-switch form-check-primary'> <input type='checkbox' class='form-check-input get_status' id='status_$group->uid' data-id='$group->uid' name='status' $status> <label class='form-check-label' for='status_$group->uid'> <span class='switch-icon-left'><i data-feather='check'></i> </span> <span class='switch-icon-right'><i data-feather='x'></i> </span> </label> </div>"; $nestedData['show'] = route('customer.contacts.show', $group->uid); $nestedData['show_label'] = __('locale.buttons.edit'); $nestedData['new_contact'] = route('customer.contact.create', $group->uid); $nestedData['new_contact_label'] = __('locale.contacts.new_contact'); $nestedData['copy'] = __('locale.buttons.copy'); $nestedData['delete'] = __('locale.buttons.delete'); $nestedData['can_create'] = Auth::user()->can('create_contact'); $nestedData['can_update'] = Auth::user()->can('update_contact_group'); $nestedData['can_delete'] = Auth::user()->can('delete_contact_group'); $data[] = $nestedData; } } $json_data = [ "draw" => intval($request->input('draw')), "recordsTotal" => intval($totalData), "recordsFiltered" => intval($totalFiltered), "data" => $data, ]; echo json_encode($json_data); exit(); } /** * create new contact group * * @return Application|Factory|View|RedirectResponse * @throws AuthorizationException */ public function create(): View|Factory|RedirectResponse|Application { if (! Auth::user()->customer->activeSubscription() ){ return redirect()->route('customer.subscriptions.index')->with([ 'status' => 'error', 'message' => __('locale.customer.no_active_subscription'), ]); } $this->authorize('create_contact_group'); $totalData = ContactGroups::where('customer_id', auth()->user()->id)->count(); $list_max = Auth::user()->customer->getOption('list_max'); if ($list_max != '-1' && $list_max < $totalData) { return redirect()->route('customer.contacts.index')->with([ 'status' => 'error', 'message' => __('locale.contacts.max_list_quota', ['max_list' => $list_max]), ]); } $breadcrumbs = [ ['link' => url('dashboard'), 'name' => __('locale.menu.Dashboard')], ['link' => url('contacts'), 'name' => __('locale.menu.Contacts')], ['name' => __('locale.contacts.new_contact_group')], ]; return view('customer.contactGroups.create', compact('breadcrumbs')); } /** * store contact group * * @param NewContactGroup $request * * @return RedirectResponse */ public function store(NewContactGroup $request): RedirectResponse { if (config('app.stage') == 'demo') { return redirect()->route('customer.contacts.index')->with([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $group = $this->contactGroups->store($request->input()); return redirect()->route('customer.contacts.show', $group->uid)->with([ 'status' => 'success', 'message' => __('locale.contacts.contact_group_successfully_added'), ]); } /** * View contact group for edit * * @param ContactGroups $contact * * @return Application|Factory|View * * @throws AuthorizationException */ public function show(ContactGroups $contact): View|Factory|Application { $this->authorize('view_contact_group'); $breadcrumbs = [ ['link' => url('dashboard'), 'name' => __('locale.menu.Dashboard')], ['link' => url('contacts'), 'name' => __('locale.menu.Contacts')], ['name' => $contact->name], ]; if (Auth::user()->customer->getOption('sender_id_verification') == 'yes') { $sender_ids = Senderid::where('user_id', auth()->user()->id)->cursor(); $phone_numbers = PhoneNumbers::where('user_id', auth()->user()->id)->cursor(); } else { $sender_ids = null; $phone_numbers = null; } $template_tags = TemplateTags::cursor(); $contact_groups = ContactGroups::where('status', 1)->where('uid', '!=', $contact->uid)->select('uid', 'name')->cursor(); $opt_in_keywords = ContactGroupsOptinKeywords::where('contact_group', $contact->id)->cursor(); $existing_opt_in = array_column($opt_in_keywords->toArray(), 'keyword'); $remain_opt_in_keywords = Keywords::where('user_id', $contact->customer_id)->where('status', 'assigned')->whereNotIn('keyword_name', $existing_opt_in)->select('keyword_name')->cursor(); $opt_out_keywords = ContactGroupsOptoutKeywords::where('contact_group', $contact->id)->cursor(); $existing_opt_out = array_column($opt_out_keywords->toArray(), 'keyword'); $remain_opt_out_keywords = Keywords::where('user_id', $contact->customer_id)->where('status', 'assigned')->whereNotIn('keyword_name', $existing_opt_out)->select('keyword_name')->cursor(); $import_history = ImportJobHistory::where('import_id', $contact->uid)->where('type', 'import_contact')->cursor(); $plan_id = Auth::user()->customer->activeSubscription()->plan_id; // Check the customer has permissions using sending servers and has his own sending servers if (Auth::user()->customer->getOption('create_sending_server') == 'yes') { if (PlansSendingServer::where('plan_id', $plan_id)->count()) { $sending_server = SendingServer::where('user_id', Auth::user()->id)->where('plain', 1)->where('status', true)->get(); if ($sending_server->count() == 0) { $sending_server_ids = PlansSendingServer::where('plan_id', $plan_id)->pluck('sending_server_id')->toArray(); $sending_server = SendingServer::where('plain', 1)->where('status', true)->whereIn('id', $sending_server_ids)->get(); } } else { $sending_server_ids = PlansSendingServer::where('plan_id', $plan_id)->pluck('sending_server_id')->toArray(); $sending_server = SendingServer::where('plain', 1)->where('status', true)->whereIn('id', $sending_server_ids)->get(); } } else { // If customer don't have permission creating sending servers $sending_server_ids = PlansSendingServer::where('plan_id', $plan_id)->pluck('sending_server_id')->toArray(); $sending_server = SendingServer::where('plain', 1)->where('status', true)->whereIn('id', $sending_server_ids)->get(); } return view('customer.contactGroups.show', compact('breadcrumbs', 'contact', 'sender_ids', 'phone_numbers', 'contact_groups', 'template_tags', 'opt_in_keywords', 'opt_out_keywords', 'remain_opt_in_keywords', 'remain_opt_out_keywords', 'import_history', 'sending_server')); } /** * change contact group status * * @param ContactGroups $contact * * @return JsonResponse * * @throws AuthorizationException * @throws GeneralException */ public function activeToggle(ContactGroups $contact): JsonResponse { if (config('app.stage') == 'demo') { return response()->json([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } try { $this->authorize('update_contact_group'); if ($contact->update(['status' => ! $contact->status])) { return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contact_group_successfully_change'), ]); } throw new GeneralException(__('locale.exceptions.something_went_wrong')); } catch (ModelNotFoundException $exception) { return response()->json([ 'status' => 'error', 'message' => $exception->getMessage(), ]); } } /** * copy contact groups * * @param ContactGroups $contact * @param Request $request * * @return JsonResponse * @throws AuthorizationException */ public function copy(ContactGroups $contact, Request $request): JsonResponse { if (config('app.stage') == 'demo') { return response()->json([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $this->authorize('create_contact_group'); $totalData = ContactGroups::where('customer_id', auth()->user()->id)->count(); $list_max = Auth::user()->customer->getOption('list_max'); if ($list_max != '-1' && $list_max < $totalData) { return response()->json([ 'status' => 'error', 'message' => __('locale.contacts.max_list_quota', ['max_list' => $list_max]), ]); } $subscriber_per_list_max = Contacts::where('group_id', $contact->id)->count(); if (Auth::user()->customer->getOption('subscriber_per_list_max') != '-1' && $subscriber_per_list_max > Auth::user()->customer->getOption('subscriber_per_list_max')) { $subscriber_max = Contacts::where('customer_id', Auth::user()->id)->count(); if (Auth::user()->customer->getOption('subscriber_max') != '-1' && $subscriber_max > Auth::user()->customer->getOption('subscriber_max')) { return response()->json([ 'status' => 'error', 'message' => __('locale.contacts.subscriber_max_quota', ['subscriber_max' => Auth::user()->customer->getOption('subscriber_max')]), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.contacts.subscriber_per_list_max_quota', ['subscriber_per_list_max' => Auth::user()->customer->getOption('subscriber_per_list_max')]), ]); } $new_group = $contact->replicate(); $new_group->name = $request->group_name; $new_group->save(); $new_group_id = $new_group->id; $opt_in_keyword = ContactGroupsOptinKeywords::where('contact_group', $contact->id)->get(); foreach ($opt_in_keyword as $keyword) { $new_keyword = $keyword->replicate(); $new_keyword->uid = uniqid(); $new_keyword->contact_group = $new_group_id; $new_keyword->save(); } $opt_out_keyword = ContactGroupsOptoutKeywords::where('contact_group', $contact->id)->get(); foreach ($opt_out_keyword as $keyword) { $new_keyword = $keyword->replicate(); $new_keyword->uid = uniqid(); $new_keyword->contact_group = $new_group_id; $new_keyword->save(); } $count = Contacts::where('group_id', $contact->id)->count(); if ($count > 2000) { $batch_list = []; Tool::resetMaxExecutionTime(); Contacts::where('group_id', $contact->id)->cursor() ->chunk(5000) ->each(function ($lines) use ($new_group_id, &$batch_list) { $batch_list[] = new ReplicateContacts($new_group_id, $lines); }); try { $import_name = 'CopyContacts_'.date('Ymdhms'); $import_job = ImportJobHistory::create([ 'name' => $import_name, 'import_id' => $new_group->uid, 'type' => 'import_contact', 'status' => 'processing', 'options' => json_encode(['status' => 'processing', 'message' => 'Import contacts are running']), 'batch_id' => null, ]); $batch = Bus::batch($batch_list) ->then(function (Batch $batch) use ($new_group, $import_name, $import_job) { $new_group->update(['batch_id' => $batch->id]); $import_job->update(['batch_id' => $batch->id]); }) ->catch(function (Batch $batch, Throwable $e) { $import_history = ImportJobHistory::where('batch_id', $batch->id)->first(); if ($import_history) { $import_history->status = 'failed'; $import_history->options = json_encode(['status' => 'failed', 'message' => $e->getMessage()]); $import_history->save(); } }) ->finally(function (Batch $batch) { $import_history = ImportJobHistory::where('batch_id', $batch->id)->first(); if ($import_history) { $import_history->status = 'finished'; $import_history->options = json_encode(['status' => 'finished', 'message' => 'Import contacts was successfully imported.']); $import_history->save(); } }) ->name($import_name) ->dispatch(); $new_group->update(['batch_id' => $batch->id]); return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contact_successfully_imported_in_background'), ]); } catch (Throwable $e) { return response()->json([ 'status' => 'error', 'message' => $e->getMessage(), ]); } } else { Contacts::where('group_id', $contact->id)->cursor()->chunk('250')->each(function ($lines) use ($new_group_id) { foreach ($lines as $line) { $new_contact = $line->replicate(); $new_contact->uid = uniqid(); $new_contact->group_id = $new_group_id; $new_contact->created_at = now()->toDateTimeString(); $new_contact->updated_at = now()->toDateTimeString(); $new_contact->save(); } }); return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contact_group_successfully_copied'), ]); } } /** * update contact group settings * * @param ContactGroups $contact * @param UpdateContactGroup $request * * @return RedirectResponse */ public function update(ContactGroups $contact, UpdateContactGroup $request): RedirectResponse { if (config('app.stage') == 'demo') { return redirect()->route('customer.contacts.index')->with([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $group = $this->contactGroups->update($contact, $request->except('_method', '_token')); return redirect()->route('customer.contacts.show', $group->uid)->withInput(['tab' => 'settings'])->with([ 'status' => 'success', 'message' => __('locale.contacts.contact_group_successfully_updated'), ]); } /** * delete contact group * * @param ContactGroups $contact * * @return JsonResponse * @throws AuthorizationException */ public function destroy(ContactGroups $contact): JsonResponse { if (config('app.stage') == 'demo') { return response()->json([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $this->authorize('delete_contact_group'); $this->contactGroups->destroy($contact); return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contact_group_successfully_deleted'), ]); } /** * Bulk Action with Enable, Disable and Delete * * @param Request $request * * @return JsonResponse * @throws AuthorizationException */ public function batchAction(Request $request): JsonResponse { if (config('app.stage') == 'demo') { return response()->json([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $action = $request->get('action'); $ids = $request->get('ids'); switch ($action) { case 'destroy': $this->authorize('delete_contact_group'); $this->contactGroups->batchDestroy($ids); return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contact_groups_deleted'), ]); case 'enable': $this->authorize('update_contact_group'); $this->contactGroups->batchActive($ids); return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contact_groups_enabled'), ]); case 'disable': $this->authorize('update_contact_group'); $this->contactGroups->batchDisable($ids); return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contact_groups_disabled'), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.exceptions.invalid_action'), ]); } /** * search contact with given data * * @param ContactGroups $contact * @param Request $request * * @throws AuthorizationException */ #[NoReturn] public function searchContact(ContactGroups $contact, Request $request): void { $this->authorize('view_contact'); $columns = [ 0 => 'responsive_id', 1 => 'uid', 2 => 'uid', 3 => 'phone', 4 => 'name', 5 => 'created_at', 6 => 'status', 7 => 'action', ]; $totalData = Contacts::where('customer_id', auth()->user()->id)->where('group_id', $contact->id)->count(); $totalFiltered = $totalData; $limit = $request->input('length'); $start = $request->input('start'); $order = $columns[$request->input('order.0.column')]; $dir = $request->input('order.0.dir'); if (empty($request->input('search.value'))) { $contacts = Contacts::where('customer_id', auth()->user()->id)->where('group_id', $contact->id)->offset($start) ->limit($limit) ->orderBy($order, $dir) ->get(); } else { $search = $request->input('search.value'); $contacts = Contacts::where('customer_id', auth()->user()->id)->where('group_id', $contact->id)->whereLike(['uid', 'phone', 'first_name', 'last_name', 'status'], $search) ->offset($start) ->limit($limit) ->orderBy($order, $dir) ->get(); $totalFiltered = Contacts::where('customer_id', auth()->user()->id)->where('group_id', $contact->id)->whereLike(['uid', 'phone', 'first_name', 'last_name', 'status'], $search)->count(); } $data = []; if ( ! empty($contacts)) { foreach ($contacts as $singleContact) { if ($singleContact->status == 'subscribe') { $status = 'checked'; } else { $status = ''; } $display_name = $singleContact->display_name(); if ($display_name == ' ') { $display_name = __('locale.labels.no_name'); } $nestedData['responsive_id'] = ''; $nestedData['uid'] = $singleContact->uid; $nestedData['phone'] = $singleContact->phone; $nestedData['name'] = $display_name; $nestedData['created_at'] = Tool::formatHumanTime($singleContact->created_at); $nestedData['status'] = "<div class='form-check form-switch form-check-primary form-switch-xl'> <input type='checkbox' class='form-check-input get_status' id='status_$singleContact->uid' data-id='$singleContact->uid' name='status' $status> <label class='form-check-label' for='status_$singleContact->uid'> <span class='switch-text-left'>".__('locale.labels.subscribe')."</span> <span class='switch-text-right'>".__('locale.labels.unsubscribe')."</span> </label> </div>"; $nestedData['show'] = route('customer.contact.edit', ['contact' => $contact->uid, 'contact_id' => $singleContact->uid]); $nestedData['show_label'] = __('locale.buttons.edit'); $nestedData['conversion'] = route('customer.reports.all', ['recipient' => $singleContact->phone]); $nestedData['conversion_label'] = __('locale.contacts.view_conversion'); $nestedData['send_sms'] = route('customer.sms.quick_send', ['recipient' => $singleContact->phone]); $nestedData['send_sms_label'] = __('locale.contacts.send_message'); $nestedData['delete'] = __('locale.buttons.delete'); $data[] = $nestedData; } } $json_data = [ "draw" => intval($request->input('draw')), "recordsTotal" => intval($totalData), "recordsFiltered" => intval($totalFiltered), "data" => $data, ]; echo json_encode($json_data); exit(); } /** * add new contact in a group * * @param ContactGroups $contact * * @return Application|Factory|View|RedirectResponse * @throws AuthorizationException */ public function createContact(ContactGroups $contact): View|Factory|RedirectResponse|Application { $this->authorize('create_contact'); $subscriber_per_list_max = Contacts::where('group_id', $contact->id)->count(); if (Auth::user()->customer->getOption('subscriber_per_list_max') != '-1' && $subscriber_per_list_max > Auth::user()->customer->getOption('subscriber_per_list_max')) { $subscriber_max = Contacts::where('customer_id', Auth::user()->id)->count(); if (Auth::user()->customer->getOption('subscriber_max') != '-1' && $subscriber_max > Auth::user()->customer->getOption('subscriber_max')) { return redirect()->route('customer.contacts.show', $contact->uid)->with([ 'status' => 'error', 'message' => __('locale.contacts.subscriber_max_quota', ['subscriber_max' => Auth::user()->customer->getOption('subscriber_max')]), ]); } return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'contact'])->with([ 'status' => 'error', 'message' => __('locale.contacts.subscriber_per_list_max_quota', ['subscriber_per_list_max' => Auth::user()->customer->getOption('subscriber_per_list_max')]), ]); } $breadcrumbs = [ ['link' => url('dashboard'), 'name' => __('locale.menu.Dashboard')], ['link' => route('customer.contacts.show', $contact->uid), 'name' => $contact->name], ['name' => __('locale.contacts.new_contact')], ]; return view('customer.Contacts.create', compact('breadcrumbs', 'contact')); } /** * store new contact * * @param ContactGroups $contact * @param StoreContact $request * * @return RedirectResponse */ public function storeContact(ContactGroups $contact, StoreContact $request): RedirectResponse { if (config('app.stage') == 'demo') { return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'contact'])->with([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $phone = str_replace(['(', ')', '+', '-', ' '], '', $request->phone); $exist = Contacts::where('group_id', $contact->id)->where('customer_id', Auth::user()->id)->where('phone', $phone)->first(); if ($exist) { return redirect()->route('customer.contact.create', $contact->uid)->withInput(['tab' => 'contact', 'phone' => $request->phone])->with([ 'status' => 'error', 'message' => __('locale.contacts.you_have_already_subscribed', ['contact_group' => $contact->name]), ]); } $this->contactGroups->storeContact($contact, $request->only('phone', 'first_name', 'last_name')); return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'contact'])->with([ 'status' => 'success', 'message' => __('locale.contacts.contact_successfully_added'), ]); } /** * update contact status * * @param ContactGroups $contact * @param Request $request * * @return JsonResponse * @throws AuthorizationException */ public function updateContactStatus(ContactGroups $contact, Request $request): JsonResponse { if (config('app.stage') == 'demo') { return response()->json([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $this->authorize('update_contact'); $status = $this->contactGroups->updateContactStatus($contact, $request->only('id')); if ($status) { return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contact_successfully_change'), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.exceptions.something_went_wrong'), ]); } /** * delete single contact from group * * @param ContactGroups $contact * @param Request $request * * @return JsonResponse * @throws AuthorizationException */ public function deleteContact(ContactGroups $contact, Request $request): JsonResponse { if (config('app.stage') == 'demo') { return response()->json([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $this->authorize('delete_contact'); $status = $this->contactGroups->contactDestroy($contact, $request->only('id')); if ($status) { return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contact_successfully_deleted'), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.exceptions.something_went_wrong'), ]); } /** * view single contact for edit * * @param ContactGroups $contact * @param Request $request * * @return Application|Factory|View|RedirectResponse * @throws AuthorizationException */ public function editContact(ContactGroups $contact, Request $request): View|Factory|RedirectResponse|Application { $this->authorize('update_contact'); $data = Contacts::where('group_id', $contact->id)->where('customer_id', Auth::user()->id)->where('uid', $request->contact_id)->first(); if ($data) { $breadcrumbs = [ ['link' => url('dashboard'), 'name' => __('locale.menu.Dashboard')], ['link' => route('customer.contacts.show', $contact->uid), 'name' => $contact->name], ['name' => __('locale.contacts.update_contact')], ]; $template_tags = null; $custom_fields = ContactsCustomField::where('contact_id', $data->id)->cursor(); if ($custom_fields->count() == 0) { $custom_fields = null; $template_tags = TemplateTags::cursor(); } return view('customer.Contacts.show', compact('breadcrumbs', 'contact', 'data', 'custom_fields', 'template_tags')); } return redirect()->route('customer.contacts.show', $contact->uid)->with([ 'status' => 'error', 'message' => __('locale.contacts.contact_not_found'), ]); } /** * update single contact information * * @param ContactGroups $contact * @param UpdateContact $request * * @return RedirectResponse */ public function updateContact(ContactGroups $contact, UpdateContact $request): RedirectResponse { if (config('app.stage') == 'demo') { return redirect()->route('customer.contacts.show', $contact->uid)->with([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $status = $this->contactGroups->updateContact($contact, $request->except('_token')); if ($status) { return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'contact'])->with([ 'status' => 'success', 'message' => __('locale.contacts.contact_successfully_updated'), ]); } return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'contact'])->with([ 'status' => 'error', 'message' => __('locale.exceptions.something_went_wrong'), ]); } /** * import contact using csv or excel * * @param ContactGroups $contact * * @return Application|Factory|View * @throws AuthorizationException */ public function importContact(ContactGroups $contact): View|Factory|Application { $this->authorize('view_contact_group'); $breadcrumbs = [ ['link' => url('dashboard'), 'name' => __('locale.menu.Dashboard')], ['link' => url('contacts'), 'name' => __('locale.menu.Contacts')], ['name' => $contact->name], ]; return view('customer.Contacts.import', compact('breadcrumbs', 'contact')); } /** * working with import contacts * * @param ContactGroups $contact * @param ImportContact $request * * @return Application|Factory|View|RedirectResponse */ public function storeImportContact(ContactGroups $contact, ImportContact $request): View|Factory|RedirectResponse|Application { if (isset($request->option_toggle) && $request->option_toggle == 'on' && isset($request->import_file) && $request->hasFile('import_file')) { if ($request->file('import_file')->isValid()) { $breadcrumbs = [ ['link' => url('dashboard'), 'name' => __('locale.menu.Dashboard')], ['link' => url('contacts'), 'name' => __('locale.menu.Contacts')], ['name' => $contact->name], ]; $data = Excel::toArray(new stdClass(), $request->file('import_file'))[0]; $csv_data_file = CsvData::create([ 'user_id' => Auth::user()->id, 'ref_id' => $contact->uid, 'ref_type' => CsvData::TYPE_CONTACT, 'csv_filename' => $request->file('import_file')->getClientOriginalName(), 'csv_header' => $request->has('header'), 'csv_data' => json_encode($data), ]); $csv_data = array_slice($data, 0, 2); return view('customer.Contacts.import_fields', compact('csv_data', 'contact', 'csv_data_file', 'breadcrumbs')); } } elseif (isset($request->recipients) && $request->recipients != null) { if (config('app.stage') == 'demo') { return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'contact'])->with([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } if ($request->delimiter == ';') { $recipients = explode(';', $request->recipients); } elseif ($request->delimiter == ',') { $recipients = explode(',', $request->recipients); } elseif ($request->delimiter == '|') { $recipients = explode('|', $request->recipients); } elseif ($request->delimiter == 'tab') { $recipients = explode(' ', $request->recipients); } elseif ($request->delimiter == 'new_line') { $recipients = explode("\n", $request->recipients); } else { return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'contact'])->with([ 'status' => 'error', 'message' => __('locale.labels.invalid_delimiter'), ]); } if (isset($recipients) && is_array($recipients) && count($recipients) > 1000) { return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'contact'])->with([ 'status' => 'error', 'message' => __('locale.contacts.upload_maximum_1000_rows'), ]); } collect($recipients)->unique()->chunk('250')->each(function ($lines) use ($contact) { $list = []; foreach ($lines as $line) { $exist = Contacts::where('group_id', $contact->id)->where('customer_id', Auth::user()->id)->where('phone', $line)->first(); if ( ! $exist){ $list[] = [ 'uid' => uniqid(), 'customer_id' => Auth::user()->id, 'group_id' => $contact->id, 'status' => 'subscribe', 'phone' => $line, 'created_at' => now()->toDateTimeString(), 'updated_at' => now()->toDateTimeString(), ]; } } Contacts::insert($list); }); $contact->updateCache('SubscribersCount'); return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'contact'])->with([ 'status' => 'success', 'message' => __('locale.contacts.contact_successfully_imported'), ]); } else { return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'contact'])->with([ 'status' => 'error', 'message' => __('locale.exceptions.invalid_action'), ]); } return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'contact'])->with([ 'status' => 'error', 'message' => __('locale.exceptions.something_went_wrong'), ]); } /** * import process data * * @param ContactGroups $contact * @param Request $request * * @return RedirectResponse */ public function importProcessData(ContactGroups $contact, Request $request): RedirectResponse { if (config('app.stage') == 'demo') { return redirect()->route('customer.contacts.show', $contact->uid)->with([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $data = CsvData::find($request->csv_data_file_id); $csv_data = json_decode($data->csv_data, true); $db_fields = $request->fields; if (is_array($db_fields) && ! in_array('phone', $db_fields)) { return redirect()->route('customer.contacts.show', $contact->uid)->with([ 'status' => 'error', 'message' => __('locale.filezone.phone_number_column_require'), ]); } $collection = collect($csv_data)->skip($data->csv_header); $batch_list = []; Tool::resetMaxExecutionTime(); $collection->chunk(5000) ->each(function ($lines) use ($contact, &$batch_list, $db_fields) { $batch_list[] = new ImportContacts(Auth::user()->id, $contact->id, $lines, $db_fields); }); try { $import_name = 'ImportContacts_'.date('Ymdhms'); $import_job = ImportJobHistory::create([ 'name' => $import_name, 'import_id' => $contact->uid, 'type' => 'import_contact', 'status' => 'processing', 'options' => json_encode(['status' => 'processing', 'message' => 'Import contacts are running']), 'batch_id' => null, ]); $batch = Bus::batch($batch_list) ->then(function (Batch $batch) use ($contact, $import_name, $import_job) { $contact->update(['batch_id' => $batch->id]); $import_job->update(['batch_id' => $batch->id]); }) ->catch(function (Batch $batch, Throwable $e) { $import_history = ImportJobHistory::where('batch_id', $batch->id)->first(); if ($import_history) { $import_history->status = 'failed'; $import_history->options = json_encode(['status' => 'failed', 'message' => $e->getMessage()]); $import_history->save(); } }) ->finally(function (Batch $batch) use ($contact, $data) { $import_history = ImportJobHistory::where('batch_id', $batch->id)->first(); if ($import_history) { $import_history->status = 'finished'; $import_history->options = json_encode(['status' => 'finished', 'message' => 'Import contacts was successfully imported.']); $import_history->save(); } $contact->updateCache('SubscribersCount'); $data->delete(); //send event notification remaining }) ->name($import_name) ->dispatch(); $contact->update(['batch_id' => $batch->id]); return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'import_history'])->with([ 'status' => 'success', 'message' => __('locale.contacts.contact_successfully_imported_in_background'), ]); } catch (Throwable $e) { return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'import_history'])->with([ 'status' => 'error', 'message' => $e->getMessage(), ]); } } /** * Bulk Action with subscribe, unsubscribe and Delete contacts * * @param ContactGroups $contact * @param Request $request * * @return JsonResponse * @throws AuthorizationException */ public function batchActionContact(ContactGroups $contact, Request $request): JsonResponse { if (config('app.stage') == 'demo') { return response()->json([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $action = $request->get('action'); $ids = $request->get('ids'); switch ($action) { case 'destroy': $this->authorize('delete_contact'); $this->contactGroups->batchContactDestroy($contact, $ids); return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contacts_deleted'), ]); case 'subscribe': $this->authorize('update_contact'); $this->contactGroups->batchContactSubscribe($contact, $ids); return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contacts_subscribe'), ]); case 'unsubscribe': $this->authorize('update_contact'); $this->contactGroups->batchContactUnsubscribe($contact, $ids); return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contacts_unsubscribe'), ]); case 'copy': $this->authorize('update_contact'); $target_group = $request->get('target_group'); $group = ContactGroups::where('uid', $target_group)->first(); if ( ! $group) { return response()->json([ 'status' => 'error', 'message' => __('locale.contacts.contact_group_not_found'), ]); } $input = [ 'ids' => $ids, 'target_group' => $group->id, ]; $this->contactGroups->batchContactCopy($contact, $input); return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contacts_copy'), ]); case 'move': $this->authorize('update_contact'); $target_group = $request->get('target_group'); $group = ContactGroups::where('uid', $target_group)->first(); if ( ! $group) { return response()->json([ 'status' => 'error', 'message' => __('locale.contacts.contact_group_not_found'), ]); } $input = [ 'ids' => $ids, 'target_group' => $group->id, ]; $this->contactGroups->batchContactMove($contact, $input); return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.contacts_moved'), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.exceptions.invalid_action'), ]); } /** * @param $group_id * * @return Generator */ public function contactsGenerator($group_id): Generator { foreach (Contacts::where('group_id', $group_id)->cursor() as $contact) { yield $contact; } } /** * @param ContactGroups $contact * * @return RedirectResponse|BinaryFileResponse * @throws AuthorizationException * @throws IOException * @throws InvalidArgumentException * @throws UnsupportedTypeException * @throws WriterNotOpenedException */ public function exportContact(ContactGroups $contact): BinaryFileResponse|RedirectResponse { if (config('app.stage') == 'demo') { return redirect()->route('customer.contacts.show', $contact->uid)->with([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $this->authorize('view_contact'); $file_name = (new FastExcel($this->contactsGenerator($contact->id)))->export(storage_path('Contacts_'.time().'.xlsx')); return response()->download($file_name); } /** * @param ContactGroups $contact * * @return Application|Factory|View */ public function subscribeURL(ContactGroups $contact): View|Factory|Application { $pageConfigs = [ 'bodyClass' => "bg-full-screen-image", 'blankPage' => true, ]; $user = User::find($contact->customer_id); $coverage = null; if ($user) { $plan_id = $user->customer->activeSubscription()->plan_id; $coverage = PlansCoverageCountries::where('plan_id', $plan_id)->where('status', true)->cursor(); return view('customer.Contacts.subscribe_form', compact('contact', 'pageConfigs', 'coverage')); } return view('customer.Contacts.subscribe_form', compact('contact', 'pageConfigs', 'coverage')); } /** * insert contact by subscription form * * @param ContactGroups $contact * @param Request $request * * @return RedirectResponse */ public function insertContactBySubscriptionForm(ContactGroups $contact, Request $request): RedirectResponse { if (config('app.stage') == 'demo') { return redirect()->route('customer.contacts.show', $contact->uid)->with([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $rules = [ 'phone' => ['required', new Phone($request->phone)], ]; if (config('no-captcha.registration')) { $rules['g-recaptcha-response'] = ['required', new CaptchaRule()]; } $messages = [ 'g-recaptcha-response.required' => __('locale.auth.recaptcha_required'), 'g-recaptcha-response.captcha' => __('locale.auth.recaptcha_required'), ]; $validation = Validator::make($request->all(), $rules, $messages); if ($validation->fails()) { return redirect()->route('contacts.subscribe_url', $contact->uid)->withInput($request->all())->withErrors($validation->errors()); } if ($request->country_code) { $phone = $request->country_code.ltrim($request->phone, 0); } else { $phone = $request->phone; } $exist = Contacts::where('group_id', $contact->id)->where('phone', $phone)->first(); if ($exist) { return redirect()->route('contacts.subscribe_url', $contact->uid)->with([ 'status' => 'error', 'message' => __('locale.contacts.you_have_already_subscribed', ['contact_group' => $contact->name]), ]); } $this->contactGroups->storeContact($contact, ['phone' => $phone, 'first_name' => $request->first_name, 'last_name' => $request->last_name]); return redirect()->route('contacts.subscribe_url', $contact->uid)->with([ 'status' => 'success', 'message' => __('locale.contacts.you_have_successfully_subscribe', ['contact_group' => $contact->name]), ]); } /** * return sms form data * * @param ContactGroups $contact * @param Request $request * * @return JsonResponse */ public function getMessageForm(ContactGroups $contact, Request $request): JsonResponse { return response()->json([ 'status' => 'success', 'message' => $contact->{$request->sms_form}, ]); } /** * update contact groups message * * @param ContactGroups $contact * @param UpdateContactGroupMessage $request * * @return RedirectResponse */ public function message(ContactGroups $contact, UpdateContactGroupMessage $request): RedirectResponse { if (config('app.stage') == 'demo') { return redirect()->route('customer.contacts.show', $contact->uid)->with([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $contact->{$request->message_form} = $request->message; $contact->save(); return redirect()->route('customer.contacts.show', $contact->uid)->withInput(['tab' => 'message'])->with([ 'status' => 'success', 'message' => __('locale.contacts.contact_groups_message_information', ['message_from' => ucfirst(str_replace('_', ' ', $request->message_form))]), ]); } /** * add opt in keyword * * @param ContactGroups $contact * @param Request $request * * @return JsonResponse */ public function optInKeyword(ContactGroups $contact, Request $request): JsonResponse { if (config('app.stage') == 'demo') { return response()->json([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $keyword_name = $request->keyword_name; if ($keyword_name) { $keyword = Keywords::where('user_id', Auth::user()->id)->where('keyword_name', $keyword_name)->first(); if ($keyword) { $status = ContactGroupsOptinKeywords::create([ 'contact_group' => $contact->id, 'keyword' => $keyword_name, ]); if ($status) { return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.optin_keyword_successfully_added'), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.exceptions.something_went_wrong'), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.contacts.keyword_info_not_found'), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.labels.at_least_one_data'), ]); } /** * add opt in keyword * * @param ContactGroups $contact * @param Request $request * * @return JsonResponse */ public function optOutKeyword(ContactGroups $contact, Request $request): JsonResponse { if (config('app.stage') == 'demo') { return response()->json([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $keyword_name = $request->keyword_name; if ($keyword_name) { $keyword = Keywords::where('user_id', Auth::user()->id)->where('keyword_name', $keyword_name)->first(); if ($keyword) { $status = ContactGroupsOptoutKeywords::create([ 'contact_group' => $contact->id, 'keyword' => $keyword_name, ]); if ($status) { return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.optout_keyword_successfully_added'), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.exceptions.something_went_wrong'), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.contacts.keyword_info_not_found'), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.labels.at_least_one_data'), ]); } /** * delete opt in keyword * * @param ContactGroups $contact * @param Request $request * * @return JsonResponse */ public function deleteOptInKeyword(ContactGroups $contact, Request $request): JsonResponse { if (config('app.stage') == 'demo') { return response()->json([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $keyword_id = $request->id; $optin_keyword = ContactGroupsOptinKeywords::where('contact_group', $contact->id)->where('uid', $keyword_id)->delete(); if ($optin_keyword) { return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.optin_keyword_successfully_deleted'), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.contacts.keyword_info_not_found'), ]); } /** * delete opt out keyword * * @param ContactGroups $contact * @param Request $request * * @return JsonResponse */ public function deleteOptOutKeyword(ContactGroups $contact, Request $request): JsonResponse { if (config('app.stage') == 'demo') { return response()->json([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $keyword_id = $request->id; $optout_keyword = ContactGroupsOptoutKeywords::where('contact_group', $contact->id)->where('uid', $keyword_id)->delete(); if ($optout_keyword) { return response()->json([ 'status' => 'success', 'message' => __('locale.contacts.optout_keyword_successfully_deleted'), ]); } return response()->json([ 'status' => 'error', 'message' => __('locale.contacts.keyword_info_not_found'), ]); } /** * @return Generator */ public function contactGroupsGenerator(): Generator { foreach (ContactGroups::where('customer_id', Auth::user()->id)->cursor() as $contactGroup) { yield $contactGroup; } } /** * @return RedirectResponse|BinaryFileResponse * @throws AuthorizationException * @throws IOException * @throws InvalidArgumentException * @throws UnsupportedTypeException * @throws WriterNotOpenedException */ public function export(): BinaryFileResponse|RedirectResponse { if (config('app.stage') == 'demo') { return redirect()->route('customer.contacts.index')->with([ 'status' => 'error', 'message' => 'Sorry! This option is not available in demo mode', ]); } $this->authorize('view_contact'); $file_name = (new FastExcel($this->contactGroupsGenerator()))->export(storage_path('ContactGroups_'.time().'.xlsx')); return response()->download($file_name); } }