Skip to content
Merged

Dev #3310

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions app/HomeSlide.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,23 @@ class HomeSlide extends Model
'open_second_new_tab' => 'boolean',
];

/**
* Normalize locale_overrides so empty/invalid JSON from Nova doesn't cause 500.
*/
public function setLocaleOverridesAttribute($value): void
{
if ($value === null || $value === '') {
$this->attributes['locale_overrides'] = null;
return;
}
if (is_string($value)) {
$decoded = json_decode($value, true);
$this->attributes['locale_overrides'] = json_encode(is_array($decoded) ? $decoded : []);
return;
}
$this->attributes['locale_overrides'] = is_array($value) ? json_encode($value) : null;
}

/**
* Display value for current locale: use optional override or translate via lang key (default English).
*/
Expand Down
48 changes: 48 additions & 0 deletions app/Http/Controllers/HomeSlideLocaleOverridesController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace App\Http\Controllers;

use App\HomeSlide;
use App\Nova\HomeSlide as HomeSlideResource;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;

class HomeSlideLocaleOverridesController extends Controller
{
/**
* Export a single slide's locale overrides as CSV. Used by Nova action redirect.
*/
public function export(Request $request): StreamedResponse
{
$id = $request->query('id');
$slide = $id ? HomeSlide::find($id) : null;
if (! $slide) {
abort(404, 'Slide not found.');
}

$filename = 'home-slide-' . $slide->id . '-locale-overrides.csv';
$headers = [
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
];

return response()->streamDownload(function () use ($slide) {
$stream = fopen('php://output', 'w');
fprintf($stream, chr(0xEF) . chr(0xBB) . chr(0xBF));
fputcsv($stream, ['locale', 'title', 'description', 'button_text', 'button2_text']);
$overrides = $slide->locale_overrides ?? [];
$locales = HomeSlideResource::localesSorted();
foreach ($locales as $locale) {
$row = $overrides[$locale] ?? [];
fputcsv($stream, [
$locale,
$row['title'] ?? '',
$row['description'] ?? '',
$row['button_text'] ?? '',
$row['button2_text'] ?? '',
]);
}
fclose($stream);
}, $filename, $headers);
}
}
29 changes: 29 additions & 0 deletions app/Nova/Actions/ExportHomeSlideLocaleOverrides.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace App\Nova\Actions;

use App\HomeSlide;
use Illuminate\Support\Collection;
use Laravel\Nova\Actions\Action;
use Laravel\Nova\Fields\ActionFields;
use Laravel\Nova\Http\Requests\NovaRequest;

class ExportHomeSlideLocaleOverrides extends Action
{
public $name = 'Export locale overrides (CSV)';

public function handle(ActionFields $fields, Collection $models)
{
$slide = $models->first();
if (! $slide instanceof HomeSlide) {
return Action::danger('Please select a homepage slide to export.');
}

return Action::redirect(route('admin.home-slides.export-locale-overrides', ['id' => $slide->id]));
}

public function fields(NovaRequest $request): array
{
return [];
}
}
91 changes: 91 additions & 0 deletions app/Nova/Actions/ImportHomeSlideLocaleOverrides.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

namespace App\Nova\Actions;

use App\HomeSlide;
use Illuminate\Support\Collection;
use Laravel\Nova\Actions\Action;
use Laravel\Nova\Fields\ActionFields;
use Laravel\Nova\Fields\File;
use Laravel\Nova\Http\Requests\NovaRequest;

class ImportHomeSlideLocaleOverrides extends Action
{
public $name = 'Import locale overrides (CSV)';

public function handle(ActionFields $fields, Collection $models)
{
$slide = $models->first();
if (! $slide instanceof HomeSlide) {
return Action::danger('Please select a homepage slide to import into.');
}

$file = $fields->csv_file ?? null;
if (! $file) {
return Action::danger('Please select a CSV file to import.');
}

$path = is_object($file) && method_exists($file, 'getRealPath')
? $file->getRealPath()
: (is_string($file) ? $file : null);
if (! $path || ! is_readable($path)) {
return Action::danger('Could not read the uploaded file.');
}
$rows = array_map('str_getcsv', file($path));
if (empty($rows)) {
return Action::danger('The CSV file is empty.');
}

$header = array_map('trim', array_map('strtolower', $rows[0]));
$localeIndex = array_search('locale', $header, true);
if ($localeIndex === false) {
return Action::danger('CSV must have a "locale" column.');
}

$titleIndex = array_search('title', $header, true);
$descIndex = array_search('description', $header, true);
$btnIndex = array_search('button_text', $header, true);
$btn2Index = array_search('button2_text', $header, true);

$overrides = $slide->locale_overrides ?? [];
for ($i = 1; $i < count($rows); $i++) {
$row = $rows[$i];
if (count($row) <= $localeIndex) {
continue;
}
$locale = trim($row[$localeIndex] ?? '');
if ($locale === '') {
continue;
}
if (! isset($overrides[$locale])) {
$overrides[$locale] = [];
}
if ($titleIndex !== false && isset($row[$titleIndex])) {
$overrides[$locale]['title'] = trim($row[$titleIndex]);
}
if ($descIndex !== false && isset($row[$descIndex])) {
$overrides[$locale]['description'] = trim($row[$descIndex]);
}
if ($btnIndex !== false && isset($row[$btnIndex])) {
$overrides[$locale]['button_text'] = trim($row[$btnIndex]);
}
if ($btn2Index !== false && isset($row[$btn2Index])) {
$overrides[$locale]['button2_text'] = trim($row[$btn2Index]);
}
}

$slide->locale_overrides = $overrides;
$slide->save();

return Action::message('Locale overrides imported. Rows: ' . (count($rows) - 1));
}

public function fields(NovaRequest $request): array
{
return [
File::make('CSV file', 'csv_file')
->rules('required', 'file', 'mimes:csv,txt')
->help('CSV with columns: locale, title, description, button_text, button2_text. Use the Export action to get a template.'),
];
}
}
Loading
Loading