Add homepage announcement module
This commit is contained in:
451
app/Http/Controllers/Settings/HomepageAnnouncementController.php
Normal file
451
app/Http/Controllers/Settings/HomepageAnnouncementController.php
Normal file
@@ -0,0 +1,451 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Settings\UpsertHomepageAnnouncementRequest;
|
||||
use App\Models\HomepageAnnouncement;
|
||||
use App\Services\HomepageAnnouncementService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class HomepageAnnouncementController extends Controller
|
||||
{
|
||||
private const BACKGROUND_WEBP_QUALITY = 84;
|
||||
|
||||
public function __construct(private readonly HomepageAnnouncementService $announcements)
|
||||
{
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$announcements = HomepageAnnouncement::query()
|
||||
->latest('updated_at')
|
||||
->paginate(20)
|
||||
->withQueryString();
|
||||
|
||||
$announcements->getCollection()->transform(fn (HomepageAnnouncement $announcement): array => $this->serializeForIndex($announcement));
|
||||
|
||||
return Inertia::render('Admin/HomepageAnnouncements/Index', [
|
||||
'announcements' => $announcements,
|
||||
'createUrl' => route('admin.homepage-announcements.create'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
$form = $this->blankForm();
|
||||
|
||||
return Inertia::render('Admin/HomepageAnnouncements/Form', [
|
||||
'announcement' => $form,
|
||||
'previewAnnouncement' => $this->announcements->previewPayload($form),
|
||||
'options' => $this->options(),
|
||||
'submitUrl' => route('admin.homepage-announcements.store'),
|
||||
'previewUrl' => route('admin.homepage-announcements.preview'),
|
||||
'indexUrl' => route('admin.homepage-announcements.index'),
|
||||
'destroyUrl' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(UpsertHomepageAnnouncementRequest $request): RedirectResponse
|
||||
{
|
||||
$actor = $this->currentActor($request);
|
||||
$announcement = new HomepageAnnouncement();
|
||||
$attributes = $this->persistedAttributes($request, $announcement);
|
||||
$attributes['created_by'] = (int) $actor->id;
|
||||
$attributes['updated_by'] = (int) $actor->id;
|
||||
|
||||
$announcement->forceFill($attributes)->save();
|
||||
|
||||
return redirect()
|
||||
->route('admin.homepage-announcements.edit', ['homepageAnnouncement' => $announcement])
|
||||
->with('success', 'Homepage announcement created.');
|
||||
}
|
||||
|
||||
public function edit(HomepageAnnouncement $homepageAnnouncement): Response
|
||||
{
|
||||
$form = $this->serializeForForm($homepageAnnouncement);
|
||||
|
||||
return Inertia::render('Admin/HomepageAnnouncements/Form', [
|
||||
'announcement' => $form,
|
||||
'previewAnnouncement' => $this->announcements->toHomepagePayload($homepageAnnouncement),
|
||||
'options' => $this->options(),
|
||||
'submitUrl' => route('admin.homepage-announcements.update', ['homepageAnnouncement' => $homepageAnnouncement]),
|
||||
'previewUrl' => route('admin.homepage-announcements.preview'),
|
||||
'indexUrl' => route('admin.homepage-announcements.index'),
|
||||
'destroyUrl' => route('admin.homepage-announcements.destroy', ['homepageAnnouncement' => $homepageAnnouncement]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpsertHomepageAnnouncementRequest $request, HomepageAnnouncement $homepageAnnouncement): RedirectResponse
|
||||
{
|
||||
$actor = $this->currentActor($request);
|
||||
$attributes = $this->persistedAttributes($request, $homepageAnnouncement);
|
||||
$attributes['updated_by'] = (int) $actor->id;
|
||||
|
||||
$homepageAnnouncement->forceFill($attributes)->save();
|
||||
|
||||
return redirect()
|
||||
->route('admin.homepage-announcements.edit', ['homepageAnnouncement' => $homepageAnnouncement])
|
||||
->with('success', 'Homepage announcement updated.');
|
||||
}
|
||||
|
||||
public function destroy(HomepageAnnouncement $homepageAnnouncement): RedirectResponse
|
||||
{
|
||||
$homepageAnnouncement->delete();
|
||||
|
||||
return redirect()
|
||||
->route('admin.homepage-announcements.index')
|
||||
->with('success', 'Homepage announcement deleted.');
|
||||
}
|
||||
|
||||
public function preview(UpsertHomepageAnnouncementRequest $request): JsonResponse
|
||||
{
|
||||
$attributes = $this->persistedAttributes($request, null, false);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'announcement' => $this->announcements->previewPayload($attributes),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function persistedAttributes(UpsertHomepageAnnouncementRequest $request, ?HomepageAnnouncement $announcement = null, bool $persistFile = true): array
|
||||
{
|
||||
$validated = $request->validated();
|
||||
unset($validated['background_image_file'], $validated['remove_background_image']);
|
||||
|
||||
$validated = $this->announcements->sanitizeAttributes($validated);
|
||||
|
||||
$validated = $this->normalizeLinkAttributes($validated, 'primary');
|
||||
$validated = $this->normalizeLinkAttributes($validated, 'secondary');
|
||||
|
||||
$currentBackground = $announcement?->background_image;
|
||||
$backgroundImageFile = $this->backgroundImageUpload($request);
|
||||
|
||||
if ($request->boolean('remove_background_image')) {
|
||||
if ($persistFile) {
|
||||
$this->deleteStoredBackgroundIfLocal($currentBackground);
|
||||
}
|
||||
|
||||
$validated['background_image'] = null;
|
||||
} elseif ($persistFile && $backgroundImageFile instanceof UploadedFile) {
|
||||
$this->deleteStoredBackgroundIfLocal($currentBackground);
|
||||
$validated['background_image'] = $this->storeBackgroundImage($backgroundImageFile);
|
||||
} elseif (! array_key_exists('background_image', $validated) && $announcement) {
|
||||
$validated['background_image'] = $announcement->background_image;
|
||||
}
|
||||
|
||||
$validated['background_type'] = filled($validated['background_image'] ?? null) ? 'image' : null;
|
||||
|
||||
return $validated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attributes
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function normalizeLinkAttributes(array $attributes, string $prefix): array
|
||||
{
|
||||
$type = (string) ($attributes[$prefix . '_link_type'] ?? HomepageAnnouncement::LINK_TYPE_NONE);
|
||||
|
||||
if ($type === HomepageAnnouncement::LINK_TYPE_NONE) {
|
||||
$attributes[$prefix . '_link_label'] = null;
|
||||
$attributes[$prefix . '_link_url'] = null;
|
||||
$attributes[$prefix . '_link_target_type'] = null;
|
||||
$attributes[$prefix . '_link_target_id'] = null;
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
$attributes[$prefix . '_link_label'] = filled($attributes[$prefix . '_link_label'] ?? null)
|
||||
? trim((string) $attributes[$prefix . '_link_label'])
|
||||
: null;
|
||||
$attributes[$prefix . '_link_url'] = filled($attributes[$prefix . '_link_url'] ?? null)
|
||||
? trim((string) $attributes[$prefix . '_link_url'])
|
||||
: null;
|
||||
$attributes[$prefix . '_link_target_id'] = filled($attributes[$prefix . '_link_target_id'] ?? null)
|
||||
? (int) $attributes[$prefix . '_link_target_id']
|
||||
: null;
|
||||
|
||||
if (in_array($type, [HomepageAnnouncement::LINK_TYPE_CUSTOM_URL, HomepageAnnouncement::LINK_TYPE_EXPLORE, HomepageAnnouncement::LINK_TYPE_UPLOAD], true)) {
|
||||
$attributes[$prefix . '_link_target_type'] = null;
|
||||
$attributes[$prefix . '_link_target_id'] = null;
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
$attributes[$prefix . '_link_target_type'] = $type;
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
private function deleteStoredBackgroundIfLocal(?string $path): void
|
||||
{
|
||||
$path = trim((string) $path);
|
||||
if ($path === '' || str_starts_with($path, 'http://') || str_starts_with($path, 'https://') || str_starts_with($path, '/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
}
|
||||
|
||||
private function backgroundImageUpload(UpsertHomepageAnnouncementRequest $request): ?UploadedFile
|
||||
{
|
||||
$file = $request->file('background_image_file');
|
||||
|
||||
if (! $file instanceof UploadedFile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$pathName = trim((string) $file->getPathname());
|
||||
if ($file->isValid() && $pathName !== '' && is_file($pathName) && is_readable($pathName)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'background_image_file' => $this->backgroundUploadErrorMessage($file),
|
||||
]);
|
||||
}
|
||||
|
||||
private function storeBackgroundImage(UploadedFile $file): string
|
||||
{
|
||||
$pathName = trim((string) $file->getPathname());
|
||||
if ($pathName === '' || ! is_file($pathName) || ! is_readable($pathName)) {
|
||||
throw ValidationException::withMessages([
|
||||
'background_image_file' => $this->backgroundUploadErrorMessage($file),
|
||||
]);
|
||||
}
|
||||
|
||||
if (! function_exists('imagecreatefromstring') || ! function_exists('imagewebp')) {
|
||||
throw ValidationException::withMessages([
|
||||
'background_image_file' => 'The server is missing WebP image support. Enable the GD WebP extension to upload announcement backgrounds.',
|
||||
]);
|
||||
}
|
||||
|
||||
$binary = @file_get_contents($pathName);
|
||||
if ($binary === false) {
|
||||
throw ValidationException::withMessages([
|
||||
'background_image_file' => 'The uploaded background image could not be opened for conversion. Please choose the file again and retry.',
|
||||
]);
|
||||
}
|
||||
|
||||
$image = @imagecreatefromstring($binary);
|
||||
if (! $image instanceof \GdImage) {
|
||||
throw ValidationException::withMessages([
|
||||
'background_image_file' => 'The uploaded background image format could not be converted. Please use JPG, PNG, or WEBP.',
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
if (! imageistruecolor($image)) {
|
||||
imagepalettetotruecolor($image);
|
||||
}
|
||||
|
||||
imagealphablending($image, true);
|
||||
imagesavealpha($image, true);
|
||||
|
||||
ob_start();
|
||||
$converted = imagewebp($image, null, self::BACKGROUND_WEBP_QUALITY);
|
||||
$webpBinary = ob_get_clean();
|
||||
|
||||
if (! $converted || ! is_string($webpBinary) || $webpBinary === '') {
|
||||
throw ValidationException::withMessages([
|
||||
'background_image_file' => 'The uploaded background image could not be converted to WebP. Please try a different image.',
|
||||
]);
|
||||
}
|
||||
|
||||
$storedPath = 'homepage-announcements/' . pathinfo(Str::replace('\\', '/', $file->hashName()), PATHINFO_FILENAME) . '.webp';
|
||||
Storage::disk('public')->put($storedPath, $webpBinary, ['visibility' => 'public']);
|
||||
} finally {
|
||||
imagedestroy($image);
|
||||
}
|
||||
|
||||
return $storedPath;
|
||||
}
|
||||
|
||||
private function backgroundUploadErrorMessage(UploadedFile $file): string
|
||||
{
|
||||
return match ($file->getError()) {
|
||||
UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'The uploaded background image exceeds the server upload limit.',
|
||||
UPLOAD_ERR_PARTIAL => 'The uploaded background image was only partially received. Please retry the upload.',
|
||||
UPLOAD_ERR_NO_TMP_DIR => 'The server upload temp directory is unavailable. Check PHP upload temp configuration.',
|
||||
UPLOAD_ERR_CANT_WRITE => 'The server could not write the uploaded background image to temporary storage.',
|
||||
UPLOAD_ERR_EXTENSION => 'A PHP extension blocked the background image upload.',
|
||||
default => 'The uploaded background image could not be read. Please choose the file again and retry.',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function blankForm(): array
|
||||
{
|
||||
return [
|
||||
'id' => null,
|
||||
'title' => '',
|
||||
'subtitle' => '',
|
||||
'badge_text' => '',
|
||||
'content_html' => '',
|
||||
'type' => HomepageAnnouncement::TYPE_ANNOUNCEMENT,
|
||||
'status' => HomepageAnnouncement::STATUS_DRAFT,
|
||||
'is_active' => true,
|
||||
'starts_at' => '',
|
||||
'ends_at' => '',
|
||||
'priority' => 0,
|
||||
'is_dismissible' => true,
|
||||
'dismiss_version' => 1,
|
||||
'gradient_preset' => HomepageAnnouncement::GRADIENT_NOVA_AURORA,
|
||||
'theme_preset' => 'launch',
|
||||
'placement' => HomepageAnnouncement::PLACEMENT_HOMEPAGE_AFTER_FEATURED,
|
||||
'text_color' => '',
|
||||
'overlay_opacity' => 55,
|
||||
'background_image' => '',
|
||||
'background_image_url' => null,
|
||||
'remove_background_image' => false,
|
||||
'background_image_file' => null,
|
||||
'primary_link_label' => '',
|
||||
'primary_link_type' => HomepageAnnouncement::LINK_TYPE_NONE,
|
||||
'primary_link_url' => '',
|
||||
'primary_link_target_id' => '',
|
||||
'secondary_link_label' => '',
|
||||
'secondary_link_type' => HomepageAnnouncement::LINK_TYPE_NONE,
|
||||
'secondary_link_url' => '',
|
||||
'secondary_link_target_id' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function serializeForForm(HomepageAnnouncement $announcement): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) $announcement->id,
|
||||
'title' => (string) $announcement->title,
|
||||
'subtitle' => (string) ($announcement->subtitle ?? ''),
|
||||
'badge_text' => (string) ($announcement->badge_text ?? ''),
|
||||
'content_html' => (string) ($announcement->content_html ?? ''),
|
||||
'type' => (string) $announcement->type,
|
||||
'status' => (string) $announcement->status,
|
||||
'is_active' => (bool) $announcement->is_active,
|
||||
'starts_at' => optional($announcement->starts_at)?->format('Y-m-d\TH:i') ?? '',
|
||||
'ends_at' => optional($announcement->ends_at)?->format('Y-m-d\TH:i') ?? '',
|
||||
'priority' => (int) $announcement->priority,
|
||||
'is_dismissible' => (bool) $announcement->is_dismissible,
|
||||
'dismiss_version' => (int) $announcement->dismiss_version,
|
||||
'gradient_preset' => (string) ($announcement->gradient_preset ?? ''),
|
||||
'theme_preset' => (string) ($announcement->theme_preset ?? ''),
|
||||
'placement' => (string) $announcement->placement,
|
||||
'text_color' => (string) ($announcement->text_color ?? ''),
|
||||
'overlay_opacity' => (int) ($announcement->overlay_opacity ?? 55),
|
||||
'background_image' => (string) ($announcement->background_image ?? ''),
|
||||
'background_image_url' => $this->announcements->toHomepagePayload($announcement)['background_image_url'] ?? null,
|
||||
'remove_background_image' => false,
|
||||
'background_image_file' => null,
|
||||
'primary_link_label' => (string) ($announcement->primary_link_label ?? ''),
|
||||
'primary_link_type' => (string) ($announcement->primary_link_type ?? HomepageAnnouncement::LINK_TYPE_NONE),
|
||||
'primary_link_url' => (string) ($announcement->primary_link_url ?? ''),
|
||||
'primary_link_target_id' => $announcement->primary_link_target_id ? (string) $announcement->primary_link_target_id : '',
|
||||
'secondary_link_label' => (string) ($announcement->secondary_link_label ?? ''),
|
||||
'secondary_link_type' => (string) ($announcement->secondary_link_type ?? HomepageAnnouncement::LINK_TYPE_NONE),
|
||||
'secondary_link_url' => (string) ($announcement->secondary_link_url ?? ''),
|
||||
'secondary_link_target_id' => $announcement->secondary_link_target_id ? (string) $announcement->secondary_link_target_id : '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function serializeForIndex(HomepageAnnouncement $announcement): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) $announcement->id,
|
||||
'title' => (string) $announcement->title,
|
||||
'type' => (string) $announcement->type,
|
||||
'status' => (string) $announcement->status,
|
||||
'is_active' => (bool) $announcement->is_active,
|
||||
'priority' => (int) $announcement->priority,
|
||||
'dismiss_version' => (int) $announcement->dismiss_version,
|
||||
'placement' => (string) $announcement->placement,
|
||||
'starts_at' => optional($announcement->starts_at)?->toIso8601String(),
|
||||
'ends_at' => optional($announcement->ends_at)?->toIso8601String(),
|
||||
'updated_at' => optional($announcement->updated_at)?->toIso8601String(),
|
||||
'edit_url' => route('admin.homepage-announcements.edit', ['homepageAnnouncement' => $announcement]),
|
||||
'destroy_url' => route('admin.homepage-announcements.destroy', ['homepageAnnouncement' => $announcement]),
|
||||
'preview' => $this->announcements->toHomepagePayload($announcement),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<int, array<string, string>>>
|
||||
*/
|
||||
private function options(): array
|
||||
{
|
||||
return [
|
||||
'types' => [
|
||||
['value' => HomepageAnnouncement::TYPE_ANNOUNCEMENT, 'label' => 'Announcement'],
|
||||
['value' => HomepageAnnouncement::TYPE_LAUNCH, 'label' => 'Launch'],
|
||||
['value' => HomepageAnnouncement::TYPE_NEWS, 'label' => 'News'],
|
||||
['value' => HomepageAnnouncement::TYPE_WORLD, 'label' => 'World'],
|
||||
['value' => HomepageAnnouncement::TYPE_EVENT, 'label' => 'Event'],
|
||||
['value' => HomepageAnnouncement::TYPE_NOTICE, 'label' => 'Notice'],
|
||||
['value' => HomepageAnnouncement::TYPE_MAINTENANCE, 'label' => 'Maintenance'],
|
||||
],
|
||||
'statuses' => [
|
||||
['value' => HomepageAnnouncement::STATUS_DRAFT, 'label' => 'Draft'],
|
||||
['value' => HomepageAnnouncement::STATUS_PUBLISHED, 'label' => 'Published'],
|
||||
['value' => HomepageAnnouncement::STATUS_ARCHIVED, 'label' => 'Archived'],
|
||||
],
|
||||
'placements' => [
|
||||
['value' => HomepageAnnouncement::PLACEMENT_HOMEPAGE_AFTER_FEATURED, 'label' => 'Homepage after Featured Artwork'],
|
||||
],
|
||||
'linkTypes' => [
|
||||
['value' => HomepageAnnouncement::LINK_TYPE_NONE, 'label' => 'None'],
|
||||
['value' => HomepageAnnouncement::LINK_TYPE_CUSTOM_URL, 'label' => 'Custom URL'],
|
||||
['value' => HomepageAnnouncement::LINK_TYPE_NEWS, 'label' => 'News article'],
|
||||
['value' => HomepageAnnouncement::LINK_TYPE_WORLD, 'label' => 'World'],
|
||||
['value' => HomepageAnnouncement::LINK_TYPE_ARTWORK, 'label' => 'Artwork'],
|
||||
['value' => HomepageAnnouncement::LINK_TYPE_COLLECTION, 'label' => 'Collection'],
|
||||
['value' => HomepageAnnouncement::LINK_TYPE_GROUP, 'label' => 'Group'],
|
||||
['value' => HomepageAnnouncement::LINK_TYPE_PROFILE, 'label' => 'Profile'],
|
||||
['value' => HomepageAnnouncement::LINK_TYPE_EXPLORE, 'label' => 'Explore'],
|
||||
['value' => HomepageAnnouncement::LINK_TYPE_UPLOAD, 'label' => 'Upload'],
|
||||
],
|
||||
'gradients' => [
|
||||
['value' => HomepageAnnouncement::GRADIENT_NOVA_AURORA, 'label' => 'Nova Aurora'],
|
||||
['value' => HomepageAnnouncement::GRADIENT_DEEP_SPACE, 'label' => 'Deep Space'],
|
||||
['value' => HomepageAnnouncement::GRADIENT_SUNRISE, 'label' => 'Sunrise'],
|
||||
['value' => HomepageAnnouncement::GRADIENT_OCEAN_GLOW, 'label' => 'Ocean Glow'],
|
||||
['value' => HomepageAnnouncement::GRADIENT_SPRING_VIBES, 'label' => 'Spring Vibes'],
|
||||
['value' => HomepageAnnouncement::GRADIENT_FANTASY_REALMS, 'label' => 'Fantasy Realms'],
|
||||
['value' => HomepageAnnouncement::GRADIENT_MINIMAL_LIGHT, 'label' => 'Minimal Light'],
|
||||
['value' => HomepageAnnouncement::GRADIENT_DARK_GLASS, 'label' => 'Dark Glass'],
|
||||
],
|
||||
'themes' => [
|
||||
['value' => 'launch', 'label' => 'Launch'],
|
||||
['value' => 'announcement', 'label' => 'Announcement'],
|
||||
['value' => 'notice', 'label' => 'Notice'],
|
||||
['value' => 'maintenance', 'label' => 'Maintenance'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function currentActor(Request $request): object
|
||||
{
|
||||
return $request->user('controlpanel') ?? $request->user() ?? abort(403, 'Admin access required.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Settings;
|
||||
|
||||
use App\Models\HomepageAnnouncement;
|
||||
use App\Services\HomepageAnnouncementSanitizer;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpsertHomepageAnnouncementRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return (bool) ($this->user('controlpanel') ?? $this->user());
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_active' => $this->boolean('is_active'),
|
||||
'is_dismissible' => $this->boolean('is_dismissible', true),
|
||||
'remove_background_image' => $this->boolean('remove_background_image'),
|
||||
'priority' => $this->filled('priority') ? (int) $this->input('priority') : 0,
|
||||
'dismiss_version' => $this->filled('dismiss_version') ? (int) $this->input('dismiss_version') : 1,
|
||||
'overlay_opacity' => $this->filled('overlay_opacity') ? (int) $this->input('overlay_opacity') : 55,
|
||||
]);
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => ['required', 'string', 'max:180'],
|
||||
'subtitle' => ['nullable', 'string', 'max:255'],
|
||||
'badge_text' => ['nullable', 'string', 'max:100'],
|
||||
'content_html' => ['nullable', 'string'],
|
||||
'type' => ['required', 'string', Rule::in(HomepageAnnouncement::types())],
|
||||
'status' => ['required', 'string', Rule::in(HomepageAnnouncement::statuses())],
|
||||
'is_active' => ['required', 'boolean'],
|
||||
'starts_at' => ['nullable', 'date'],
|
||||
'ends_at' => ['nullable', 'date', 'after_or_equal:starts_at'],
|
||||
'priority' => ['required', 'integer', 'min:-9999', 'max:9999'],
|
||||
'is_dismissible' => ['required', 'boolean'],
|
||||
'dismiss_version' => ['required', 'integer', 'min:1', 'max:999999'],
|
||||
'gradient_preset' => ['nullable', 'string', Rule::in(HomepageAnnouncement::gradientPresets())],
|
||||
'theme_preset' => ['nullable', 'string', 'max:80'],
|
||||
'placement' => ['required', 'string', Rule::in(HomepageAnnouncement::placements())],
|
||||
'text_color' => ['nullable', 'string', 'max:32'],
|
||||
'overlay_opacity' => ['nullable', 'integer', 'min:0', 'max:100'],
|
||||
'background_image' => ['nullable', 'string', 'max:2048'],
|
||||
'background_image_file' => ['nullable', 'file', 'image', 'mimes:jpeg,jpg,png,webp', 'max:5120'],
|
||||
'remove_background_image' => ['nullable', 'boolean'],
|
||||
|
||||
'primary_link_label' => ['nullable', 'string', 'max:80'],
|
||||
'primary_link_type' => ['nullable', 'string', Rule::in(HomepageAnnouncement::linkTypes())],
|
||||
'primary_link_url' => ['nullable', 'string', 'max:2048'],
|
||||
'primary_link_target_id' => ['nullable', 'integer', 'min:1'],
|
||||
|
||||
'secondary_link_label' => ['nullable', 'string', 'max:80'],
|
||||
'secondary_link_type' => ['nullable', 'string', Rule::in(HomepageAnnouncement::linkTypes())],
|
||||
'secondary_link_url' => ['nullable', 'string', 'max:2048'],
|
||||
'secondary_link_target_id' => ['nullable', 'integer', 'min:1'],
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator): void {
|
||||
$sanitizer = app(HomepageAnnouncementSanitizer::class);
|
||||
|
||||
foreach (['primary', 'secondary'] as $prefix) {
|
||||
$type = (string) ($this->input($prefix . '_link_type') ?: HomepageAnnouncement::LINK_TYPE_NONE);
|
||||
$label = trim((string) $this->input($prefix . '_link_label', ''));
|
||||
$url = trim((string) $this->input($prefix . '_link_url', ''));
|
||||
$targetId = (int) $this->input($prefix . '_link_target_id', 0);
|
||||
|
||||
if ($url !== '' && ! $sanitizer->isSafeCustomUrl($url)) {
|
||||
$validator->errors()->add($prefix . '_link_url', 'Use a relative path starting with / or an https:// URL. Unsafe protocols are not allowed.');
|
||||
}
|
||||
|
||||
if ($type !== HomepageAnnouncement::LINK_TYPE_NONE && $label === '') {
|
||||
$validator->errors()->add($prefix . '_link_label', 'Provide a CTA label when this link is enabled.');
|
||||
}
|
||||
|
||||
if ($type === HomepageAnnouncement::LINK_TYPE_CUSTOM_URL && $url === '') {
|
||||
$validator->errors()->add($prefix . '_link_url', 'Provide a URL for custom links.');
|
||||
}
|
||||
|
||||
if (! in_array($type, [HomepageAnnouncement::LINK_TYPE_NONE, HomepageAnnouncement::LINK_TYPE_CUSTOM_URL, HomepageAnnouncement::LINK_TYPE_EXPLORE, HomepageAnnouncement::LINK_TYPE_UPLOAD], true)
|
||||
&& $url === ''
|
||||
&& $targetId < 1) {
|
||||
$validator->errors()->add($prefix . '_link_target_id', 'Provide a target id or a fallback URL for this CTA type.');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user