presseportale/app/Services/Import/PressReleaseImporter.php
Kevin Adametz 5b8bdf4182
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
12-05-2026 Frontend dev
2026-05-12 18:32:33 +02:00

261 lines
8.6 KiB
PHP

<?php
namespace App\Services\Import;
use App\Enums\Portal;
use App\Enums\PressReleaseStatus;
use App\Models\LegacyImportMap;
use App\Models\PressRelease;
use App\Models\PressReleaseImage;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class PressReleaseImporter
{
private const CHUNK_SIZE = 200;
/** Legacy-Status → neuer Status */
private const STATUS_MAP = [
'new' => PressReleaseStatus::Draft,
'edited' => PressReleaseStatus::Draft,
'prepublished' => PressReleaseStatus::Review,
'published' => PressReleaseStatus::Published,
'rejected' => PressReleaseStatus::Rejected,
];
public function run(ImportContext $ctx): ImportResult
{
$result = new ImportResult;
$conn = $ctx->connection;
$legacyPortal = $ctx->legacyPortalValue();
$portal = $ctx->portalEnum;
// Bilder und Kontakt-Pivots vorladen
$images = DB::connection($conn)
->table('press_release_image')
->get()
->groupBy('press_release_id');
$prContacts = DB::connection($conn)
->table('press_release_contact')
->get()
->groupBy('press_release_id');
DB::connection($conn)
->table('press_release')
->orderBy('id')
->chunk(self::CHUNK_SIZE, function ($rows) use ($ctx, $result, $legacyPortal, $portal, $images, $prContacts): void {
foreach ($rows as $row) {
try {
$this->importRow($row, $ctx, $result, $legacyPortal, $portal, $images, $prContacts);
} catch (\Throwable $e) {
$result->addError("PR legacy_id={$row->id}: {$e->getMessage()}");
}
}
});
return $result;
}
private function importRow(
object $row,
ImportContext $ctx,
ImportResult $result,
string $legacyPortal,
Portal $portal,
Collection $images,
Collection $prContacts,
): void {
$alreadyImported = LegacyImportMap::query()
->where('legacy_portal', $legacyPortal)
->where('legacy_table', 'press_release')
->where('legacy_id', $row->id)
->exists();
if ($alreadyImported && ! $ctx->force) {
$result->incrementSkipped();
return;
}
if ($ctx->dryRun) {
$result->incrementImported();
return;
}
// User aus Import-Map
$userId = null;
if ($row->user_id) {
$userMap = LegacyImportMap::query()
->where('legacy_portal', $legacyPortal)
->where('legacy_table', 'sf_guard_user')
->where('legacy_id', $row->user_id)
->first();
$userId = $userMap?->target_id;
}
if (! $userId) {
$result->incrementSkipped();
return;
}
// Company aus Import-Map
$companyId = null;
if ($row->company_id) {
$companyMap = LegacyImportMap::query()
->where('legacy_portal', $legacyPortal)
->where('legacy_table', 'company')
->where('legacy_id', $row->company_id)
->first();
$companyId = $companyMap?->target_id;
}
// Kategorie aus Import-Map
$categoryId = null;
if ($row->category_id) {
$catMap = LegacyImportMap::query()
->where('legacy_table', 'category')
->where('legacy_id', $row->category_id)
->first();
$categoryId = $catMap?->target_id;
}
// Fallback-Kategorie (erste verfügbare)
if (! $categoryId) {
$categoryId = LegacyImportMap::query()
->where('legacy_table', 'category')
->value('target_id');
}
if (! $categoryId) {
$result->incrementSkipped();
return;
}
$status = self::STATUS_MAP[$row->status] ?? PressReleaseStatus::Draft;
$language = in_array($row->language, ['de', 'en']) ? $row->language : 'de';
// Beim Update (--force): bestehenden Slug behalten, um Kollisionen zu vermeiden.
$existingPr = $alreadyImported
? PressRelease::withoutGlobalScopes()
->where('legacy_portal', $legacyPortal)
->where('legacy_id', $row->id)
->first(['id', 'slug'])
: null;
$slug = $existingPr?->slug ?? $this->uniqueSlug(
$row->slug ?: Str::slug($row->title) ?: 'pressemitteilung',
$portal->value,
$language,
$existingPr?->id,
);
$publishedAt = ($status === PressReleaseStatus::Published)
? ($row->updated_at ?? $row->created_at)
: null;
$pr = PressRelease::withoutTimestamps(function () use (
$legacyPortal, $row, $portal, $userId, $companyId, $categoryId,
$language, $slug, $status, $publishedAt,
): PressRelease {
return PressRelease::withoutGlobalScopes()->updateOrCreate(
['legacy_portal' => $legacyPortal, 'legacy_id' => $row->id],
[
'uuid' => (string) Str::uuid(),
'portal' => $portal->value,
'user_id' => $userId,
'company_id' => $companyId,
'category_id' => $categoryId,
'language' => $language,
'title' => $row->title ?: 'Ohne Titel',
'slug' => $slug,
'text' => $row->text ?: '',
'backlink_url' => $row->backlink_url ?: null,
'keywords' => $row->keywords ?: null,
'status' => $status->value,
'hits' => max(0, (int) $row->hits),
'teaser_begin' => max(0, (int) ($row->teaser_begin ?? 0)),
'teaser_end' => max(0, (int) ($row->teaser_end ?? 0)),
'no_export' => (bool) ($row->no_export ?? false),
'published_at' => $publishedAt,
'created_at' => $row->created_at ?? now(),
'updated_at' => $row->updated_at ?? $row->created_at ?? now(),
]
);
});
foreach ($images->get($row->id, []) as $img) {
PressReleaseImage::withoutGlobalScopes()->updateOrCreate(
['legacy_portal' => $legacyPortal, 'legacy_id' => $img->id],
[
'press_release_id' => $pr->id,
'path' => $img->image,
'title' => $img->title ?: null,
'description' => $img->description ?: null,
'copyright' => $img->copyright ?: null,
'is_preview' => (bool) $img->is_preview_image,
'sort_order' => 0,
]
);
}
// Kontakt-Pivot importieren
$contactIds = [];
foreach ($prContacts->get($row->id, []) as $pivot) {
$contactMap = LegacyImportMap::query()
->where('legacy_portal', $legacyPortal)
->where('legacy_table', 'contact')
->where('legacy_id', $pivot->contact_id)
->first();
if ($contactMap) {
$contactIds[] = $contactMap->target_id;
}
}
if ($contactIds !== []) {
$pr->contacts()->syncWithoutDetaching($contactIds);
}
LegacyImportMap::query()->updateOrCreate(
[
'legacy_portal' => $legacyPortal,
'legacy_table' => 'press_release',
'legacy_id' => $row->id,
],
[
'target_table' => 'press_releases',
'target_id' => $pr->id,
'imported_at' => now(),
]
);
if ($alreadyImported) {
$result->incrementUpdated();
} else {
$result->incrementImported();
}
}
private function uniqueSlug(string $base, string $portal, string $language, ?int $excludeId = null): string
{
$slug = $base;
$i = 2;
while (PressRelease::withoutGlobalScopes()
->where('portal', $portal)
->where('language', $language)
->where('slug', $slug)
->when($excludeId, fn ($q) => $q->where('id', '!=', $excludeId))
->exists()
) {
$slug = $base.'-'.$i++;
}
return $slug;
}
}