23-01-2026

This commit is contained in:
Kevin Adametz 2026-01-23 17:34:40 +01:00
parent 8fd1f4d451
commit 389d5d1820
59 changed files with 9642 additions and 883 deletions

View file

@ -0,0 +1,122 @@
<?php
namespace App\Console\Commands;
use App\Models\NewsletterContact;
use Illuminate\Console\Command;
class CleanupNewsletterBlockedEmails extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'newsletter:cleanup-blocked-emails {--dry-run : Nur anzeigen, ohne zu löschen}';
/**
* The console description of the command.
*
* @var string
*/
protected $description = 'Entfernt Newsletter-Kontakte mit blockierten E-Mail-Adressen (Alias/Proxy von Buchungsplattformen)';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->info('Suche nach blockierten E-Mail-Adressen...');
$dryRun = $this->option('dry-run');
// Liste der blockierten Domains
$blockedDomains = [
'@guest.booking.com',
'@messages.homeaway.com',
'@fewo.check24.de',
'@booking.com',
'@homeaway.com',
'@check24.de',
'@partner.booking.com',
];
$stats = [
'found' => 0,
'deleted' => 0,
];
// Hole alle Newsletter-Kontakte
$contacts = NewsletterContact::all();
$this->info("Prüfe {$contacts->count()} Kontakte...");
$bar = $this->output->createProgressBar($contacts->count());
$bar->start();
foreach ($contacts as $contact) {
$emailLower = strtolower($contact->email);
$isBlockedEmail = false;
foreach ($blockedDomains as $domain) {
if (str_ends_with($emailLower, strtolower($domain))) {
$isBlockedEmail = true;
break;
}
}
if ($isBlockedEmail) {
$stats['found']++;
if ($dryRun) {
$this->newLine();
$this->warn("Würde löschen: {$contact->email} (ID: {$contact->id})");
} else {
// Log erstellen vor dem Löschen
$contact->logs()->create([
'action' => 'deleted',
'description' => 'Kontakt entfernt - blockierte E-Mail-Domain (Buchungsplattform-Alias)',
]);
$contact->delete();
$stats['deleted']++;
}
}
$bar->advance();
}
$bar->finish();
$this->newLine(2);
// Statistiken ausgeben
if ($dryRun) {
$this->info('Dry-Run abgeschlossen!');
$this->table(
['Statistik', 'Anzahl'],
[
['Gefundene blockierte E-Mails', $stats['found']],
]
);
if ($stats['found'] > 0) {
$this->newLine();
$this->info('Führe den Befehl ohne --dry-run aus, um die Kontakte zu löschen:');
$this->comment('php artisan newsletter:cleanup-blocked-emails');
}
} else {
$this->info('Bereinigung abgeschlossen!');
$this->table(
['Statistik', 'Anzahl'],
[
['Gefundene blockierte E-Mails', $stats['found']],
['Gelöschte Kontakte', $stats['deleted']],
]
);
}
return 0;
}
}

View file

@ -0,0 +1,254 @@
<?php
namespace App\Console\Commands;
use App\Models\TravelUserBookingFewo;
use App\Models\TravelUser;
use App\Models\NewsletterContact;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class SyncNewsletterFerienwohnungen extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'newsletter:sync-ferienwohnungen {--force : Force full sync}';
/**
* The console description of the command.
*
* @var string
*/
protected $description = 'Synchronisiert Ferienwohnungs-Buchungen mit Newsletter-Kontakten';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->info('Starte Synchronisation von Ferienwohnungs-Buchungen...');
$force = $this->option('force');
// Statistiken
$stats = [
'processed' => 0,
'created' => 0,
'updated' => 0,
'skipped' => 0,
'errors' => 0,
];
// Hole alle Buchungen mit TravelUser und invoice_number
$query = TravelUserBookingFewo::with(['travel_user'])
->whereNotNull('travel_user_id')
->whereNotNull('invoice_number')
->where('invoice_number', '!=', '')
// Nur wenn invoice_number eine reine Nummer ist (keine Storno etc.)
->whereRaw('invoice_number REGEXP "^[0-9]+$"')
->whereHas('travel_user', function ($q) {
$q->whereNotNull('email')
->where('email', '!=', '');
})
// Nur Buchungen, bei denen die Reise bereits beendet ist (to_date in der Vergangenheit)
->whereNotNull('to_date')
->where('to_date', '<', now());
if (!$force) {
// Nur Buchungen der letzten 30 Tage (basierend auf Rückreisedatum) wenn nicht --force
$query->where('to_date', '>=', now()->subDays(30));
}
$bookings = $query->get();
$this->info("Verarbeite {$bookings->count()} Buchungen...");
$bar = $this->output->createProgressBar($bookings->count());
$bar->start();
foreach ($bookings as $booking) {
try {
$stats['processed']++;
$travelUser = $booking->travel_user;
// Validiere E-Mail
if (!$travelUser || !$travelUser->email || !filter_var($travelUser->email, FILTER_VALIDATE_EMAIL)) {
$stats['skipped']++;
$bar->advance();
continue;
}
// Filtere Alias/Proxy E-Mail-Adressen von Buchungsplattformen
$blockedDomains = [
'@guest.booking.com',
'@messages.homeaway.com',
'@fewo.check24.de',
'@booking.com',
'@homeaway.com',
'@check24.de',
'@partner.booking.com',
];
$emailLower = strtolower($travelUser->email);
$isBlockedEmail = false;
foreach ($blockedDomains as $domain) {
if (str_ends_with($emailLower, strtolower($domain))) {
$isBlockedEmail = true;
break;
}
}
if ($isBlockedEmail) {
$stats['skipped']++;
$bar->advance();
continue;
}
// Prüfe ob invoice_number wirklich eine reine Zahl ist
if (!preg_match('/^[0-9]+$/', $booking->invoice_number)) {
$stats['skipped']++;
$bar->advance();
continue;
}
// Generiere Hash für Duplikat-Erkennung
$syncHash = NewsletterContact::generateSyncHash(
$travelUser->email,
NewsletterContact::SOURCE_BOOKING_FERIENWOHNUNGEN
);
// Suche oder erstelle Kontakt
$contact = NewsletterContact::withTrashed()
->where('email', strtolower(trim($travelUser->email)))
->first();
$isNew = false;
if (!$contact) {
// Neuer Kontakt
$contact = new NewsletterContact();
$isNew = true;
$stats['created']++;
} else {
// Wenn gelöscht, wiederherstellen
if ($contact->trashed()) {
$contact->restore();
}
$stats['updated']++;
}
// Aktualisiere Kontaktdaten
$contact->email = strtolower(trim($travelUser->email));
$contact->firstname = $travelUser->first_name ?: $contact->firstname;
$contact->lastname = $travelUser->last_name ?: $contact->lastname;
// Setze Gruppe Ferienwohnungen
$contact->group_ferienwohnungen = true;
// Source nur bei neuem Kontakt setzen (wenn noch nicht aus Kulturreisen)
if ($isNew) {
$contact->source = NewsletterContact::SOURCE_BOOKING_FERIENWOHNUNGEN;
$contact->subscribed_at = $booking->booking_date ?
\Carbon\Carbon::parse($booking->booking_date) :
$booking->created_at;
}
// Referenz zum TravelUser
$contact->travel_user_id = $travelUser->id;
// Aktualisiere Buchungsstatistiken
// Nur Buchungen mit invoice_number (reine Nummer) zählen
$userBookings = TravelUserBookingFewo::where('travel_user_id', $travelUser->id)
->whereNotNull('invoice_number')
->where('invoice_number', '!=', '')
->whereRaw('invoice_number REGEXP "^[0-9]+$"')
->count();
$contact->total_bookings_ferienwohnungen = $userBookings;
// Letztes Buchungsdatum
$lastBooking = TravelUserBookingFewo::where('travel_user_id', $travelUser->id)
->whereNotNull('invoice_number')
->where('invoice_number', '!=', '')
->whereRaw('invoice_number REGEXP "^[0-9]+$"')
->orderBy('booking_date', 'DESC')
->first();
if ($lastBooking && $lastBooking->booking_date) {
$lastBookingDate = \Carbon\Carbon::parse($lastBooking->booking_date);
if (!$contact->last_booking_at || $lastBookingDate->gt($contact->last_booking_at)) {
$contact->last_booking_at = $lastBookingDate;
}
}
// Letztes Reiseenddatum (to_date) - nur abgeschlossene Reisen
$lastTravelEndBooking = TravelUserBookingFewo::where('travel_user_id', $travelUser->id)
->whereNotNull('invoice_number')
->where('invoice_number', '!=', '')
->whereRaw('invoice_number REGEXP "^[0-9]+$"')
->whereNotNull('to_date')
->where('to_date', '<', now())
->orderBy('to_date', 'DESC')
->first();
if ($lastTravelEndBooking && $lastTravelEndBooking->to_date) {
$lastTravelEndDate = \Carbon\Carbon::parse($lastTravelEndBooking->to_date);
if (!$contact->last_travel_end_date || $lastTravelEndDate->gt($contact->last_travel_end_date)) {
$contact->last_travel_end_date = $lastTravelEndDate;
}
}
// Status
if ($isNew || $contact->status === NewsletterContact::STATUS_INACTIVE) {
$contact->status = NewsletterContact::STATUS_ACTIVE;
}
$contact->sync_hash = $syncHash;
$contact->last_synced_at = now();
$contact->save();
// Log erstellen
if ($isNew) {
$contact->logs()->create([
'action' => 'booking_added',
'description' => 'Kontakt durch Ferienwohnungs-Buchung erstellt',
'metadata' => [
'booking_id' => $booking->id,
'invoice_number' => $booking->invoice_number,
],
]);
}
} catch (\Exception $e) {
$stats['errors']++;
$this->error("Fehler bei Buchung {$booking->id}: " . $e->getMessage());
}
$bar->advance();
}
$bar->finish();
$this->newLine(2);
// Statistiken ausgeben
$this->info('Synchronisation abgeschlossen!');
$this->table(
['Statistik', 'Anzahl'],
[
['Verarbeitet', $stats['processed']],
['Neu erstellt', $stats['created']],
['Aktualisiert', $stats['updated']],
['Übersprungen', $stats['skipped']],
['Fehler', $stats['errors']],
]
);
return 0;
}
}

