Files

115 lines
4.8 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Admin;
use App\Models\Bank;
use App\Models\Organization;
use App\Models\Poll;
use App\Models\Post;
use App\Models\PostTranslation;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
/**
* Dedicated page of a poll: its post (title, image, intro, text) with the poll form below.
*/
class PollController extends Controller
{
public function show(string $slug)
{
// Polls are for logged-in profiles only. The route requires a login already; this is a
// safety net should the route ever be moved out of its protected group.
if (!Auth::guard('web')->check()) {
session(['url.intended' => request()->url()]);
return redirect(LaravelLocalization::localizeUrl('/login'));
}
$postId = PostTranslation::where('slug', $slug)->value('post_id');
$post = $postId ? Post::with(['poll', 'category.translations'])->find($postId) : null;
if (!$post || !$post->poll) {
return view('posts.not_found');
}
// Poll admins may preview unpublished polls and drafts; for everyone else a draft does
// not exist yet, even when its post already has a publication date (security audit R3)
$preview = Poll::viewerIsPollAdmin();
if ($post->poll->status === Poll::STATUS_DRAFT && !$preview) {
return view('posts.not_found');
}
$published = function ($query) use ($preview) {
if (!$preview) {
$query->whereDate('from', '<=', now())
->where(fn ($q) => $q->whereDate('till', '>', now())->orWhereNull('till'));
}
};
// The current language, otherwise any published language
$translation = $post->translations()->where('locale', App::getLocale())->where($published)->first()
?? $post->translations()->where($published)->first();
if (!$translation) {
return view('posts.not_found');
}
// Another of the user's profiles may take part while the active one may not: right after
// logging in to this poll (a one-time session marker set by LoginResponse) with only one
// such profile, switch to it straight away (its own login flow, password for banks and
// admins); otherwise offer them on the page.
$justLoggedIn = session()->pull('poll_switch_after_login') === '/' . ltrim(request()->path(), '/');
$switchProfiles = $this->switchProfiles($post->poll, request()->url());
if ($justLoggedIn && count($switchProfiles) === 1) {
return redirect($switchProfiles[0]['url']);
}
$media = $post->getFirstMedia('posts');
$category = $post->category?->translations->firstWhere('locale', App::getLocale())?->name
?? $post->category?->translations->first()?->name;
$update = Carbon::parse($translation->updated_at)->isoFormat('LL');
return view('polls.show', compact('post', 'translation', 'media', 'category', 'update', 'switchProfiles'));
}
/**
* The logged-in user's other profiles that may take part when the active profile may not,
* each with the direct login link of that profile that returns to this poll.
*
* @return array<int, array{name: string, type: string, url: string}>
*/
private function switchProfiles(Poll $poll, string $pollUrl): array
{
$user = Auth::guard('web')->user();
$active = getActiveProfile();
if (!$user instanceof User || !$active || !$poll->isOpen() || $poll->isTargetedAt($active, $user)) {
return [];
}
$user->loadMissing(['organizations', 'banksManaged', 'admins']);
$routes = [
User::class => ['user.direct-login', 'userId'],
Organization::class => ['organization.direct-login', 'organizationId'],
Bank::class => ['bank.direct-login', 'bankId'],
Admin::class => ['admin.direct-login', 'adminId'],
];
$profiles = collect([$user])->concat($user->organizations)->concat($user->banksManaged)->concat($user->admins)
->reject(fn (Model $profile) => $profile->is($active))
->filter(fn (Model $profile) => $poll->isTargetedAt($profile, $user));
return $profiles->map(function (Model $profile) use ($routes, $pollUrl) {
[$route, $parameter] = $routes[get_class($profile)];
return [
'name' => $profile->name,
'type' => class_basename($profile),
'url' => route($route, [$parameter => $profile->getKey()]) . '?intended=' . urlencode($pollUrl),
];
})->values()->all();
}
}