Add WooCommerce-style featured image and product gallery flow.
Deploy Ladill Woo Manager / deploy (push) Successful in 24s

Sync full image sets through the plugin, with upload/URL support in the product editor and thumbnails in the list.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-07 00:27:11 +00:00
co-authored by Cursor
parent e3ef23218f
commit 085645fd0f
10 changed files with 434 additions and 67 deletions
+2
View File
@@ -115,6 +115,7 @@ class CatalogWriteService
'short_description' => $input['short_description'] ?? null,
'description' => $input['description'] ?? null,
'categories' => $categories,
'images' => $input['images'] ?? null,
], fn ($value) => $value !== null && $value !== '');
}
@@ -183,6 +184,7 @@ class CatalogWriteService
'short_description' => $payload['short_description'] ?? null,
'description' => $payload['description'] ?? null,
'category_external_ids' => $categoryIds,
'images' => $payload['images'] ?? null,
];
}
+32
View File
@@ -4,6 +4,7 @@ namespace App\Services\Woo;
use App\Models\WooStore;
use Illuminate\Http\Client\Response;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
@@ -32,6 +33,37 @@ class PluginClient
return $this->raw($store, 'DELETE', $path)->successful();
}
/** @return array<string, mixed>|null */
public function uploadMedia(WooStore $store, UploadedFile $file, string $alt = ''): ?array
{
$site = rtrim((string) $store->site_url, '/');
$url = $site.'/wp-json/ladill-woo/v1/media';
$response = Http::acceptJson()
->timeout(60)
->withHeaders(['X-Ladill-Store' => $store->public_id])
->attach(
'file',
file_get_contents($file->getRealPath()) ?: '',
$file->getClientOriginalName() ?: 'upload.jpg',
)
->post($url, array_filter(['alt' => $alt !== '' ? $alt : null]));
if (! $response->successful()) {
Log::warning('Woo plugin media upload failed', [
'store_id' => $store->id,
'status' => $response->status(),
]);
return null;
}
/** @var array<string, mixed>|null $json */
$json = $response->json();
return is_array($json) ? $json : null;
}
/** @return array<string, mixed>|null */
private function request(WooStore $store, string $method, string $path, array $data = []): ?array
{
+4 -2
View File
@@ -20,12 +20,14 @@ class ProductIngestService
$sale = $this->priceMinor($payload['sale_price'] ?? null);
$images = collect((array) ($payload['images'] ?? []))
->map(fn (array $image) => [
->map(fn (array $image, int $index) => [
'id' => (int) ($image['id'] ?? 0),
'src' => (string) ($image['src'] ?? ''),
'alt' => (string) ($image['alt'] ?? ''),
'position' => (int) ($image['position'] ?? $index),
])
->filter(fn (array $image) => $image['src'] !== '')
->filter(fn (array $image) => $image['src'] !== '' || $image['id'] > 0)
->sortBy('position')
->values()
->all();
+106
View File
@@ -0,0 +1,106 @@
<?php
namespace App\Services\Woo;
use App\Models\WooStore;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
class ProductMediaService
{
public function __construct(private PluginClient $client) {}
/** @return list<array{id?: int, src: string, alt?: string, position?: int}> */
public function buildImagesFromRequest(Request $request, WooStore $store): array
{
/** @var list<array<string, mixed>> $images */
$images = json_decode((string) $request->input('product_images', '[]'), true) ?? [];
if (! is_array($images)) {
$images = [];
}
if ($request->hasFile('featured_image_upload')) {
$uploaded = $this->upload($store, $request->file('featured_image_upload'), (string) $request->input('featured_image_alt', ''));
if ($uploaded !== null) {
$images[0] = $uploaded;
}
} elseif ($src = trim((string) $request->input('featured_image_src', ''))) {
$images[0] = [
'src' => $src,
'alt' => trim((string) $request->input('featured_image_alt', '')),
];
}
foreach ($request->file('gallery_uploads', []) as $file) {
if ($file instanceof UploadedFile && $file->isValid()) {
$uploaded = $this->upload($store, $file, '');
if ($uploaded !== null) {
$images[] = $uploaded;
}
}
}
foreach ((array) $request->input('gallery_image_srcs', []) as $src) {
if (! is_string($src)) {
continue;
}
$src = trim($src);
if ($src !== '') {
$images[] = ['src' => $src, 'alt' => ''];
}
}
return $this->normalizeImages($images);
}
/** @return array{id: int, src: string, alt: string}|null */
public function upload(WooStore $store, UploadedFile $file, string $alt = ''): ?array
{
$response = $this->client->uploadMedia($store, $file, $alt);
if (! is_array($response) || empty($response['src'])) {
return null;
}
return [
'id' => (int) ($response['id'] ?? 0),
'src' => (string) $response['src'],
'alt' => (string) ($response['alt'] ?? $alt),
];
}
/** @param list<array<string, mixed>> $images */
private function normalizeImages(array $images): array
{
$normalized = [];
foreach (array_values($images) as $position => $image) {
if (! is_array($image)) {
continue;
}
$src = trim((string) ($image['src'] ?? ''));
$id = (int) ($image['id'] ?? 0);
if ($src === '' && $id <= 0) {
continue;
}
$row = [
'alt' => trim((string) ($image['alt'] ?? '')),
'position' => $position,
];
if ($id > 0) {
$row['id'] = $id;
}
if ($src !== '') {
$row['src'] = $src;
}
$normalized[] = $row;
}
return $normalized;
}
}