Feature: Adding Product Reviews Importer and views
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Modules\Core\MigrateImport;
|
||||
|
||||
use Modules\Core\MigrateImport\JudgeMe\JudgeMeExportImporter;
|
||||
use Modules\Core\MigrateImport\Shopify\ShopifyExportImporter;
|
||||
|
||||
class ImporterFactory
|
||||
@@ -9,8 +10,9 @@ class ImporterFactory
|
||||
public static function make(ImportSpec $spec): Importer
|
||||
{
|
||||
return match ([$spec->source, $spec->type]) {
|
||||
["shopify", "export"] => new ShopifyExportImporter(),
|
||||
['shopify', 'export'] => new ShopifyExportImporter,
|
||||
// ["shopify", "api"] => new ShopifyApiImporter(),
|
||||
['judgeme', 'export'] => new JudgeMeExportImporter,
|
||||
// ["woocommerce", "export"] => new WooCommerceExportImporter(),
|
||||
// ["woocommerce", "api"] => new WooCommerceApiImporter(),
|
||||
default => throw new \InvalidArgumentException(
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\MigrateImport\JudgeMe;
|
||||
|
||||
class JudgeMeCsvReader
|
||||
{
|
||||
/**
|
||||
* @return array<int, array<string, string>>
|
||||
*/
|
||||
public function read(string $csvPath): array
|
||||
{
|
||||
$handle = fopen($csvPath, 'r');
|
||||
|
||||
if ($handle === false) {
|
||||
throw new \RuntimeException("Could not open CSV file: {$csvPath}");
|
||||
}
|
||||
|
||||
$headers = fgetcsv($handle);
|
||||
$rows = [];
|
||||
|
||||
while (($row = fgetcsv($handle)) !== false) {
|
||||
$rows[] = array_combine($headers, $row);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\MigrateImport\JudgeMe;
|
||||
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Modules\Core\MigrateImport\Importer;
|
||||
use Modules\Core\MigrateImport\ImportSpec;
|
||||
use Modules\Core\MigrateImport\JudgeMe\Resolvers\ProductResolver;
|
||||
use Modules\Core\MigrateImport\Models\ImportMapping;
|
||||
use Modules\Core\Review\Models\ProductReview;
|
||||
|
||||
class JudgeMeExportImporter implements Importer
|
||||
{
|
||||
private const SOURCE = 'judgeme';
|
||||
|
||||
public function __construct(
|
||||
private readonly JudgeMeCsvReader $csvReader = new JudgeMeCsvReader,
|
||||
private readonly ProductResolver $productResolver = new ProductResolver,
|
||||
) {}
|
||||
|
||||
public function import(ImportSpec $spec): void
|
||||
{
|
||||
foreach ($this->csvReader->read($spec->filePath) as $row) {
|
||||
$this->importReview($row);
|
||||
}
|
||||
}
|
||||
|
||||
private function importReview(array $row): void
|
||||
{
|
||||
$handle = trim((string) ($row['product_handle'] ?? ''));
|
||||
$externalId = $row['metaobject_handle'] ?? '';
|
||||
|
||||
$product = $handle !== '' ? $this->productResolver->resolve($handle) : null;
|
||||
|
||||
if (! $product) {
|
||||
Log::warning('JudgeMe import: no product found for handle, skipping review', [
|
||||
'product_handle' => $handle,
|
||||
'metaobject_handle' => $externalId,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$existing = ImportMapping::resolve(self::SOURCE, 'review', $externalId);
|
||||
|
||||
$review = $existing instanceof ProductReview ? $existing : new ProductReview;
|
||||
$review->product_id = $product->id;
|
||||
$review->title = trim((string) ($row['title'] ?? '')) ?: null;
|
||||
$review->body = $row['body'] ?? null;
|
||||
$review->rating = (int) ($row['rating'] ?? 0);
|
||||
$review->reviewed_at = $this->parseDate($row['review_date'] ?? null);
|
||||
$review->reviewer_name = $row['reviewer_name'] ?? null;
|
||||
$review->reviewer_email = trim((string) ($row['reviewer_email'] ?? '')) ?: null;
|
||||
$review->reply = trim((string) ($row['reply'] ?? '')) ?: null;
|
||||
$review->replied_at = $this->parseDate($row['reply_date'] ?? null);
|
||||
$review->location = trim((string) ($row['location'] ?? '')) ?: null;
|
||||
$review->source = trim((string) ($row['source'] ?? '')) ?: null;
|
||||
$review->save();
|
||||
|
||||
ImportMapping::record(self::SOURCE, 'review', $externalId, $review);
|
||||
|
||||
$pictureUrls = $this->parsePictureUrls($row['picture_urls'] ?? null);
|
||||
|
||||
if ($pictureUrls && $review->getMedia(ProductReview::IMAGES_COLLECTION)->isEmpty()) {
|
||||
$this->importImages($review, $pictureUrls);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $urls
|
||||
*/
|
||||
private function importImages(ProductReview $review, array $urls): void
|
||||
{
|
||||
foreach ($urls as $url) {
|
||||
try {
|
||||
$review->addMediaFromUrl($url)->toMediaCollection(ProductReview::IMAGES_COLLECTION);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('JudgeMe import: failed to download review image', [
|
||||
'review_id' => $review->id,
|
||||
'url' => $url,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseDate(?string $value): ?Carbon
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
|
||||
return $value !== '' ? Carbon::parse($value) : null;
|
||||
}
|
||||
|
||||
private function parsePictureUrls(?string $value): ?array
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_map('trim', explode(',', $value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\MigrateImport\JudgeMe\Resolvers;
|
||||
|
||||
use Lunar\Models\Product;
|
||||
use Lunar\Models\Url;
|
||||
|
||||
class ProductResolver
|
||||
{
|
||||
public function resolve(string $handle): ?Product
|
||||
{
|
||||
$url = Url::query()
|
||||
->where('slug', $handle)
|
||||
->where('element_type', (new Product)->getMorphClass())
|
||||
->first();
|
||||
|
||||
return $url?->element;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user