View file

@ -0,0 +1,226 @@
<?php
namespace App\Console\Commands;
use App\Models\Booking;
use App\Models\Customer;
use App\Models\NewsletterContact;
use App\Models\Status;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class SyncNewsletterKulturreisen extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'newsletter:sync-kulturreisen {--force : Force full sync}';
/**
* The console description of the command.
*
* @var string
*/
protected $description = 'Synchronisiert Kulturreisen-Buchungen mit Newsletter-Kontakten';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->info('Starte Synchronisation von Kulturreisen-Buchungen...');
$force = $this->option('force');
// Statistiken
$stats = [
'processed' => 0,
'created' => 0,
'updated' => 0,
'skipped' => 0,
'errors' => 0,
];
// Hole alle Buchungen mit Kunden
$query = Booking::with(['customer', 'lead.status'])
->whereNotNull('customer_id')
->whereHas('customer', function ($q) {
$q->whereNotNull('email')
->where('email', '!=', '');
})
// Nur Buchungen, bei denen die Reise bereits beendet ist (end_date in der Vergangenheit)
->whereNotNull('end_date')
->where('end_date', '<', now());
if (!$force) {
// Nur Buchungen der letzten 30 Tage (basierend auf Rückreisedatum) wenn nicht --force
$query->where('end_date', '>=', now()->subDays(30));
}
$bookings = $query->get();
$this->info("Verarbeite {$bookings->count()} Buchungen...");
$bar = $this->output->createProgressBar($bookings->count());
$bar->start();
foreach ($bookings as $booking) {
try {
$stats['processed']++;
$customer = $booking->customer;
// Validiere E-Mail
if (!$customer || !$customer->email || !filter_var($customer->email, FILTER_VALIDATE_EMAIL)) {
$stats['skipped']++;
$bar->advance();
continue;
}
// Filtere Alias/Proxy E-Mail-Adressen von Buchungsplattformen
$blockedDomains = [
'@guest.booking.com',
'@messages.homeaway.com',
'@fewo.check24.de',
'@booking.com',
'@homeaway.com',
'@check24.de',
'@partner.booking.com',
];
$emailLower = strtolower($customer->email);
$isBlockedEmail = false;
foreach ($blockedDomains as $domain) {
if (str_ends_with($emailLower, strtolower($domain))) {
$isBlockedEmail = true;
break;
}
}
if ($isBlockedEmail) {
$stats['skipped']++;
$bar->advance();
continue;
}
// Generiere Hash für Duplikat-Erkennung
$syncHash = NewsletterContact::generateSyncHash(
$customer->email,
NewsletterContact::SOURCE_BOOKING_KULTURREISEN
);
// Suche oder erstelle Kontakt
$contact = NewsletterContact::withTrashed()
->where('email', strtolower(trim($customer->email)))
->first();
$isNew = false;
if (!$contact) {
// Neuer Kontakt
$contact = new NewsletterContact();
$isNew = true;
$stats['created']++;
} else {
// Wenn gelöscht, wiederherstellen
if ($contact->trashed()) {
$contact->restore();
}
$stats['updated']++;
}
// Aktualisiere Kontaktdaten
$contact->email = strtolower(trim($customer->email));
$contact->firstname = $customer->firstname ?: $contact->firstname;
$contact->lastname = $customer->name ?: $contact->lastname;
// Setze Gruppe Kulturreisen
$contact->group_kulturreisen = true;
// Source nur bei neuem Kontakt setzen
if ($isNew) {
$contact->source = NewsletterContact::SOURCE_BOOKING_KULTURREISEN;
$contact->subscribed_at = $booking->booking_date ?: $booking->created_at;
}
// Referenz zum Customer
$contact->customer_id = $customer->id;
// Aktualisiere Buchungsstatistiken
$customerBookings = Booking::where('customer_id', $customer->id)->count();
$contact->total_bookings_kulturreisen = $customerBookings;
// Letztes Buchungsdatum
$lastBooking = Booking::where('customer_id', $customer->id)
->orderBy('booking_date', 'DESC')
->first();
if ($lastBooking && $lastBooking->booking_date) {
$contact->last_booking_at = $lastBooking->booking_date;
}
// Letztes Reiseenddatum (end_date) - nur abgeschlossene Reisen
$lastTravelEndBooking = Booking::where('customer_id', $customer->id)
->whereNotNull('end_date')
->where('end_date', '<', now())
->orderBy('end_date', 'DESC')
->first();
if ($lastTravelEndBooking && $lastTravelEndBooking->end_date) {
if (!$contact->last_travel_end_date || $lastTravelEndBooking->end_date->gt($contact->last_travel_end_date)) {
$contact->last_travel_end_date = $lastTravelEndBooking->end_date;
}
}
// Status
if ($isNew || $contact->status === NewsletterContact::STATUS_INACTIVE) {
$contact->status = NewsletterContact::STATUS_ACTIVE;
}
$contact->sync_hash = $syncHash;
$contact->last_synced_at = now();
$contact->save();
// Log erstellen
if ($isNew) {
$contact->logs()->create([
'action' => 'booking_added',
'description' => 'Kontakt durch Kulturreisen-Buchung erstellt',
'metadata' => [
'booking_id' => $booking->id,
'lead_id' => $booking->lead_id,
],
]);
}
} catch (\Exception $e) {
$stats['errors']++;
$this->error("Fehler bei Buchung {$booking->id}: " . $e->getMessage());
}
$bar->advance();
}
$bar->finish();
$this->newLine(2);
// Statistiken ausgeben
$this->info('Synchronisation abgeschlossen!');
$this->table(
['Statistik', 'Anzahl'],
[
['Verarbeitet', $stats['processed']],
['Neu erstellt', $stats['created']],
['Aktualisiert', $stats['updated']],
['Übersprungen', $stats['skipped']],
['Fehler', $stats['errors']],
]
);
return 0;
}
}

View file

@ -0,0 +1,18 @@
Newsletter-Synchronisation (wie gewohnt):
# Normale Synchronisation (letzte 30 Tage)
php artisan newsletter:sync-ferienwohnungenphp artisan newsletter:sync-kulturreisen
# Vollständige Synchronisation
php artisan newsletter:sync-ferienwohnungen --forcephp artisan newsletter:sync-kulturreisen --force
#Bereinigung bestehender blockierter E-Mails:
# Erst testen (zeigt nur an, was gelöscht würde)
php artisan newsletter:cleanup-blocked-emails --dry-run
# Tatsächlich löschen
php artisan newsletter:cleanup-blocked-emails

View file

@ -0,0 +1,76 @@
<?php
namespace App\Exports;
use App\Models\NewsletterContact;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
class NewsletterExport implements FromCollection, WithHeadings, WithMapping, ShouldAutoSize
{
protected $contacts;
public function __construct($contacts)
{
$this->contacts = $contacts;
}
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
return $this->contacts;
}
/**
* @return array
*/
public function headings(): array
{
return [
'ID',
'E-Mail',
'Vorname',
'Nachname',
'Gruppe Kulturreisen',
'Gruppe Ferienwohnungen',
'Status',
'Herkunft',
'Buchungen Kulturreisen',
'Buchungen Ferienwohnungen',
'Letzte Buchung',
'Letzte Reise',
'Angemeldet am',
'Abgemeldet am',
'Erstellt am',
];
}
/**
* @param NewsletterContact $contact
* @return array
*/
public function map($contact): array
{
return [
$contact->id,
$contact->email,
$contact->firstname,
$contact->lastname,
$contact->group_kulturreisen ? 'Ja' : 'Nein',
$contact->group_ferienwohnungen ? 'Ja' : 'Nein',
$contact->status_label,
$contact->source_label,
$contact->total_bookings_kulturreisen,
$contact->total_bookings_ferienwohnungen,
$contact->last_booking_at ? $contact->last_booking_at->format('d.m.Y') : '',
$contact->last_travel_end_date ? $contact->last_travel_end_date->format('d.m.Y') : '',
$contact->subscribed_at ? $contact->subscribed_at->format('d.m.Y H:i') : '',
$contact->unsubscribed_at ? $contact->unsubscribed_at->format('d.m.Y H:i') : '',
$contact->created_at->format('d.m.Y H:i'),
];
}
}

View file

