middleware('admin');
}
public function index(NewsletterListFilterRequest $request): View|RedirectResponse
{
if ($request->get('reset') === 'filter') {
set_user_attr(self::FILTER_SETTING_KEY, null);
return redirect(route('admin_newsletter_lists'));
}
$filter = $request->query() === []
? NewsletterListFilter::fromArray((array) (get_user_attr(self::FILTER_SETTING_KEY) ?: []))
: $request->toFilter();
return $this->renderIndex($filter, $request->listId() ? NewsletterList::find($request->listId()) : null);
}
/**
* Lädt eine gespeicherte Liste in die Filter-Seite.
*/
public function load(NewsletterList $list): View
{
set_user_attr(self::FILTER_SETTING_KEY, $list->toFilter()->toArray());
return $this->renderIndex($list->toFilter(), $list);
}
public function datatable(NewsletterListFilterRequest $request): JsonResponse
{
$filter = $request->toFilter();
set_user_attr(self::FILTER_SETTING_KEY, $filter->toArray());
$result = $this->service->cached($filter, (int) $request->user()->id, $request->boolean('refresh'));
return DataTables::of($result->rows)
->editColumn('email', fn (array $row) => e($row['email']))
->addColumn('name', fn (array $row) => e(trim($row['firstname'].' '.$row['lastname'])))
->editColumn('optin_label', fn (array $row) => $this->optinBadge($row))
->editColumn('status_label', fn (array $row) => $this->statusBadge($row))
->rawColumns(['optin_label', 'status_label'])
->with('stats', $result->stats)
->toJson();
}
public function export(NewsletterListFilterRequest $request, string $format): BinaryFileResponse|StreamedResponse
{
$filter = $request->toFilter();
set_user_attr(self::FILTER_SETTING_KEY, $filter->toArray());
$list = $request->listId() ? NewsletterList::find($request->listId()) : null;
return $this->download($request->user(), $filter, $format, $list);
}
/**
* Export direkt aus der Übersicht der gespeicherten Listen mit den dort hinterlegten Filtern.
*/
public function exportSaved(Request $request, NewsletterList $list, string $format): BinaryFileResponse|StreamedResponse
{
return $this->download($request->user(), $list->toFilter(), $format, $list);
}
public function store(StoreNewsletterListRequest $request): RedirectResponse
{
$list = new NewsletterList([
'name' => $request->listName(),
'created_by' => $request->user()->id,
]);
$list->applyFilter($request->toFilter());
$list->save();
return redirect(route('admin_newsletter_lists_load', [$list->id]))->with('alert-success', __('newsletter.saved.stored'));
}
public function update(StoreNewsletterListRequest $request, NewsletterList $list): RedirectResponse
{
$list->name = $request->listName();
$list->applyFilter($request->toFilter());
$list->save();
return redirect(route('admin_newsletter_lists_load', [$list->id]))->with('alert-success', __('newsletter.saved.updated'));
}
public function destroy(NewsletterList $list): RedirectResponse
{
$list->exports()->update(['newsletter_list_id' => null]);
$list->delete();
return redirect(route('admin_newsletter_lists_saved'))->with('alert-success', __('newsletter.saved.deleted'));
}
public function saved(): View
{
return view('admin.newsletter.saved');
}
public function savedDatatable(): JsonResponse
{
$query = NewsletterList::query()->with('creator.account')->select('newsletter_lists.*');
return DataTables::eloquent($query)
->editColumn('name', fn (NewsletterList $list) => ''.e($list->name).'')
->editColumn('source', fn (NewsletterList $list) => __('newsletter.source_option.'.$list->source))
->addColumn('summary', fn (NewsletterList $list) => e($this->service->describe($list->toFilter())))
->editColumn('last_exported_at', fn (NewsletterList $list) => $list->last_exported_at?->format('d.m.Y H:i') ?? '–')
->editColumn('last_row_count', fn (NewsletterList $list) => $list->last_row_count === null ? '–' : (string) $list->last_row_count)
->addColumn('creator', fn (NewsletterList $list) => e($list->creator?->getFullName(false) ?? ''))
->editColumn('created_at', fn (NewsletterList $list) => $list->created_at?->format('d.m.Y') ?? '')
->addColumn('actions', fn (NewsletterList $list) => $this->savedActions($list))
->orderColumn('name', 'name $1')
->orderColumn('last_exported_at', 'last_exported_at $1')
->orderColumn('last_row_count', 'last_row_count $1')
->orderColumn('created_at', 'created_at $1')
->rawColumns(['name', 'actions'])
->make(true);
}
public function exports(): View
{
return view('admin.newsletter.exports');
}
public function exportsDatatable(): JsonResponse
{
$query = NewsletterListExport::query()->with(['list', 'user.account'])->select('newsletter_list_exports.*');
return DataTables::eloquent($query)
->editColumn('created_at', fn (NewsletterListExport $export) => $export->created_at?->format('d.m.Y H:i') ?? '')
->addColumn('user', fn (NewsletterListExport $export) => e($export->user?->getFullName(false) ?? ''))
->addColumn('list', function (NewsletterListExport $export) {
if ($export->list) {
return ''.e($export->list->name).'';
}
return ''.e(__('newsletter.exports.adhoc')).'';
})
->addColumn('summary', fn (NewsletterListExport $export) => e($this->service->describe(NewsletterListFilter::fromArray((array) $export->filters))))
->editColumn('format', fn (NewsletterListExport $export) => strtoupper($export->format))
->orderColumn('created_at', 'created_at $1')
->orderColumn('row_count', 'row_count $1')
->orderColumn('format', 'format $1')
->rawColumns(['list'])
->make(true);
}
private function renderIndex(NewsletterListFilter $filter, ?NewsletterList $list): View
{
return view('admin.newsletter.index', [
'filter' => $filter->toArray(),
'list' => $list,
'countries' => Country::query()->orderBy(app()->getLocale() === 'en' ? 'en' : 'de')->get(),
'members' => User::query()
->with('account')
->whereNotNull('account_id')
->where('admin', '<', 5)
->get()
->sortBy(fn (User $user) => mb_strtolower($user->getFullName(false)))
->values(),
'levels' => UserLevel::query()->orderBy('pos')->get(),
'languages' => ['de', 'en', 'es', 'fr'],
'sources' => NewsletterListFilter::sources(),
'optin_options' => NewsletterListFilter::optinOptions(),
'purchase_options' => NewsletterListFilter::purchaseOptions(),
'company_options' => NewsletterListFilter::companyOptions(),
'consultant_status_options' => NewsletterListFilter::consultantStatusOptions(),
]);
}
/**
* Baut den Download, protokolliert den Export und aktualisiert die gespeicherte Liste.
*/
private function download(User $user, NewsletterListFilter $filter, string $format, ?NewsletterList $list): BinaryFileResponse|StreamedResponse
{
$result = $this->service->build($filter);
$headers = $this->service->exportHeaders($filter->templateOnly);
$rows = $this->service->exportRows($result->rows, $filter->templateOnly);
$filename = 'newsletter_'.$filter->source.'_'.($filter->language ?? 'alle').'_'.now()->format('Y-m-d').'.'.$format;
$this->logExport($user, $filter, $format, $filename, $result, $list);
if ($format === 'csv') {
return $this->streamCsv($filename, $headers, $rows);
}
return Excel::download(new xExport($rows, $headers), $filename);
}
private function logExport(User $user, NewsletterListFilter $filter, string $format, string $filename, NewsletterListResult $result, ?NewsletterList $list): void
{
NewsletterListExport::create([
'newsletter_list_id' => $list?->id,
'user_id' => $user->id,
'format' => $format,
'row_count' => $result->stats['total'],
'filename' => $filename,
'filters' => $filter->toArray(),
'stats' => $result->stats,
]);
if ($list !== null) {
$list->forceFill([
'last_exported_at' => now(),
'last_row_count' => $result->stats['total'],
])->save();
}
}
/**
* @param string[] $headers
* @param array> $rows
*/
private function streamCsv(string $filename, array $headers, array $rows): StreamedResponse
{
return response()->streamDownload(function () use ($headers, $rows) {
$out = fopen('php://output', 'w');
fwrite($out, "\xEF\xBB\xBF");
fputcsv($out, $headers, ';', '"', '\\');
foreach ($rows as $row) {
fputcsv($out, self::csvSafeRow($row), ';', '"', '\\');
}
fclose($out);
}, $filename, ['Content-Type' => 'text/csv; charset=UTF-8']);
}
/**
* Schützt vor CSV-Injection: Werte, die mit Formelzeichen beginnen, werden entschärft.
*
* @param array $row
* @return array
*/
private static function csvSafeRow(array $row): array
{
return array_map(static function (string $value) {
return $value !== '' && str_contains("=+-@\t\r", $value[0]) ? "'".$value : $value;
}, $row);
}
private function savedActions(NewsletterList $list): string
{
return ' '
.' '
.' '
.'';
}
/**
* @param array $row
*/
private function optinBadge(array $row): string
{
$class = match ($row['optin']) {
NewsletterListService::OPTIN_YES => 'badge-success',
NewsletterListService::OPTIN_CONTRACT => 'badge-info',
default => 'badge-danger',
};
return ''.e($row['optin_label']).'';
}
/**
* @param array $row
*/
private function statusBadge(array $row): string
{
$class = $row['status'] === NewsletterListService::STATUS_ACTIVE ? 'badge-success' : 'badge-secondary';
return ''.e($row['status_label']).'';
}
}