pp-planer/app/Services/SongPrefixPostfixService.php
Thorsten Bus 42b8b5f428 fix(export): copyright/blank slides, bundle-relative media, probundle injection, song links
- populate COPYRIGHT (title/author/copyright/CCLI) + blank slides on every song; songHasContent ignores locked sections
- foreground info/moderation images now bundle-relative (fixes blank images)
- pre-added .probundle injection: Zip64-fix + verbatim .pro extraction (fixes empty bundle)
- nametag subtitle split (text + subtitle); smaller non-bold render
- skip songs with no content slides at export with German warning
- link service agenda songs to SongDB edit modal via #song-<id>
- allow CCLI import of metadata-only songs (no lyric sections)
- expose has_content_slides on service songs; show "Keine Inhaltsfolien"
2026-06-21 09:58:55 +02:00

171 lines
5 KiB
PHP

<?php
namespace App\Services;
use App\Models\Label;
use App\Models\Setting;
use App\Models\Song;
use App\Models\SongArrangement;
use App\Models\SongArrangementSection;
use App\Models\SongSection;
use Illuminate\Support\Facades\DB;
class SongPrefixPostfixService
{
public function ensure(Song $song): void
{
DB::transaction(function () use ($song): void {
$prefixLabelId = $this->resolvePrefixLabelId();
$postfixLabelId = $this->resolvePostfixLabelId();
$prefixSection = $this->ensureLockedSection($song, $prefixLabelId, 0, 'prefix');
$postfixSection = $this->ensureLockedSection($song, $postfixLabelId, PHP_INT_MAX, 'postfix');
$arrangement = $this->resolveDefaultArrangement($song);
$this->ensureSectionInArrangement($arrangement, $prefixSection, 'first');
$this->ensureSectionInArrangement($arrangement, $postfixSection, 'last');
});
}
private function resolvePrefixLabelId(): int
{
$id = Setting::get('song_prefix_label_id');
if ($id !== null && $id !== '') {
return (int) $id;
}
$label = Label::firstOrCreate(
['name' => 'COPYRIGHT'],
['color' => '#24B34C'],
);
Setting::set('song_prefix_label_id', (string) $label->id);
return $label->id;
}
private function resolvePostfixLabelId(): int
{
$id = Setting::get('song_postfix_label_id');
if ($id !== null && $id !== '') {
return (int) $id;
}
$label = Label::firstOrCreate(
['name' => 'BLANK'],
['color' => '#000000'],
);
Setting::set('song_postfix_label_id', (string) $label->id);
return $label->id;
}
/**
* @param 'prefix'|'postfix' $kind
*/
private function ensureLockedSection(Song $song, int $labelId, int $orderHint, string $kind): SongSection
{
$section = SongSection::firstOrCreate(
['song_id' => $song->id, 'label_id' => $labelId],
['order' => $orderHint, 'locked' => true],
);
$section->update(['locked' => true]);
$section->slides()->delete();
$textContent = $kind === 'prefix'
? $this->buildCopyrightText($song)
: '';
$section->slides()->create([
'order' => 1,
'text_content' => $textContent,
]);
return $section;
}
private function buildCopyrightText(Song $song): string
{
$title = trim((string) ($song->title ?? $song->name ?? ''));
$lines = [];
if ($title !== '') {
$lines[] = $title;
}
if (trim((string) $song->author) !== '') {
$lines[] = trim((string) $song->author);
}
if (trim((string) $song->copyright_text) !== '') {
$lines[] = trim((string) $song->copyright_text);
}
if (trim((string) $song->ccli_id) !== '') {
$lines[] = 'CCLI-Liednr. '.trim((string) $song->ccli_id);
}
return implode("\n", $lines);
}
private function resolveDefaultArrangement(Song $song): SongArrangement
{
return SongArrangement::firstOrCreate(
['song_id' => $song->id, 'name' => 'normal'],
['is_default' => true],
);
}
private function ensureSectionInArrangement(
SongArrangement $arrangement,
SongSection $section,
string $position,
): void {
$existing = SongArrangementSection::where('song_arrangement_id', $arrangement->id)
->where('song_section_id', $section->id)
->first();
if ($position === 'first') {
if ($existing === null) {
SongArrangementSection::where('song_arrangement_id', $arrangement->id)
->increment('order');
SongArrangementSection::create([
'song_arrangement_id' => $arrangement->id,
'song_section_id' => $section->id,
'order' => 0,
]);
} else {
if ($existing->order !== 0) {
SongArrangementSection::where('song_arrangement_id', $arrangement->id)
->where('id', '!=', $existing->id)
->where('order', '<', $existing->order)
->increment('order');
$existing->update(['order' => 0]);
}
}
} else {
$maxOrder = SongArrangementSection::where('song_arrangement_id', $arrangement->id)
->where('song_section_id', '!=', $section->id)
->max('order') ?? 0;
if ($existing === null) {
SongArrangementSection::create([
'song_arrangement_id' => $arrangement->id,
'song_section_id' => $section->id,
'order' => $maxOrder + 1,
]);
} else {
$existing->update(['order' => $maxOrder + 1]);
}
}
}
}