@ -0,0 +1,185 @@
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Services\NavigationTreeService;
use Illuminate\Http\JsonResponse;
class NavigationController extends Controller
{
protected $navigationService;
public function __construct(NavigationTreeService $navigationService)
{
$this->navigationService = $navigationService;
}
/**
* Gibt den kompletten Navigationsbaum zurück
*
* @return JsonResponse
*/
public function getNavigationTree(): JsonResponse
{
try {
$tree = $this->navigationService->getNavigationTree();
return response()->json([
'success' => true,
'data' => $tree,
'meta' => [
'total_nodes' => $this->navigationService->countNodes($tree),
'generated_at' => now()->toIso8601String()
]
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
/**
* Gibt einen spezifischen Teil des Navigationsbaums zurück
*
* @param int $rootId Die ID des Root-Knotens
* @return JsonResponse
*/
public function getNavigationSubTree(int $rootId): JsonResponse
{
try {
$tree = $this->navigationService->getNavigationSubTree($rootId);
if (!$tree) {
return response()->json([
'success' => false,
'error' => 'Navigation node not found'
], 404);
}
return response()->json([
'success' => true,
'data' => $tree,
'meta' => [
'total_nodes' => $this->navigationService->countNodes([$tree]),
'generated_at' => now()->toIso8601String()
]
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
/**
* Gibt eine flache Liste aller Navigationspunkte zurück (ohne Hierarchie)
*
* @return JsonResponse
*/
public function getFlatNavigationList(): JsonResponse
{
try {
$list = $this->navigationService->getFlatNavigationList();
return response()->json([
'success' => true,
'data' => $list,
'meta' => [
'total_nodes' => count($list),
'generated_at' => now()->toIso8601String()
]
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
/**
* Gibt nur die aktiven Navigationspunkte zurück
*
* @return JsonResponse
*/
public function getActiveNavigationTree(): JsonResponse
{
try {
$tree = $this->navigationService->getNavigationTree(true);
return response()->json([
'success' => true,
'data' => $tree,
'meta' => [
'total_nodes' => $this->navigationService->countNodes($tree),
'generated_at' => now()->toIso8601String()
]
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
/**
* Gibt den Breadcrumb-Pfad für eine bestimmte Seite zurück
*
* @param int $pageId
* @return JsonResponse
*/
public function getBreadcrumb(int $pageId): JsonResponse
{
try {
$breadcrumb = $this->navigationService->getBreadcrumb($pageId);
if (empty($breadcrumb)) {
return response()->json([
'success' => false,
'error' => 'Page not found'
], 404);
}
return response()->json([
'success' => true,
'data' => $breadcrumb,
'meta' => [
'depth' => count($breadcrumb),
'generated_at' => now()->toIso8601String()
]
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
/**
* Löscht den Navigation-Cache
*
* @return JsonResponse
*/
public function clearCache(): JsonResponse
{
try {
$this->navigationService->clearCache();
return response()->json([
'success' => true,
'message' => 'Navigation cache cleared successfully'
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
}

View file

@ -8,12 +8,13 @@ use App\Models\News;
use App\Models\Page;
use Carbon\Carbon;
use IqContent\LaravelFilemanager\Lfm;
use Illuminate\Support\Str;
use Request;
class CMSNewsController extends Controller
{
/*
/*
* Create a new controller instance.
*
* @return void
@ -21,29 +22,25 @@ class CMSNewsController extends Controller
public function __construct()
{
$this->middleware(['admin', '2fa']);
}
public function index()
{
$data = [
'news' => News::all(),//News::where('lvl', 1)->get(),
'news' => News::all(), //News::where('lvl', 1)->get(),
];
return view('cms.news.index', $data);
}
public function detail($id)
{
if($id === "new") {
if ($id === "new") {
$news = new News();
$id = 'new';
$news->status = 1;
$news->content_new = "";
}else{
} else {
$news = News::findOrFail($id);
$id = $news->id;
}
@ -53,26 +50,25 @@ class CMSNewsController extends Controller
'lfm_helper' => app(Lfm::class),
];
return view('cms.news.detail', $data);
}
public function store($id)
{
$data = Request::all();
if($id === "new") {
if ($id === "new") {
$news = new News();
$news->model = 'news';
$news->owner_second = 0;
$news->show_in_navi = 1;
$news->catalog_id = 1;
}else{
} else {
$news = News::findOrFail($id);
}
$news->title = $data['title'];
$news->status = isset($data['status']) ? true : false;
$news->slug = $data['slug'];
$news->slug = Str::slug($data['slug']);
$news->date = $data['date'];
$news->content_new = $data['content_new'];
$news->box_body = $data['image'];
@ -80,37 +76,37 @@ class CMSNewsController extends Controller
$news->pagetitle = $data['pagetitle'];
$news->keywords = $data['keywords'];
$news->order = (new Carbon($news->date))->format('Ymd')*-1;
$news->order = (new Carbon($news->date))->format('Ymd') * -1;
$root_news = News::where('cms_settings', 'news_root')->first();
if($id != $root_news->id){
if ($id != $root_news->id) {
//root ID = 3126
$news->lvl = 1;
$news->owner = $root_news->id;
$news->parent_id = $root_news->id;
$news->tree_root = $root_news->id;
if($first_news = $root_news->children->first()){
if ($first_news = $root_news->children->first()) {
$news->lft = $first_news->lft;
$news->rgt = $first_news->rgt;
}else{
$news->lft = $root_news->lft +1;
$news->rgt = $root_news->lft +2;
} else {
$news->lft = $root_news->lft + 1;
$news->rgt = $root_news->lft + 2;
}
}
$news->save();
\Session()->flash('alert-save', '1');
return redirect(route('cms_news_detail', [$news->id]));
}
public function delete($id){
public function delete($id)
{
$news = News::findOrFail($id);
//TODO
//check for delete, only delete lvl 2 .,...?
if ($news->lvl != 1){
if ($news->lvl != 1) {
abort(404);
die();
}
@ -119,6 +115,4 @@ class CMSNewsController extends Controller
\Session()->flash('alert-success', __('News gelöscht'));
return redirect(route('cms_news'));
}
}

View file

@ -0,0 +1,165 @@
<?php
namespace App\Http\Controllers;
use App\Services\NavigationTreeService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class NavigationTreeController extends Controller
{
protected $navigationService;
public function __construct(NavigationTreeService $navigationService)
{
$this->navigationService = $navigationService;
}
/**
* Zeigt die Navigationsbaum-Übersicht
*
* @return \Illuminate\View\View
*/
public function index()
{
return view('navigation.index');
}
/**
* Gibt die Navigationsbaum-Daten als JSON zurück (Frontend-Struktur)
*
* @param Request $request
* @return JsonResponse
*/
public function getData(Request $request): JsonResponse
{
try {
$includeHidden = $request->get('include_hidden', true);
$tree = $this->navigationService->getFrontendNavigationTree($includeHidden);
return response()->json([
'success' => true,
'data' => $tree,
'meta' => [
'total_nodes' => $this->navigationService->countNodes($tree),
'include_hidden' => $includeHidden,
'structure' => 'frontend'
]
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
/**
* Sucht im Navigationsbaum
*
* @param Request $request
* @return JsonResponse
*/
public function search(Request $request): JsonResponse
{
try {
$query = $request->get('query', '');
$flatList = $this->navigationService->getFlatNavigationList();
// Filtere nach Suchbegriff
$results = array_filter($flatList, function ($node) use ($query) {
return stripos($node['title'], $query) !== false
|| stripos($node['slug'], $query) !== false
|| stripos($node['url'], $query) !== false;
});
return response()->json([
'success' => true,
'data' => array_values($results),
'meta' => [
'query' => $query,
'total_results' => count($results)
]
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
/**
* Exportiert den Navigationsbaum als JSON-Datei
*
* @param Request $request
* @return \Illuminate\Http\Response
*/
public function export(Request $request)
{
try {
$includeHidden = $request->get('include_hidden', true);
$tree = $this->navigationService->getFrontendNavigationTree($includeHidden);
$filename = 'navigation-tree-frontend-' . date('Y-m-d-His') . '.json';
$json = json_encode($tree, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
return response($json)
->header('Content-Type', 'application/json')
->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
} catch (\Exception $e) {
return back()->with('error', 'Export fehlgeschlagen: ' . $e->getMessage());
}
}
/**
* Löscht den Cache
*
* @return \Illuminate\Http\RedirectResponse
*/
public function clearCache()
{
try {
$this->navigationService->clearCache();
return back()->with('success', 'Navigation-Cache erfolgreich gelöscht');
} catch (\Exception $e) {
return back()->with('error', 'Cache-Löschung fehlgeschlagen: ' . $e->getMessage());
}
}
/**
* Zeigt die Statistiken
*
* @return JsonResponse
*/
public function stats(): JsonResponse
{
try {
$allTree = $this->navigationService->getNavigationTree(false);
$activeTree = $this->navigationService->getNavigationTree(true);
$flatList = $this->navigationService->getFlatNavigationList();
// Zähle verschiedene Typen
$stats = [
'total_pages' => count($flatList),
'total_nodes' => $this->navigationService->countNodes($allTree),
'active_nodes' => $this->navigationService->countNodes($activeTree),
'inactive_nodes' => $this->navigationService->countNodes($allTree) - $this->navigationService->countNodes($activeTree),
'travel_programs' => count(array_filter($flatList, fn($n) => $n['is_travel_program'])),
'fewo_lodgings' => count(array_filter($flatList, fn($n) => $n['is_fewo_lodging'])),
'country_pages' => count(array_filter($flatList, fn($n) => $n['is_country_page'])),
];
return response()->json([
'success' => true,
'data' => $stats
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
}

View file

@ -0,0 +1,391 @@
<?php
namespace App\Http\Controllers;
use App\Models\NewsletterContact;
use App\Models\NewsletterLog;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Validator;
use Yajra\DataTables\Facades\DataTables;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\NewsletterExport;
use Carbon\Carbon;
class NewsletterController extends Controller
{
public function __construct()
{
$this->middleware(['admin', '2fa']);
}
/**
* Liste aller Newsletter-Kontakte
*/
public function index()
{
$data = [
'statistics' => $this->getStatistics(),
];
return view('newsletter.index', $data);
}
/**
* DataTables Daten für die Liste
*/
public function getDatatable(Request $request)
{
$query = NewsletterContact::query()
->select([
'id',
'email',
'firstname',
'lastname',
'group_kulturreisen',
'group_ferienwohnungen',
'status',
'source',
'total_bookings_kulturreisen',
'total_bookings_ferienwohnungen',
'last_booking_at',
'last_travel_end_date',
'created_at',
]);
// Filter nach Gruppe
if ($request->has('group') && $request->group != '') {
if ($request->group == 'kulturreisen') {
$query->where('group_kulturreisen', true);
} elseif ($request->group == 'ferienwohnungen') {
$query->where('group_ferienwohnungen', true);
}
}
// Filter nach Status
if ($request->has('status') && $request->status != '') {
$query->where('status', $request->status);
}
// Filter nach Source
if ($request->has('source') && $request->source != '') {
$query->where('source', $request->source);
}
// Filter nach Datum der letzten Buchung (von)
if ($request->has('travel_from') && $request->travel_from != '') {
$query->whereDate('last_travel_end_date', '>=', Carbon::parse($request->travel_from)->format('Y-m-d H:i:s'));
}
// Filter nach Datum der letzten Buchung (bis)
if ($request->has('travel_to') && $request->travel_to != '') {
$query->whereDate('last_travel_end_date', '<=', Carbon::parse($request->travel_to)->format('Y-m-d H:i:s'));
}
return DataTables::of($query)
->addColumn('full_name', function ($contact) {
return $contact->full_name ?: '-';
})
->addColumn('groups', function ($contact) {
$html = '';
if ($contact->group_kulturreisen) {
$html .= '<span class="badge badge-info">Kulturreisen</span> ';
}
if ($contact->group_ferienwohnungen) {
$html .= '<span class="badge badge-primary">Ferienwohnungen</span>';
}
return $html ?: '-';
})
->addColumn('status_badge', function ($contact) {
return '<span class="badge badge-' . $contact->status_color . '">' . $contact->status_label . '</span>';
})
->addColumn('total_bookings', function ($contact) {
$html = '';
if ($contact->total_bookings_kulturreisen > 0) {
$html .= '<span class="badge badge-secondary">K: ' . $contact->total_bookings_kulturreisen . '</span> ';
}
if ($contact->total_bookings_ferienwohnungen > 0) {
$html .= '<span class="badge badge-secondary">F: ' . $contact->total_bookings_ferienwohnungen . '</span>';
}
return $html ?: '0';
})
->addColumn('last_booking', function ($contact) {
return $contact->last_booking_at ? $contact->last_booking_at->format('d.m.Y') : '-';
})
->addColumn('last_travel', function ($contact) {
return $contact->last_travel_end_date ? $contact->last_travel_end_date->format('d.m.Y') : '-';
})
->addColumn('source_label', function ($contact) {
return $contact->source_label;
})
->addColumn('created', function ($contact) {
return $contact->created_at->format('d.m.Y');
})
->addColumn('actions', function ($contact) {
$html = '<div class="btn-group">';
$html .= '<a href="' . route('newsletter.detail', $contact->id) . '" class="btn btn-sm btn-info"><i class="fa fa-eye"></i></a>';
$html .= '<a href="' . route('newsletter.edit', $contact->id) . '" class="btn btn-sm btn-primary"><i class="fa fa-edit"></i></a>';
$html .= '</div>';
return $html;
})
->rawColumns(['groups', 'status_badge', 'total_bookings', 'actions'])
->make(true);
}
/**
* Detailansicht eines Kontakts
*/
public function detail($id)
{
$contact = NewsletterContact::with(['customer', 'travel_user', 'logs.user'])
->findOrFail($id);
$data = [
'contact' => $contact,
];
return view('newsletter.detail', $data);
}
/**
* Formular zum Bearbeiten eines Kontakts
*/
public function edit($id)
{
if ($id === 'new') {
$contact = new NewsletterContact();
$contact->status = NewsletterContact::STATUS_ACTIVE;
$contact->source = NewsletterContact::SOURCE_MANUAL;
} else {
$contact = NewsletterContact::findOrFail($id);
}
$data = [
'contact' => $contact,
'id' => $id,
];
return view('newsletter.edit', $data);
}
/**
* Speichern eines Kontakts
*/
public function store($id, Request $request)
{
$rules = [
'email' => 'required|email',
'status' => 'required|in:' . implode(',', [
NewsletterContact::STATUS_ACTIVE,
NewsletterContact::STATUS_INACTIVE,
NewsletterContact::STATUS_UNSUBSCRIBED,
NewsletterContact::STATUS_BOUNCED,
]),
];
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
return back()
->withErrors($validator)
->withInput();
}
if ($id === 'new') {
$contact = new NewsletterContact();
$isNew = true;
} else {
$contact = NewsletterContact::findOrFail($id);
$isNew = false;
}
// Speichere alte Werte für Log
$oldStatus = $contact->status;
$oldGroups = [
'kulturreisen' => $contact->group_kulturreisen,
'ferienwohnungen' => $contact->group_ferienwohnungen,
];
$contact->email = strtolower(trim($request->email));
$contact->firstname = $request->firstname;
$contact->lastname = $request->lastname;
$contact->status = $request->status;
$contact->source = $request->source ?? NewsletterContact::SOURCE_MANUAL;
$contact->group_kulturreisen = $request->has('group_kulturreisen');
$contact->group_ferienwohnungen = $request->has('group_ferienwohnungen');
$contact->notes = $request->notes;
if ($isNew) {
$contact->subscribed_at = now();
}
$contact->save();
// Log erstellen
if ($isNew) {
$contact->logs()->create([
'action' => 'subscribed',
'description' => 'Kontakt manuell erstellt',
'user_id' => auth()->id(),
]);
} else {
// Status geändert?
if ($oldStatus !== $contact->status) {
$contact->logs()->create([
'action' => 'status_changed',
'description' => 'Status geändert von ' . NewsletterContact::$statusLabels[$oldStatus] . ' zu ' . $contact->status_label,
'user_id' => auth()->id(),
]);
}
// Gruppen geändert?
if (
$oldGroups['kulturreisen'] !== $contact->group_kulturreisen ||
$oldGroups['ferienwohnungen'] !== $contact->group_ferienwohnungen
) {
$contact->logs()->create([
'action' => 'group_changed',
'description' => 'Gruppenzugehörigkeit geändert',
'user_id' => auth()->id(),
]);
}
}
\Session()->flash('alert-success', $isNew ? 'Kontakt erstellt' : 'Kontakt aktualisiert');
return redirect()->route('newsletter.detail', $contact->id);
}
/**
* Kontakt löschen (soft delete)
*/
public function delete($id)
{
$contact = NewsletterContact::findOrFail($id);
$contact->logs()->create([
'action' => 'unsubscribed',
'description' => 'Kontakt gelöscht',
'user_id' => auth()->id(),
]);
$contact->delete();
\Session()->flash('alert-success', 'Kontakt gelöscht');
return redirect()->route('newsletter.index');
}
/**
* Kontakt abmelden
*/
public function unsubscribe($id, Request $request)
{
$contact = NewsletterContact::findOrFail($id);
$contact->unsubscribe($request->reason);
\Session()->flash('alert-success', 'Kontakt abgemeldet');
return back();
}
/**
* Kontakt wieder aktivieren
*/
public function resubscribe($id)
{
$contact = NewsletterContact::findOrFail($id);
$contact->resubscribe();
\Session()->flash('alert-success', 'Kontakt wieder aktiviert');
return back();
}
/**
* Synchronisation starten
*/
public function sync(Request $request)
{
$type = $request->get('type', 'all');
$force = $request->has('force');
$output = [];
if ($type === 'all' || $type === 'kulturreisen') {
Artisan::call('newsletter:sync-kulturreisen', $force ? ['--force' => true] : []);
$output['kulturreisen'] = Artisan::output();
}
if ($type === 'all' || $type === 'ferienwohnungen') {
Artisan::call('newsletter:sync-ferienwohnungen', $force ? ['--force' => true] : []);
$output['ferienwohnungen'] = Artisan::output();
}
\Session()->flash('alert-success', 'Synchronisation abgeschlossen');
return back()->with('sync_output', $output);
}
/**
* Export von Kontakten
*/
public function export(Request $request)
{
$query = NewsletterContact::query();
// Filter nach Gruppe
if ($request->has('group') && $request->group != '') {
if ($request->group == 'kulturreisen') {
$query->where('group_kulturreisen', true);
} elseif ($request->group == 'ferienwohnungen') {
$query->where('group_ferienwohnungen', true);
}
}
// Filter nach Status
if ($request->has('status') && $request->status != '') {
$query->where('status', $request->status);
}
// Filter nach Source
if ($request->has('source') && $request->source != '') {
$query->where('source', $request->source);
}
// Filter nach Datum der letzten Reise (von)
if ($request->has('travel_from') && $request->travel_from != '') {
$query->whereDate('last_travel_end_date', '>=', Carbon::parse($request->travel_from)->format('Y-m-d H:i:s'));
}
// Filter nach Datum der letzten Reise (bis)
if ($request->has('travel_to') && $request->travel_to != '') {
$query->whereDate('last_travel_end_date', '<=', Carbon::parse($request->travel_to)->format('Y-m-d H:i:s'));
}
$contacts = $query->get();
// Dateiname mit Datum und Filter-Infos
$group = $request->get('group', 'all');
$status = $request->get('status', 'all');
$filename = 'newsletter_' . $group . '_' . $status . '_' . date('Y-m-d') . '.csv';
return Excel::download(new NewsletterExport($contacts), $filename);
}
/**
* Statistiken für Dashboard
*/
private function getStatistics()
{
return [
'total' => NewsletterContact::count(),
'active' => NewsletterContact::where('status', NewsletterContact::STATUS_ACTIVE)->count(),
'kulturreisen' => NewsletterContact::where('group_kulturreisen', true)->count(),
'ferienwohnungen' => NewsletterContact::where('group_ferienwohnungen', true)->count(),
'with_bookings' => NewsletterContact::withBookings()->count(),
'multiple_bookers' => NewsletterContact::multipleBookers()->count(),
'unsubscribed' => NewsletterContact::where('status', NewsletterContact::STATUS_UNSUBSCRIBED)->count(),
'last_sync' => NewsletterContact::max('last_synced_at'),
];
}
}

View file

@ -0,0 +1,326 @@
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Collection;
/**
* Class NewsletterContact
*
* @property int $id
* @property string $email
* @property string|null $firstname
* @property string|null $lastname
* @property bool $group_kulturreisen
* @property bool $group_ferienwohnungen
* @property string $source
* @property string $status
* @property Carbon|null $subscribed_at
* @property Carbon|null $unsubscribed_at
* @property Carbon|null $last_booking_at
* @property Carbon|null $last_travel_end_date
* @property int $total_bookings_kulturreisen
* @property int $total_bookings_ferienwohnungen
* @property int|null $customer_id
* @property int|null $travel_user_id
* @property Carbon|null $last_synced_at
* @property string|null $sync_hash
* @property string|null $notes
* @property Carbon $created_at
* @property Carbon $updated_at
* @property Carbon|null $deleted_at
* @property-read Customer|null $customer
* @property-read TravelUser|null $travel_user
* @property-read Collection|NewsletterLog[] $logs
* @package App\Models
*/
class NewsletterContact extends Model
{
use SoftDeletes;
protected $connection = 'mysql';
protected $table = 'newsletter_contacts';
protected $casts = [
'group_kulturreisen' => 'boolean',
'group_ferienwohnungen' => 'boolean',
'subscribed_at' => 'datetime',
'unsubscribed_at' => 'datetime',
'last_booking_at' => 'datetime',
'last_travel_end_date' => 'datetime',
'last_synced_at' => 'datetime',
'total_bookings_kulturreisen' => 'int',
'total_bookings_ferienwohnungen' => 'int',
'customer_id' => 'int',
'travel_user_id' => 'int',
];
protected $fillable = [
'email',
'firstname',
'lastname',
'group_kulturreisen',
'group_ferienwohnungen',
'source',
'status',
'subscribed_at',
'unsubscribed_at',
'last_booking_at',
'last_travel_end_date',
'total_bookings_kulturreisen',
'total_bookings_ferienwohnungen',
'customer_id',
'travel_user_id',
'last_synced_at',
'sync_hash',
'notes',
];
// Konstanten für Source
const SOURCE_BOOKING_KULTURREISEN = 'booking_kulturreisen';
const SOURCE_BOOKING_FERIENWOHNUNGEN = 'booking_ferienwohnungen';
const SOURCE_NEWSLETTER_SIGNUP = 'newsletter_signup';
const SOURCE_MANUAL = 'manual';
const SOURCE_IMPORT = 'import';
// Konstanten für Status
const STATUS_ACTIVE = 'active';
const STATUS_INACTIVE = 'inactive';
const STATUS_UNSUBSCRIBED = 'unsubscribed';
const STATUS_BOUNCED = 'bounced';
public static $sourceLabels = [
self::SOURCE_BOOKING_KULTURREISEN => 'Buchung Kulturreisen',
self::SOURCE_BOOKING_FERIENWOHNUNGEN => 'Buchung Ferienwohnungen',
self::SOURCE_NEWSLETTER_SIGNUP => 'Newsletter-Anmeldung',
self::SOURCE_MANUAL => 'Manuell',
self::SOURCE_IMPORT => 'Import',
];
public static $statusLabels = [
self::STATUS_ACTIVE => 'Aktiv',
self::STATUS_INACTIVE => 'Inaktiv',
self::STATUS_UNSUBSCRIBED => 'Abgemeldet',
self::STATUS_BOUNCED => 'Bounced',
];
public static $statusColors = [
self::STATUS_ACTIVE => 'success',
self::STATUS_INACTIVE => 'secondary',
self::STATUS_UNSUBSCRIBED => 'warning',
self::STATUS_BOUNCED => 'danger',
];
/**
* Beziehung zum Customer (Kulturreisen)
*/
public function customer()
{
return $this->belongsTo(Customer::class, 'customer_id');
}
/**
* Beziehung zum TravelUser (Ferienwohnungen)
*/
public function travel_user()
{
return $this->belongsTo(TravelUser::class, 'travel_user_id');
}
/**
* Logs zu diesem Kontakt
*/
public function logs()
{
return $this->hasMany(NewsletterLog::class, 'newsletter_contact_id')->orderBy('created_at', 'DESC');
}
/**
* Vollständiger Name
*/
public function getFullNameAttribute()
{
return trim($this->firstname . ' ' . $this->lastname);
}
/**
* Gruppenzugehörigkeit als Array
*/
public function getGroupsAttribute()
{
$groups = [];
if ($this->group_kulturreisen) {
$groups[] = 'Kulturreisen';
}
if ($this->group_ferienwohnungen) {
$groups[] = 'Ferienwohnungen';
}
return $groups;
}
/**
* Gruppenzugehörigkeit als String
*/
public function getGroupsStringAttribute()
{
return implode(', ', $this->groups);
}
/**
* Status-Label
*/
public function getStatusLabelAttribute()
{
return self::$statusLabels[$this->status] ?? $this->status;
}
/**
* Status-Color
*/
public function getStatusColorAttribute()
{
return self::$statusColors[$this->status] ?? 'secondary';
}
/**
* Source-Label
*/
public function getSourceLabelAttribute()
{
return self::$sourceLabels[$this->source] ?? $this->source;
}
/**
* Status-Badge HTML
*/
public function getStatusBadgeAttribute()
{
return '<span class="badge badge-' . $this->status_color . '">' . $this->status_label . '</span>';
}
/**
* Gesamtzahl Buchungen
*/
public function getTotalBookingsAttribute()
{
return $this->total_bookings_kulturreisen + $this->total_bookings_ferienwohnungen;
}
/**
* Ist Kontakt aktiv?
*/
public function isActive()
{
return $this->status === self::STATUS_ACTIVE;
}
/**
* Ist Kontakt abgemeldet?
*/
public function isUnsubscribed()
{
return $this->status === self::STATUS_UNSUBSCRIBED;
}
/**
* Hat Kontakt mindestens eine Buchung?
*/
public function hasBookings()
{
return $this->total_bookings > 0;
}
/**
* Kontakt abmelden
*/
public function unsubscribe($reason = null)
{
$this->status = self::STATUS_UNSUBSCRIBED;
$this->unsubscribed_at = now();
$this->save();
// Log erstellen
$this->logs()->create([
'action' => 'unsubscribed',
'description' => $reason ?? 'Kontakt abgemeldet',
]);
return $this;
}
/**
* Kontakt wieder aktivieren
*/
public function resubscribe()
{
$this->status = self::STATUS_ACTIVE;
$this->unsubscribed_at = null;
$this->save();
// Log erstellen
$this->logs()->create([
'action' => 'subscribed',
'description' => 'Kontakt wieder aktiviert',
]);
return $this;
}
/**
* Hash für Duplikat-Erkennung generieren
*/
public static function generateSyncHash($email, $source)
{
return md5(strtolower(trim($email)) . '_' . $source);
}
/**
* Scope: Nur aktive Kontakte
*/
public function scopeActive($query)
{
return $query->where('status', self::STATUS_ACTIVE);
}
/**
* Scope: Nur Kulturreisen
*/
public function scopeKulturreisen($query)
{
return $query->where('group_kulturreisen', true);
}
/**
* Scope: Nur Ferienwohnungen
*/
public function scopeFerienwohnungen($query)
{
return $query->where('group_ferienwohnungen', true);
}
/**
* Scope: Mit Buchungen
*/
public function scopeWithBookings($query)
{
return $query->where(function ($q) {
$q->where('total_bookings_kulturreisen', '>', 0)
->orWhere('total_bookings_ferienwohnungen', '>', 0);
});
}
/**
* Scope: Mehrfachbucher
*/
public function scopeMultipleBookers($query)
{
return $query->where(function ($q) {
$q->where('total_bookings_kulturreisen', '>', 1)
->orWhere('total_bookings_ferienwohnungen', '>', 1);
});
}
}

View file

@ -0,0 +1,86 @@
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
/**
* Class NewsletterLog
*
* @property int $id
* @property int $newsletter_contact_id
* @property string $action
* @property string|null $description
* @property array|null $metadata
* @property int|null $user_id
* @property Carbon $created_at
* @property Carbon $updated_at
* @property-read NewsletterContact $newsletter_contact
* @property-read SfGuardUser|null $user
* @package App\Models
*/
class NewsletterLog extends Model
{
protected $connection = 'mysql';
protected $table = 'newsletter_logs';
protected $casts = [
'newsletter_contact_id' => 'int',
'user_id' => 'int',
'metadata' => 'array',
];
protected $fillable = [
'newsletter_contact_id',
'action',
'description',
'metadata',
'user_id',
];
// Aktions-Konstanten
const ACTION_SUBSCRIBED = 'subscribed';
const ACTION_UNSUBSCRIBED = 'unsubscribed';
const ACTION_BOOKING_ADDED = 'booking_added';
const ACTION_STATUS_CHANGED = 'status_changed';
const ACTION_GROUP_CHANGED = 'group_changed';
const ACTION_EMAIL_SENT = 'email_sent';
const ACTION_BOUNCED = 'bounced';
public static $actionLabels = [
self::ACTION_SUBSCRIBED => 'Angemeldet',
self::ACTION_UNSUBSCRIBED => 'Abgemeldet',
self::ACTION_BOOKING_ADDED => 'Buchung hinzugefügt',
self::ACTION_STATUS_CHANGED => 'Status geändert',
self::ACTION_GROUP_CHANGED => 'Gruppe geändert',
self::ACTION_EMAIL_SENT => 'E-Mail versendet',
self::ACTION_BOUNCED => 'Bounced',
];
/**
* Beziehung zum Newsletter-Kontakt
*/
public function newsletter_contact()
{
return $this->belongsTo(NewsletterContact::class, 'newsletter_contact_id');
}
/**
* Beziehung zum Admin-User
*/
public function user()
{
return $this->belongsTo(SfGuardUser::class, 'user_id');
}
/**
* Action-Label
*/
public function getActionLabelAttribute()
{
return self::$actionLabels[$this->action] ?? $this->action;
}
}

View file

@ -0,0 +1,681 @@
<?php
namespace App\Services;
use App\Models\Page;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
class NavigationTreeService
{
/**
* Cache-Zeit in Minuten
* @var int
*/
protected $cacheTime = 60;
/**
* Ob Caching aktiviert ist
* @var bool
*/
protected $cacheEnabled = true;
/**
* Aktiviert oder deaktiviert Caching
*
* @param bool $enabled
* @return $this
*/
public function setCacheEnabled(bool $enabled): self
{
$this->cacheEnabled = $enabled;
return $this;
}
/**
* Setzt die Cache-Zeit
*
* @param int $minutes
* @return $this
*/
public function setCacheTime(int $minutes): self
{
$this->cacheTime = $minutes;
return $this;
}
/**
* Löscht den Navigation-Cache
*
* @return void
*/
public function clearCache(): void
{
Cache::forget('navigation_tree_full');
Cache::forget('navigation_tree_active');
Cache::forget('navigation_flat_full');
Cache::forget('navigation_flat_active');
// Lösche auch alle Subtree-Caches
$keys = Cache::get('navigation_subtree_keys', []);
foreach ($keys as $key) {
Cache::forget($key);
}
Cache::forget('navigation_subtree_keys');
}
/**
* Gibt den kompletten Navigationsbaum zurück
*
* @param bool $onlyActive Nur aktive und sichtbare Seiten zurückgeben
* @param bool $onlyShowInNavi Nur Seiten die in der Navigation angezeigt werden sollen
* @return array
*/
public function getNavigationTree(bool $onlyActive = false, bool $onlyShowInNavi = false): array
{
$cacheKey = $onlyActive ? 'navigation_tree_active' : 'navigation_tree_full';
if ($this->cacheEnabled) {
return Cache::remember($cacheKey, $this->cacheTime, function () use ($onlyActive, $onlyShowInNavi) {
return $this->buildNavigationTree($onlyActive, $onlyShowInNavi);
});
}
return $this->buildNavigationTree($onlyActive, $onlyShowInNavi);
}
/**
* Gibt den Navigationsbaum wie im Frontend zurück (nur Länderseiten mit Children)
*
* @param bool $includeHidden Auch ausgeblendete Pages anzeigen
* @return array
*/
public function getFrontendNavigationTree(bool $includeHidden = false): array
{
$cacheKey = 'navigation_tree_frontend_' . ($includeHidden ? 'with_hidden' : 'visible');
if ($this->cacheEnabled) {
return Cache::remember($cacheKey, $this->cacheTime, function () use ($includeHidden) {
return $this->buildFrontendNavigationTree($includeHidden);
});
}
return $this->buildFrontendNavigationTree($includeHidden);
}
/**
* Baut den Frontend-Navigationsbaum auf
*
* @param bool $includeHidden
* @return array
*/
protected function buildFrontendNavigationTree(bool $includeHidden = false): array
{
$tree = [];
// 1. Länderseiten (Hauptnavigation)
$countryPages = $this->getCountryPages($includeHidden);
foreach ($countryPages as $page) {
$node = $this->buildFrontendNode($page, $includeHidden);
if ($node) {
$node['section'] = 'Länder-Navigation';
$tree[] = $node;
}
}
// 2. Ferienwohnungen (USEDOM)
$fewoPages = $this->getFewoPages($includeHidden);
if (!empty($fewoPages)) {
$tree[] = [
'is_section_separator' => true,
'title' => 'USEDOM Ferienwohnungen',
'icon' => 'isv-fewo',
'section' => 'Ferienwohnungen'
];
foreach ($fewoPages as $page) {
$node = $this->buildFrontendNode($page, $includeHidden);
if ($node) {
$node['section'] = 'Ferienwohnungen';
$tree[] = $node;
}
}
}
// 3. Weitere wichtige Seiten (Mehr-Menü)
$morePages = $this->getMoreMenuPages($includeHidden);
if (!empty($morePages)) {
$tree[] = [
'is_section_separator' => true,
'title' => 'Weitere Seiten (Mehr-Menü)',
'icon' => 'fa fa-ellipsis-v',
'section' => 'Mehr'
];
foreach ($morePages as $page) {
$node = $this->buildFrontendNode($page, $includeHidden);
if ($node) {
$node['section'] = 'Mehr';
$tree[] = $node;
}
}
}
return $tree;
}
/**
* Hole alle Länderseiten
*
* @param bool $includeHidden
* @return \Illuminate\Database\Eloquent\Collection
*/
protected function getCountryPages(bool $includeHidden = false)
{
$query = Page::whereNull('parent_id')
->whereNotNull('country_id')
->orderBy('order')
->orderBy('title');
if (!$includeHidden) {
$query->where('show_in_navi', 1);
$query->where('status', 1);
}
return $query->get();
}
/**
* Hole Ferienwohnungs-Übersichtsseite mit Children
*
* @param bool $includeHidden
* @return array
*/
protected function getFewoPages(bool $includeHidden = false)
{
// Suche die Hauptseite "Ferienwohnungen"
$query = Page::where('slug', 'ferienwohnungen')
->orWhere('real_url_path', '/ferienwohnungen');
if (!$includeHidden) {
$query->where('status', 1);
}
$fewoMainPage = $query->first();
if (!$fewoMainPage) {
return [];
}
// Nur die Hauptseite zurückgeben, Children werden über buildFrontendNode geladen
return [$fewoMainPage];
}
/**
* Hole Seiten für "Mehr"-Menü
*
* @param bool $includeHidden
* @return array
*/
protected function getMoreMenuPages(bool $includeHidden = false)
{
// Typische Slugs/Pfade aus dem Mehr-Menü mit ihren möglichen Children
$morePages = [
'ueber-uns' => true, // Könnte Children haben
'reiseversicherung' => false, // Keine Children
'reisefuehrer' => true, // Könnte Children haben
'reisemagazin' => true, // Könnte Children haben
'reisenews' => true // Könnte Children haben
];
$pages = [];
foreach ($morePages as $slug => $hasChildren) {
$query = Page::where('slug', $slug)
->orWhere('real_url_path', '/' . $slug);
if (!$includeHidden) {
$query->where('status', 1);
}
$page = $query->first();
if ($page) {
$pages[] = $page;
}
}
return $pages;
}
/**
* Baut einen Frontend-Node auf (mit Children gruppiert nach beforeTitle)
*
* @param Page $page
* @param bool $includeHidden
* @param bool $loadChildren Soll Children geladen werden?
* @return array|null
*/
protected function buildFrontendNode(Page $page, bool $includeHidden = false, bool $loadChildren = true): ?array
{
$node = $this->buildNodeData($page, true);
if (!$loadChildren) {
$node['children'] = [];
$node['has_children'] = false;
return $node;
}
// Hole Children
$query = Page::where('parent_id', $page->id)
->orderBy('order')
->orderBy('title');
if (!$includeHidden) {
$query->where('show_in_navi', 1);
$query->where('status', 1);
}
$children = $query->get();
// Gruppiere Children nach beforeTitle
$groupedChildren = [
'main' => [],
'infos' => []
];
foreach ($children as $child) {
$childNode = $this->buildNodeData($child, true);
if ($child->before_title === 'Infos') {
$groupedChildren['infos'][] = $childNode;
} else {
$groupedChildren['main'][] = $childNode;
}
}
// Baue finale Children-Liste mit Gruppierung
$finalChildren = [];
// Erst Haupt-Children
foreach ($groupedChildren['main'] as $childNode) {
$finalChildren[] = $childNode;
}
// Dann Info-Children mit Separator
if (!empty($groupedChildren['infos'])) {
$finalChildren[] = [
'is_separator' => true,
'title' => 'Infos',
'icon' => 'fa fa-info-circle'
];
foreach ($groupedChildren['infos'] as $childNode) {
$finalChildren[] = $childNode;
}
}
$node['children'] = $finalChildren;
$node['has_children'] = count($finalChildren) > 0;
return $node;
}
/**
* Baut den Navigationsbaum auf (ohne Caching)
*
* @param bool $onlyActive
* @param bool $onlyShowInNavi
* @return array
*/
protected function buildNavigationTree(bool $onlyActive = false, bool $onlyShowInNavi = false): array
{
// Hole alle Root-Seiten (ohne Parent)
$query = Page::whereNull('parent_id')
->orderBy('order')
->orderBy('title');
if ($onlyActive) {
$query->where('status', 1);
}
if ($onlyShowInNavi) {
$query->where('show_in_navi', 1);
}
$rootPages = $query->get();
$tree = [];
foreach ($rootPages as $page) {
$tree[] = $this->buildNode($page, $onlyActive, $onlyShowInNavi);
}
return $tree;
}
/**
* Gibt einen Teilbaum zurück, beginnend mit einer bestimmten Page
*
* @param int $rootId Die ID der Root-Page
* @param bool $onlyActive Nur aktive Seiten zurückgeben
* @param bool $onlyShowInNavi Nur Seiten die in der Navigation angezeigt werden sollen
* @return array|null
*/
public function getNavigationSubTree(int $rootId, bool $onlyActive = false, bool $onlyShowInNavi = false): ?array
{
$cacheKey = "navigation_subtree_{$rootId}_" . ($onlyActive ? 'active' : 'full');
if ($this->cacheEnabled) {
// Speichere Cache-Key für späteres Löschen
$keys = Cache::get('navigation_subtree_keys', []);
if (!in_array($cacheKey, $keys)) {
$keys[] = $cacheKey;
Cache::put('navigation_subtree_keys', $keys, $this->cacheTime);
}
return Cache::remember($cacheKey, $this->cacheTime, function () use ($rootId, $onlyActive, $onlyShowInNavi) {
return $this->buildSubTree($rootId, $onlyActive, $onlyShowInNavi);
});
}
return $this->buildSubTree($rootId, $onlyActive, $onlyShowInNavi);
}
/**
* Baut einen Teilbaum auf (ohne Caching)
*
* @param int $rootId
* @param bool $onlyActive
* @param bool $onlyShowInNavi
* @return array|null
*/
protected function buildSubTree(int $rootId, bool $onlyActive = false, bool $onlyShowInNavi = false): ?array
{
$page = Page::find($rootId);
if (!$page) {
return null;
}
return $this->buildNode($page, $onlyActive, $onlyShowInNavi);
}
/**
* Gibt eine flache Liste aller Navigationspunkte zurück
*
* @param bool $onlyActive Nur aktive Seiten zurückgeben
* @param bool $onlyShowInNavi Nur Seiten die in der Navigation angezeigt werden sollen
* @return array
*/
public function getFlatNavigationList(bool $onlyActive = false, bool $onlyShowInNavi = false): array
{
$cacheKey = $onlyActive ? 'navigation_flat_active' : 'navigation_flat_full';
if ($this->cacheEnabled) {
return Cache::remember($cacheKey, $this->cacheTime, function () use ($onlyActive, $onlyShowInNavi) {
return $this->buildFlatList($onlyActive, $onlyShowInNavi);
});
}
return $this->buildFlatList($onlyActive, $onlyShowInNavi);
}
/**
* Baut eine flache Liste auf (ohne Caching)
*
* @param bool $onlyActive
* @param bool $onlyShowInNavi
* @return array
*/
protected function buildFlatList(bool $onlyActive = false, bool $onlyShowInNavi = false): array
{
$query = Page::orderBy('order')
->orderBy('title');
if ($onlyActive) {
$query->where('status', 1);
}
if ($onlyShowInNavi) {
$query->where('show_in_navi', 1);
}
$pages = $query->get();
$list = [];
foreach ($pages as $page) {
$list[] = $this->buildNodeData($page, false);
}
return $list;
}
/**
* Baut einen einzelnen Knoten mit allen Kindern rekursiv auf
*
* @param Page $page
* @param bool $onlyActive
* @param bool $onlyShowInNavi
* @return array
*/
protected function buildNode(Page $page, bool $onlyActive = false, bool $onlyShowInNavi = false): array
{
$node = $this->buildNodeData($page, true);
// Hole alle Child-Seiten
$query = Page::where('parent_id', $page->id)
->orderBy('order')
->orderBy('title');
if ($onlyActive) {
$query->where('status', 1);
}
if ($onlyShowInNavi) {
$query->where('show_in_navi', 1);
}
$children = $query->get();
$childNodes = [];
foreach ($children as $child) {
$childNodes[] = $this->buildNode($child, $onlyActive, $onlyShowInNavi);
}
$node['children'] = $childNodes;
$node['has_children'] = count($childNodes) > 0;
return $node;
}
/**
* Erstellt die Datenstruktur für einen einzelnen Navigationspunkt
*
* @param Page $page
* @param bool $includeRelations Beziehungen laden (TravelProgram, etc.)
* @return array
*/
protected function buildNodeData(Page $page, bool $includeRelations = true): array
{
$data = [
'id' => $page->id,
'title' => $page->title,
'title_short' => $page->title_short,
'before_title' => $page->before_title,
'slug' => $page->slug,
'real_url_path' => $page->real_url_path,
'url' => $page->real_url_path ?: $this->buildUrlPath($page),
'status' => $page->status,
'show_in_navi' => $page->show_in_navi,
'order' => $page->order,
'template' => $page->template,
'lvl' => $page->lvl,
'parent_id' => $page->parent_id,
// SEO-Informationen
'pagetitle' => $page->pagetitle,
'description' => $page->description,
'keywords' => $page->keywords,
'canonical_url' => $page->canonical_url,
// Tree-Informationen
'lft' => $page->lft,
'rgt' => $page->rgt,
'tree_root' => $page->tree_root,
// Box-Informationen (für Karten/Boxen in der Navigation)
'box_body' => $page->box_body,
'box_image_url' => $page->box_image_url,
'box_star' => $page->box_star,
'box_discount' => $page->box_discount,
// Zusätzliche Flags
'is_travel_program' => !is_null($page->travel_program),
'is_fewo_lodging' => !is_null($page->fewo_lodging),
'is_country_page' => !is_null($page->country_id),
];
if ($includeRelations) {
// Lade Beziehungen wenn benötigt
if ($page->travel_program) {
$travelProgram = $page->travel_program_content;
if ($travelProgram) {
$data['travel_program'] = [
'id' => $travelProgram->id,
'title' => $travelProgram->title,
'subtitle' => $travelProgram->subtitle,
'status' => $travelProgram->status,
'position' => $travelProgram->position,
'duration' => $travelProgram->duration,
'price_from' => $travelProgram->price_from,
];
}
}
if ($page->fewo_lodging) {
$fewoLodging = $page->fewo_lodging();
if ($fewoLodging) {
$fewo = $fewoLodging->first();
if ($fewo) {
$data['fewo_lodging'] = [
'id' => $fewo->id,
'name' => $fewo->name,
'status' => $fewo->status,
];
}
}
}
if ($page->country_id) {
$country = $page->travel_country;
if ($country) {
$data['country'] = [
'id' => $country->id,
'name' => $country->name,
'title' => $country->title,
'slug' => $country->slug,
];
}
}
}
// CMS Settings (JSON)
if ($page->cms_settings) {
try {
$data['cms_settings'] = json_decode($page->cms_settings, true);
} catch (\Exception $e) {
$data['cms_settings'] = null;
}
}
return $data;
}
/**
* Baut den URL-Pfad für eine Page rekursiv auf (Fallback wenn real_url_path nicht gesetzt)
*
* @param Page $page
* @return string
*/
protected function buildUrlPath(Page $page): string
{
$slugs = [];
$current = $page;
// Sammle alle Slugs von unten nach oben
while ($current) {
if ($current->slug) {
array_unshift($slugs, $current->slug);
}
$current = $current->parent_page;
}
return '/' . implode('/', $slugs);
}
/**
* Zählt die Anzahl der Knoten im Baum
*
* @param array $tree
* @return int
*/
public function countNodes(array $tree): int
{
$count = 0;
foreach ($tree as $node) {
$count++; // Zähle den aktuellen Knoten
if (isset($node['children']) && is_array($node['children'])) {
$count += $this->countNodes($node['children']);
}
}
return $count;
}
/**
* Findet einen Knoten anhand seiner ID im Baum
*
* @param array $tree
* @param int $nodeId
* @return array|null
*/
public function findNodeById(array $tree, int $nodeId): ?array
{
foreach ($tree as $node) {
if ($node['id'] === $nodeId) {
return $node;
}
if (isset($node['children']) && is_array($node['children'])) {
$found = $this->findNodeById($node['children'], $nodeId);
if ($found) {
return $found;
}
}
}
return null;
}
/**
* Gibt den Breadcrumb-Pfad für eine bestimmte Page zurück
*
* @param int $pageId
* @return array
*/
public function getBreadcrumb(int $pageId): array
{
$page = Page::find($pageId);
if (!$page) {
return [];
}
$breadcrumb = [];
$current = $page;
while ($current) {
array_unshift($breadcrumb, [
'id' => $current->id,
'title' => $current->title,
'title_short' => $current->title_short,
'url' => $current->real_url_path ?: $this->buildUrlPath($current),
'slug' => $current->slug,
]);
$current = $current->parent_page;
}
return $breadcrumb;
}
}

View file

@ -1,12 +1,14 @@
<?php
namespace App\Services;
class Util
{
public static function isTestSystem($dev = false){
if(\Config::get('app.domain_tld') === 'test'){
if($dev && config('app.debug') !== true){
public static function isTestSystem($dev = false)
{
if (\Config::get('app.domain_tld') === 'test') {
if ($dev && config('app.debug') !== true) {
return false;
}
return true;
@ -14,119 +16,165 @@ class Util
return false;
}
public static function formatDate(){
if(\App::getLocale() == "en"){
public static function formatDate()
{
if (\App::getLocale() == "en") {
return 'yyyy-mm-dd';
}
return 'dd.mm.yyyy';
}
public static function formatDateDB(){
if(\App::getLocale() == "en"){
public static function formatDateDB()
{
if (\App::getLocale() == "en") {
return 'Y-m-d';
}
return 'd.m.Y';
}
public static function formatDateTimeDB(){
if(\App::getLocale() == "en"){
public static function formatDateTimeDB()
{
if (\App::getLocale() == "en") {
return 'Y-m-d - H:i';
}
return 'd.m.Y - H:i';
}
public static function _format_text($text, $opt = 'html'){
public static function _format_text($text, $opt = 'html')
{
if($opt === 'html'){
if ($opt === 'html') {
return html_entity_decode(nl2br($text));
}
return $text;
}
public static function _format_date($date, $to = 'date'){
if($to === 'datetime'){
public static function _format_date($date, $to = 'date')
{
if ($to === 'datetime') {
return \Carbon::parse($date)->format(\Util::formatDateTimeDB());
}
//date
return \Carbon::parse($date)->format(\Util::formatDateDB());
}
public static function _reformat_date($date, $to = 'date'){
if($to === 'datetime'){
public static function _reformat_date($date, $to = 'date')
{
if ($to === 'datetime') {
return \Carbon::parse($date)->format('Y-m-d - H:i');
}
return \Carbon::parse($date)->format('Y-m-d');
}
public static function _format_number($value){
public static function _format_number($value)
{
return preg_replace("/[^0-9,]/", "", $value);
}
public static function _number_format($value, $dec=2){
public static function _number_format($value, $dec = 2)
{
return number_format(($value), $dec, ',', '.');
}
public static function _first_replace($value, $search='re:', $replace=''){
public static function _first_replace($value, $search = 're:', $replace = '')
{
do {
$before = strlen($value);
$value = trim(preg_replace('/^'.$search.'/i', $replace, $value));
}while($before !== strlen($value));
$value = trim(preg_replace('/^' . $search . '/i', $replace, $value));
} while ($before !== strlen($value));
return $value;
}
public static function _explodeLines($value = false){
if($value){
return explode('#', str_replace(array("\r\n", "\r", "\n"),"#", $value));
public static function _explodeLines($value = false)
{
if ($value) {
return explode('#', str_replace(array("\r\n", "\r", "\n"), "#", $value));
}
return null;
}
public static function _implodeLines($value, $glue=PHP_EOL){
if(is_array($value)){
public static function _implodeLines($value, $glue = PHP_EOL)
{
if (is_array($value)) {
return implode($glue, $value);
}
return $value;
}
public static function _clean_float($value){
public static function _clean_float($value)
{
$groups = explode(".", preg_replace("/[^0-9.-]/", "", str_replace(',', '.', $value)));
$lastGroup = 0;
if(count($groups) > 1){
if (count($groups) > 1) {
$lastGroup = array_pop($groups);
}
$number = implode('', $groups);
return (strlen($lastGroup) < 3) ? floatval($number.'.'.$lastGroup) : floatval($number.$lastGroup);
return (strlen($lastGroup) < 3) ? floatval($number . '.' . $lastGroup) : floatval($number . $lastGroup);
}
public static function sanitize($string, $force_lowercase = true, $anal = false, $substr = false)
{
$strip = array("~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "+", "[", "{", "]",
"}", "\\", "|", ";", ":", "\"", "'", "&#8216;", "&#8217;", "&#8220;", "&#8221;", "&#8211;", "&#8212;",
"—", "–", ",", "<", ".", ">", "/", "?");
$strip = array(
"~",
"`",
"!",
"@",
"#",
"$",
"%",
"^",
"&",
"*",
"(",
")",
"_",
"=",
"+",
"[",
"{",
"]",
"}",
"\\",
"|",
";",
":",
"\"",
"'",
"&#8216;",
"&#8217;",
"&#8220;",
"&#8221;",
"&#8211;",
"&#8212;",
"—",
"–",
",",
"<",
".",
">",
"/",
"?"
);
$clean = trim(str_replace($strip, "", strip_tags($string)));
$clean = preg_replace('/\s+/', "_", $clean);
$clean = ($anal) ? preg_replace("/[^a-zA-Z0-9]/", "", $clean) : $clean ;
if($substr){
$clean = (strlen($clean) > 33) ? substr($clean,0,33) : $clean;
$clean = ($anal) ? preg_replace("/[^a-zA-Z0-9]/", "", $clean) : $clean;
if ($substr) {
$clean = (strlen($clean) > 33) ? substr($clean, 0, 33) : $clean;
}
return ($force_lowercase) ?
(function_exists('mb_strtolower')) ?
mb_strtolower($clean, 'UTF-8') :
strtolower($clean) :
mb_strtolower($clean, 'UTF-8') :
strtolower($clean) :
$clean;
}
public static function replacePlaceholders($search, $replace){
public static function replacePlaceholders($search, $replace)
{
preg_match_all("/\{{(.+?)\}}/", $search, $matches);
if (isset($matches[1]) && count($matches[1]) > 0){
if (isset($matches[1]) && count($matches[1]) > 0) {
foreach ($matches[1] as $key => $value) {
$kvalue = trim($value);
if (array_key_exists($kvalue, $replace)){
if (array_key_exists($kvalue, $replace)) {
$search = preg_replace("/\{\{$value\}\}/", $replace[$kvalue], $search);
}
}
@ -158,7 +206,7 @@ class Util
// $html = preg_replace("/(</?)div/", "$1p", $html);
// $html = preg_replace("/(</?)div/", "$1p", $html);
$html = str_replace('<p> </p>', '', $html);
$html = str_replace('<p></p>', '', $html);
@ -176,7 +224,7 @@ class Util
@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
$removeStyleTags = ['ul', 'li', 'h1', 'h2', 'br'];
foreach ($removeStyleTags as $removeStyleTag){
foreach ($removeStyleTags as $removeStyleTag) {
$elements = $dom->getElementsByTagName($removeStyleTag);
foreach ($elements as $element) {
$element->removeAttribute('style');
@ -184,17 +232,16 @@ class Util
}
$removeFullTags = ['span', 'a'];
foreach ($removeFullTags as $removeFullTag){
foreach ($removeFullTags as $removeFullTag) {
$domElemsToRemove = [];
$elements = $dom->getElementsByTagName($removeFullTag);
foreach ($elements as $element) {
$domElemsToRemove[] = $element;
}
foreach ($domElemsToRemove as $domElem) {
if($removeFullTag == 'span' && strpos($domElem->getAttribute('style'), 'font-weight: 700') !== false){
$new_node = $dom->createTextNode("<strong>".$domElem->nodeValue."</strong>");
}else{
if ($removeFullTag == 'span' && strpos($domElem->getAttribute('style'), 'font-weight: 700') !== false) {
$new_node = $dom->createTextNode("<strong>" . $domElem->nodeValue . "</strong>");
} else {
$new_node = $dom->createTextNode($domElem->nodeValue);
}
$domElem->parentNode->replaceChild($new_node, $domElem);
@ -235,28 +282,28 @@ class Util
$element->parentNode->replaceChild($new_node, $element);
} */
$new_node = $dom->createTextNode($element->nodeValue . "</p>");
$p = $dom->createElement('p', $element->nodeValue);
$div = $dom->createElement('div');
// $new_node = $dom->createElement('div', $new_node);
$div->setAttribute('class', 'mediaInfo');
$div->appendChild($p);
// dump($element);
// die();
//
$element->parentNode->replaceChild($div, $element);
$new_node = $dom->createTextNode($element->nodeValue . "</p>");
$p = $dom->createElement('p', $element->nodeValue);
$div = $dom->createElement('div');
// $new_node = $dom->createElement('div', $new_node);
$div->setAttribute('class', 'mediaInfo');
$div->appendChild($p);
// dump($element);
// die();
//
$element->parentNode->replaceChild($div, $element);
}
$html = $dom->saveHTML();
return $html;
}
public static function prepareCollapseValues(){
if(\Session::has('collapse_shows')){
public static function prepareCollapseValues()
{
if (\Session::has('collapse_shows')) {
$collapse_shows = \Session::get('collapse_shows');
if(strpos($collapse_shows, ',')){
if (strpos($collapse_shows, ',')) {
$collapse_shows = explode(',', $collapse_shows);
return json_encode($collapse_shows);
}
@ -266,32 +313,35 @@ class Util
return json_encode([0 => 'non']);
}
public static function convertArrayWindowsCharset($values){
foreach ($values as $key=>$string){
public static function convertArrayWindowsCharset($values)
{
foreach ($values as $key => $string) {
$values[$key] = self::convertStringWindowsCharset($string);
}
return $values;
}
public static function convertStringWindowsCharset($value) {
public static function convertStringWindowsCharset($value)
{
$charset = mb_detect_encoding($value, "UTF-8, ISO-8859-1, ISO-8859-15", true);
return mb_convert_encoding($value, "Windows-1252", $charset);
}
public static function getMimeFromHeader($http_response_header){
public static function getMimeFromHeader($http_response_header)
{
$pattern = "/^content-type\s*:\s*(.*)$/i";
if (($header = array_values(preg_grep($pattern, $http_response_header))) &&
(preg_match($pattern, $header[0], $match) !== false))
{
(preg_match($pattern, $header[0], $match) !== false)
) {
return $match[1];
}
return "";
}
public static function getExtensionFromMime($mine){
public static function getExtensionFromMime($mine)
{
$mime_types = [
'application/pdf' => 'pdf',
'image/png' => 'png',
@ -304,15 +354,16 @@ class Util
return isset($mime_types[$mine]) ? $mime_types[$mine] : "";
}
public static function getURLasContent($url, $base=false){
$arrContextOptions=array(
"ssl"=>array(
"verify_peer"=>false,
"verify_peer_name"=>false,
public static function getURLasContent($url, $base = false)
{
$arrContextOptions = array(
"ssl" => array(
"verify_peer" => false,
"verify_peer_name" => false,
),
);
$content = file_get_contents($url, false, stream_context_create($arrContextOptions));
if($base){
if ($base) {
$type = pathinfo($url, PATHINFO_EXTENSION);
$base64Data = base64_encode($content);
return 'data:image/' . $type . ';base64,' . $base64Data;
@ -332,6 +383,4 @@ class Util
return $size;
}
}
}
}