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');
}
}