Initial Commit

This commit is contained in:
2026-01-07 15:46:00 +01:00
commit 7133af82e3
1454 changed files with 362274 additions and 0 deletions

View File

@@ -0,0 +1,265 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\User;
use App\Models\Post;
use App\Models\NewsletterSubscriber;
use SawaStacks\Utils\Kropify;
use Illuminate\Support\Facades\File;
use App\Models\GeneralSetting;
class AdminController extends Controller
{
/**
* Zeigt das Admin-Dashboard an.
*
* ROUTE: /admin/dashboard
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function adminDashboard(Request $request)
{
$data = [
"pageTitle" => "Dashboard",
"tagsCount" => Post::whereNotNull('tags')->where('tags', '!=', '')->pluck('tags')->count(),
"postsCount" => Post::count(),
"subscribersCount" => NewsletterSubscriber::count(),
"latestPosts" => Post::latest()->take(5)->get(),
"postsWithoutMetaDescriptionCount" => Post::whereNull('meta_description')->orWhere('meta_description', '')->count(),
"postsWithoutMetaKeywordsCount" => Post::whereNull('meta_keywords')->orWhere('meta_keywords', '')->count(),
"postsWithoutTagsCount" => Post::whereNull('tags')->orWhere('tags', '')->count(),
"postsWithoutMetaDescription" => Post::whereNull('meta_description')->orWhere('meta_description', '')->latest()->take(5)->get(),
"postsWithoutMetaKeywords" => Post::whereNull('meta_keywords')->orWhere('meta_keywords', '')->latest()->take(5)->get(),
"postsWithoutTags" => Post::whereNull('tags')->orWhere('tags', '')->latest()->take(5)->get(),
];
return view("back.pages.dashboard", $data);
}
/**
* Meldet den Benutzer ab und beendet die Session.
*
* ROUTE: /logout
* METHOD: POST
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function logoutHandler(Request $request) {
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
if(isset($request->source)) {
return redirect()->back();
}
return redirect()->route("admin.login")->with("success", "Du hast dich ausgeloggt");
}
/**
* Zeigt die Profilseite des eingeloggten Benutzers an.
*
* ROUTE: /admin/profile
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function profileView(Request $request) {
$data = [
"pageTitle" => "Profile"
];
return view("back.pages.profile", $data);
}
/**
* Aktualisiert das Profilbild des eingeloggten Benutzers.
*
* ROUTE: /admin/profile/picture
* METHOD: POST
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function updateProfilePicture(Request $request) {
$user = User::findOrFail(auth()->id());
if (!$request->hasFile('profilePictureFile')) {
return response()->json([
'status' => 0,
'message' => 'Keine Datei hochgeladen.',
]);
}
$file = $request->file('profilePictureFile');
// Der Zielordner relativ zum public/ Verzeichnis:
$path = 'images/users/';
// Dateiname erzeugen
$filename = 'IMG_' . uniqid() . '.png';
// Altes Bild speichern zum Löschen
$oldPicture = $user->getAttributes()['picture'] ?? null;
// Datei mit Kropify speichern (direkt public_path)
$upload = Kropify::getFile($file, $filename)
->setPath($path) // relativ zu public/
->useMove() // Speichert physisch in public/<path>
->save();
if (!$upload) {
return response()->json([
'status' => 0,
'message' => 'Fehler beim Hochladen des Profilfotos.',
]);
}
// Neues Bild wurde erfolgreich gespeichert → altes löschen
if ($oldPicture && File::exists(public_path($path . $oldPicture))) {
File::delete(public_path($path . $oldPicture));
}
// Im User speichern
$user->update([
'picture' => $filename,
]);
return response()->json([
'status' => 1,
'message' => 'Profilfoto erfolgreich hochgeladen.',
'image' => $path . $filename, // falls du die URL direkt brauchst
]);
}
/**
* Zeigt die Seite für allgemeine Einstellungen an.
*
* ROUTE: /admin/settings/general
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function generalSettings(Request $request) {
$data = [
'pageTitle' => "Allgemeine Einstellungen"
];
return view("back.pages.general_settings", $data);
}
/**
* Aktualisiert das Website-Logo in den allgemeinen Einstellungen.
*
* ROUTE: /admin/settings/logo
* METHOD: POST
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function updateLogo(Request $request) {
$settings = GeneralSetting::take(1)->first();
if(!is_null($settings)) {
$path = 'images/site/';
$old_logo = $settings->site_logo;
$file = $request->file("site_logo");
$filename = 'logo_'.uniqid().'.png';
if($request->hasFile('site_logo')) {
$upload = $file->move(public_path($path), $filename);
if($upload) {
if($old_logo != null && File::exists(public_path($path.$old_logo))) {
File::delete(public_path($path.$old_logo));
}
$settings->update(['site_logo' => $filename]);
return response()->json(['status' => 1, 'image_path' => $path.$filename, 'message' => 'Logo wurde erfolgreich geändert']);
} else {
return response()->json(['status' => 0, 'message' => 'Fehler beim hochladen von Logo']);
}
}
} else {
return response()->json(['status' => 0, 'message' => 'Make sure you updated general Settings form First']);
}
}
/**
* Aktualisiert das Website-Favicon in den allgemeinen Einstellungen.
*
* ROUTE: /admin/settings/favicon
* METHOD: POST
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function updateFavicon(Request $request) {
$settings = GeneralSetting::take(1)->first();
if(!is_null($settings)) {
$path = 'images/site/';
$old_favicon = $settings->site_favicon;
$file = $request->file("site_favicon");
$filename = 'favicon_'.uniqid().'.png';
if($request->hasFile('site_favicon')) {
$upload = $file->move(public_path($path), $filename);
if($upload) {
if($old_favicon != null && File::exists(public_path($path.$old_favicon))) {
File::delete(public_path($path.$old_favicon));
}
$settings->update(['site_favicon' => $filename]);
return response()->json(['status' => 1, 'image_path' => $path.$filename, 'message' => 'Favicon wurde erfolgreich geändert']);
} else {
return response()->json(['status' => 0, 'message' => 'Fehler beim hochladen von Favicon']);
}
}
} else {
return response()->json(['status' => 0, 'message' => 'Make sure you updated general Settings form First']);
}
}
/**
* Zeigt die Verwaltungsseite für Kategorien an.
*
* ROUTE: /admin/categories
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function categoriesPage(Request $request) {
$data = [
"pageTitle" => "Kategorien verwalten"
];
return view("back.pages.categories_page", $data);
}
/**
* Zeigt die Verwaltungsseite für den Home-Slider an.
*
* ROUTE: /admin/slider
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function manageSlider(Request $request) {
$data = [
'pageTitle' => 'Manage Home Slider'
];
return view('back.pages.slider', $data);
}
}

View File

@@ -0,0 +1,253 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
use App\UserStatus;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Support\Carbon;
use App\Helpers\CMail;
class AuthController extends Controller
{
/**
* Zeigt das Login-Formular im Backend an.
*
* ROUTE: /login
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function loginForm(Request $request) {
$data = [
"pageTitle" => "Login"
];
return view("back.pages.auth.login", $data);
}
/**
* Zeigt das Formular zum Zurücksetzen des Passworts an.
*
* ROUTE: /forgot-password
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function forgotForm(Request $request) {
$data = [
"pageTitle" => "Forgot Password"
];
return view("back.pages.auth.forgot", $data);
}
/**
* Verarbeitet den Login-Vorgang für Benutzer.
*
* ROUTE: /login
* METHOD: POST
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function loginHandler(Request $request) {
$fieldType = filter_var($request->login_id, FILTER_VALIDATE_EMAIL) ? "email" : "username";
if($fieldType == "email") {
$request->validate([
"login_id" => "required|email|exists:users,email",
"password"=> "required|min:5"
], [
"login_id.required" => "Gebe deine Email oder Benutzernamen ein",
"login_id.email" => "Ungültige Email Adresse",
"login_id.exists" => "Kein Account unter dieser Email gefunden",
"password.required" => "Passwort wird benötigt",
"password.min" => "Bitte gebe mind. 5 Zeichen ein",
]);
} else {
$request->validate([
"login_id" => "required|exists:users,username",
"password"=> "required|min:5"
], [
"login_id.required" => "Gebe deine Email oder Benutzernamen ein",
"login_id.exists" => "Kein Account unter dieser Email gefunden",
"password.required" => "Passwort wird benötigt",
"password.min" => "Bitte gebe mind. 5 Zeichen ein",
]);
}
$creds = array(
$fieldType=>$request->login_id,
"password" => $request->password
);
if(Auth::attempt($creds)) {
// Überprüfen ob Benutzer inactive ist
if(auth()->user()->status == UserStatus::Inactive) {
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route("admin.login")->with("fail", "Dein Account ist derzeit Inactive. Bitte kontaktiere den Support unter (support@larablog.dev) für weitere Informationen");
}
if(auth()->user()->status == UserStatus::Pending) {
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route("admin.login")->with("fail", "Dein Account ist derzeit in Bearbeitung. Bitte kontaktiere den Support unter (support@larablog.dev) für weitere Informationen");
}
return redirect()->route("admin.dashboard");
} else {
return redirect()->route("admin.login")->withInput()->with("fail", "Incorrect Password");
}
}
/**
* Versendet einen Link zum Zurücksetzen des Passworts per E-Mail.
*
* ROUTE: /forgot-password
* METHOD: POST
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function sendPasswordresetLink(Request $request) {
$request->validate([
"email" => "required|email|exists:users,email"
], [
"email.required"=>"Email Adresse wird benötigt",
"email.email" => "Ungültige Email Adresse",
"email.exists" => "Wir konnte diese Email nicht in unseren System finden",
]);
$user = User::where("email", $request->email)->first();
$token = base64_encode(Str::random(64));
$oldToken = DB::table("password_reset_tokens")->where("email", $user->email)->first();
if($oldToken) {
DB::table("password_reset_tokens")->where("email", $request->email)->update([
"token" => $token,
"created_at" => Carbon::now()
]);
} else {
DB::table("password_reset_tokens")->insert([
"email" => $user->email,
"token" => $token,
"created_at" => Carbon::now(),
]);
}
$actionLink = route("admin.reset_password_form", ["token" => $token]);
$data = array("actionlink" => $actionLink, "user" => $user);
$mail_body = view("email-templates.forgot-template", $data)->render();
$mailConfig = array(
"recipient_address" => $user->email,
"recipient_name" => $user->name,
"subject" => "Reset Passwort",
"body" => $mail_body
);
if(CMail::send($mailConfig)) {
return redirect()->route("admin.forgot")->with("success", "Wir haben Ihnen einen Link per E-Mail zugesendet");
} else {
return redirect()->route("admin.forgot")->with("fail", "Leider ist etwas schief gegangen, bitte versuchen Sie es später wieder");
}
}
/**
* Zeigt das Formular zum Zurücksetzen des Passworts an.
*
* ROUTE: /reset-password/{token}
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @param string|null $token Passwort-Reset-Token
* @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse
*/
public function resetForm(Request $request, $token = null) {
$isTokenExists = DB::table("password_reset_tokens")->where("token", $token)->first();
if(!$isTokenExists) {
return redirect()->route("admin.forgot")->with("fail", "Ungültiger Token, fordere einen neuen an");
} else {
$diffMins = Carbon::createFromFormat("Y-m-D H:i:s", $isTokenExists->created_at)->diffInMinutes(Carbon::now());
if($diffMins > 30) {
return redirect()->route("admin.forgot")->with("fail", "Der Reset Link ist leider abgelaufen, fordere einen neuen Link an");
}
$data = [
"pageTitle" => "Passwort zurücksetzen",
"token" => $token
];
return view("back.pages.auth.reset", $data);
}
}
/**
* Verarbeitet das Zurücksetzen des Passworts.
*
* ROUTE: /reset-password
* METHOD: POST
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function resetPasswordHandler(Request $request) {
$request->validate([
"new_password" => "required|min:5|required_with:new_password_confirm|same:new_password_confirm",
"new_password_confirm" => "required"
], [
"new_password.required" => "Neues Passwort wird benötigt",
"new_password_confirm.required" => "Neues Passwort wird benötigt",
"new_password.same" => "Du musst das neue Passwort bestätigen",
"new_password.min" => "Bitte gebe mind. 5 Zeichen ein",
]);
$dbToken = DB::table("password_reset_tokens")->where("token", $request->token)->first();
$user = User::where("email", $dbToken->email)->first();
User::where("email", $user->email)->update([
"password" => Hash::make($request->new_password)
]);
$data = array(
"user" => $user,
"new_password" => $request->new_password
);
$mail_body = view("email-templates.password-changes-template", $data)->render();
$mailConfig = array(
"recipient_address" => $user->email,
"recipient_name" => $user->name,
"subject" => "Passwort geändert",
"body" => $mail_body
);
if(CMail::send($mailConfig)) {
DB::table("password_reset_tokens")->where([
"email" => $dbToken->email,
"token" => $dbToken->token,
])->delete();
return redirect()->route("admin.login")->with("success", "Wir haben Ihr Passwort geändert, Sie können sich nun einloggen");
} else {
return redirect()->route("admin.reset_password_form", ["token" => $dbToken->token])->with("fail", "Leider ist etwas schief gegangen, bitte versuchen Sie es später wieder");
}
}
}

View File

@@ -0,0 +1,326 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Artesaos\SEOTools\Facades\SEOTools;
use Artesaos\SEOTools\Facades\SEOMeta;
use App\Models\Post;
use App\Models\Category;
use App\Models\User;
use App\Helpers\CMail;
class BlogController extends Controller
{
/**
* Zeigt die Startseite der Website an.
*
* ROUTE: /
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request) {
$title = isset(settings()->site_title) ? settings()->site_title : '';
$description = isset(settings()->site_meta_description) ? settings()->site_meta_description : '';
$imgURL = isset(settings()->site_logo) ? asset('/images/site/'.settings()->site_logo) : '';
$keywords = isset(settings()->site_meta_keywords) ? settings()->site_meta_keywords : '';
$currentUrl = url()->current();
/**
* META SEO
*/
SEOTools::setTitle($title, false);
SEOTools::setDescription($description);
SEOMeta::setKeywords($keywords);
/**
* OPEN GRAPH
*/
SEOTools::opengraph()->setUrl($currentUrl);
SEOTools::opengraph()->addImage($imgURL);
SEOTools::opengraph()->addProperty('type', 'articles');
/**
* TWITTER
*/
SEOTools::twitter()->addImage($imgURL);
SEOTools::twitter()->setUrl($currentUrl);
SEOTools::twitter()->setSite('@larablog');
$title = isset(settings()->site_title) ? settings()->site_title : '';
$data = [
'pageTitle' => $title
];
return view('front.pages.index', $data);
}
/**
* Gibt alle sichtbaren Blogbeiträge einer Kategorie aus.
*
* ROUTE: /category/{slug}
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @param string|null $slug Slug der Kategorie
* @return \Illuminate\View\View
*/
public function categoryPosts(Request $request, $slug = null) {
$category = Category::where('slug', $slug)->firstOrFail();
$posts = Post::where('category', $category->id)->where('visibility',1)->paginate(8);
$title = 'Posts in der Kategorie: '.$category->name;
$description = 'Durchstöbern Sie die neuesten Beiträge in der Kategorie '.$category->name.'. Bleiben Sie mit Artikeln, Einblicken und Anleitungen auf dem Laufenden.';
/**
* SEO
*/
SEOTools::setTitle($title, false);
SEOTools::setDescription($description);
SEOTools::opengraph()->setUrl(url()->current());
$data = [
'pageTitle' => $category->name,
'posts' => $posts
];
return view('front.pages.category_posts', $data);
}
/**
* Gibt alle sichtbaren Blogbeiträge eines Autors aus.
*
* ROUTE: /author/{username}
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @param string|null $username Benutzername des Autors
* @return \Illuminate\View\View
*/
public function authorPosts(Request $request, $username = null) {
$author = User::where('username', $username)->firstOrFail();
$posts = Post::where('author_id', $author->id)->where('visibility',1)->orderBy('created_at', 'asc')->paginate(8);
$title = $author->name.' - Blog Posts';
$description = 'Entdecke die neuesten Beiträge von .'.$author->name.' zu verschiedenen Themen';
/**
* SEO
*/
SEOTools::setTitle($title, false);
SEOTools::setDescription($description);
SEOTools::setCanonical(route('author_posts', ['username' => $author->username]));
SEOTools::opengraph()->setUrl(route('author_posts', ['username' => $author->username]));
SEOTools::opengraph()->addProperty('type', 'profile');
SEOTools::opengraph()->setProfile([
'first_name' => $author->name,
'username' => $author->username
]);
$data = [
'pageTitle' => $author->name,
'author' => $author,
'posts' => $posts
];
return view('front.pages.author_posts', $data);
}
/**
* Gibt alle sichtbaren Blogbeiträge zu einem bestimmten Tag aus.
*
* ROUTE: /tag/{tag}
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @param string|null $tag Tag-Bezeichnung
* @return \Illuminate\View\View
*/
public function tagPosts(Request $request, $tag = null) {
$posts = Post::where('tags', 'LIKE', "%{$tag}%")->where('visibility', 1)->paginate(8);
$title = "Beiträge mit dem Tag '{$tag}'";
$description = "Entdecke alle Beiträge in unserem Blog, die mit {$tag} getaggt sind.";
/**
* SEO
*/
SEOTools::setTitle($title, false);
SEOTools::setDescription($description);
SEOTools::setCanonical(url()->current());
SEOTools::opengraph()->setUrl(url()->current());
SEOTools::opengraph()->addProperty('type', 'articles');
$data = [
'pageTitle' => $title,
'tag' => $tag,
'posts' => $posts,
];
return view('front.pages.tag_posts', $data);
}
/**
* Durchsucht Blogbeiträge anhand eines Suchbegriffs.
*
* ROUTE: /search
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function searchPosts(Request $request) {
$query = $request->input('q');
if($query) {
$keywords = explode(' ', $query);
$postsQuery = Post::query();
foreach($keywords as $keyword) {
$postsQuery->orWhere('title', 'LIKE', '%'.$keyword.'%')->orWhere('tags', 'LIKE', '%'.$keyword.'%');
}
$posts = $postsQuery->where('visibility', 1)->orderBy('created_at', 'desc')->paginate(10);
$title = "Such Ergebnis für {$query}";
$description = "Durchsuchen Sie die Suchergebnisse für {$query} auf unserem Blog.";
} else {
$posts = collect();
$title = 'Suche';
$description = "Suchen Sie auf unserer Website nach Blogbeiträgen.";
}
SEOTools::setTitle($title, false);
SEOTools::setDescription($description);
$data = [
'pageTitle' => $title,
'query' => $query,
'posts' => $posts
];
return view('front.pages.search_posts', $data);
}
/**
* Gibt einen einzelnen Blogbeitrag anhand des Slugs aus.
*
* ROUTE: /post/{slug}
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @param string|null $slug Eindeutiger Slug des Beitrags
* @return \Illuminate\View\View
*/
public function readPost(Request $request, $slug = null) {
$post = Post::where('slug', $slug)->firstOrFail();
$relatedPosts = Post::where('category', $post->category)->where('id', '!=', $post->id)->where('visibility', 1)->take(3)->get();
$nextPost = Post::where('id', '>', $post->id)->where('visibility', 1)->orderBy('id', 'asc')->first();
$prevPost = Post::where('id', '<', $post->id)->where('visibility', 1)->orderBy('id', 'desc')->first();
/**
* SEO
*/
$title = $post->title;
$description = ($post->meta_description != '') ? $post->meta_description : words($post->content, 35);
SEOTools::setTitle($title, false);
SEOTools::setDescription($description);
SEOTools::opengraph()->setUrl(route('read_post', ['slug' => $post->slug]));
SEOTools::opengraph()->addProperty('type', 'article');
SEOTools::opengraph()->addImage(asset('images/posts/'.$post->featured_image));
SEOTools::twitter()->setImage(asset('images/posts/'.$post->featured_image));
SEOMeta::addMeta('article:published_time', $post->created_at->toW3CString(), 'property');
SEOMeta::addMeta('article:section', $post->category, 'property');
$data = [
'pageTitle' => $title,
'post' => $post,
'relatedPosts' => $relatedPosts,
'nextPost' => $nextPost,
'prevPost' => $prevPost
];
return view('front.pages.single_post', $data);
}
/**
* Zeigt die Kontaktseite an.
*
* ROUTE: /contact
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function contactPage(Request $request) {
$title = "Kontaktiere uns";
$description = "Sie hassen Formulare? Schreiben Sie uns eine E-Mail.";
/**
* SEO
*/
SEOTools::setTitle($title, false);
SEOTools::setDescription($description);
return view('front.pages.contact');
}
/**
* Verarbeitet das Kontaktformular und versendet eine E-Mail.
*
* ROUTE: /contact/send
* METHOD: POST
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function sendEmail(Request $request) {
$request->validate([
'name' => 'required',
'email' => 'required|email',
'subject' => 'required',
'message' => 'required',
], [
'name.required' => 'Name wird benötigt',
'email.required' => 'Email wird benötigt',
'email.email' => 'Bitte gültige Email angeben',
'subject.required' => 'Titel wird benötigt',
'message.required' => 'Nachricht wird benötigt',
]);
$siteInfo = settings();
$data = [
'name' => $request->name,
'email' => $request->email,
'message' => $request->message,
'subject' => $request->subject
];
$mail_body = view('email-templates.contact-message-template', $data);
$mail_config = [
'from_name' => config('services.mail.from_name'),
'replyToAddress' => $request->email,
'replyToName' => $request->name,
'recipient_address' => $siteInfo->site_email,
'recipient_name' => $siteInfo->site_title,
'subject' => $request->subject,
'body' => $mail_body
];
if(CMail::send($mail_config, true)) {
return redirect()->back()->with("success", "E-Mail wurde gesendet");
} else {
return redirect()->back()->with("fail", "Leider ist etwas schief gegangen, bitte versuchen Sie es später wieder");
}
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

View File

@@ -0,0 +1,354 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\ParentCategory;
use App\Models\Category;
use App\Models\Post;
use Intervention\Image\Laravel\Facades\Image;
use Illuminate\Support\Facades\File;
use App\Models\NewsletterSubscriber;
use App\Jobs\SendNewsletterJob;
class PostController extends Controller
{
/**
* Zeigt das Formular zum Erstellen eines neuen Blogbeitrags an.
*
* ROUTE: /admin/posts/create
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function addPost(Request $request) {
$categories_html = '';
$pcategories = ParentCategory::whereHas('children')->orderBy('name', 'asc')->get();
$categories = Category::where('parent', 0)->orderBy('name', 'asc')->get();
if(count($pcategories) > 0) {
foreach($pcategories as $item) {
$categories_html.='<optgroup label="'.$item->name.'">';
foreach($item->children as $category) {
$categories_html.='<option value="'.$category->id.'">'.$category->name.'</option>';
}
$categories_html.='</optgroup>';
}
}
if(count($categories) > 0) {
foreach($categories as $item) {
$categories_html.='<option value="'.$item->id.'">'.$item->name.'</option>';
}
}
$data = [
'pageTitle' => 'Add new Post',
'categories_html' => $categories_html
];
return view('back.pages.add_post', $data);
}
/**
* Erstellt einen neuen Blogbeitrag und speichert das Beitragsbild.
*
* ROUTE: /admin/posts
* METHOD: POST
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function createPost(Request $request)
{
$request->validate([
'title' => 'required|unique:posts,title',
'content' => 'required',
'category' => 'required|exists:categories,id',
'featured_image' => 'required|mimes:png,jpg,jpeg',
], [
'title.required' => 'Titel wird benötigt',
'title.unique' => 'Titel bereits vergeben',
'content.required' => 'Content wird benötigt',
'category.required' => 'Kategorie wird benötigt',
'category.exists' => 'Kategorie existiert nicht',
'featured_image.required' => 'Image wird benötigt',
'featured_image.mimes' => 'Es sind nur png,jpg,jpeg erlaubt',
]);
if ($request->hasFile('featured_image')) {
$relativePath = 'images/posts/'; // relativ zu /public
$basePath = public_path($relativePath); // absoluter Pfad
$file = $request->file('featured_image');
$filename = $file->getClientOriginalName();
$newFilename = time().'_'.$filename;
// Original speichern
if (!File::isDirectory($basePath)) {
File::makeDirectory($basePath, 0777, true, true);
}
$upload = $file->move($basePath, $newFilename);
if ($upload) {
$originalPath = $basePath.DIRECTORY_SEPARATOR.$newFilename;
$quality = 80;
// resized-Ordner
$resizedDir = $basePath.DIRECTORY_SEPARATOR.'resized'.DIRECTORY_SEPARATOR;
if (!File::isDirectory($resizedDir)) {
File::makeDirectory($resizedDir, 0777, true, true);
}
// 1) Thumbnail (250x250 aus Original)
$thumbPath = $resizedDir.'thumb_'.$newFilename;
Image::read($originalPath)
->cover(250, 250) // v3-Pendant zu fit(250,250)
->save($thumbPath, $quality);
// 2) Resized (512x320 aus Original)
$resizedPath = $resizedDir.'resized_'.$newFilename;
Image::read($originalPath)
->cover(512, 320) // v3-Pendant zu fit(512,320)
->save($resizedPath, $quality);
// Post speichern
$post = new Post();
$post->author_id = auth()->id();
$post->category = $request->category; // ggf. category_id
$post->title = $request->title;
$post->content = $request->content;
$post->featured_image = $newFilename;
$post->tags = $request->tags;
$post->meta_keywords = $request->meta_keywords;
$post->meta_description = $request->meta_description;
$post->visibility = $request->visibility;
$post->structured_data = $request->structured_data;
$saved = $post->save();
if ($saved) {
/**
* Send Email to Newsletter Subscribers
*/
if($request->visibility == 1) {
$latestPost = Post::latest()->first();
if(NewsletterSubscriber::count() > 0) {
$subscribers = NewsLetterSubscriber::pluck('email');
foreach($subscribers as $subscriber_email) {
SendNewsletterJob::dispatch($subscriber_email, $latestPost);
}
$latestPost->is_notified = true;
$latestPost->save();
}
}
return response()->json(['status' => 1, 'message' => 'Post wurde erfolgreich erstellt']);
}
return response()->json(['status' => 0, 'message' => 'Post konnte nicht erstellt werden'], 500);
}
return response()->json(['status' => 0, 'message' => 'Leider gab es ein Problem, versuch es später nochmal'], 500);
}
return response()->json(['status' => 0, 'message' => 'Kein Bild hochgeladen'], 400);
}
/**
* Zeigt die Übersichtsseite aller Blogbeiträge im Backend an.
*
* ROUTE: /admin/posts
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function allPosts(Request $request) {
$data = [
'pageTitle' => 'Posts'
];
return view('back.pages.posts', $data);
}
/**
* Zeigt das Formular zum Bearbeiten eines bestehenden Blogbeitrags an.
*
* ROUTE: /admin/posts/{id}/edit
* METHOD: GET
*
* @param \Illuminate\Http\Request $request
* @param int|null $id ID des Blogbeitrags
* @return \Illuminate\View\View
*/
public function editPost(Request $request, $id = null) {
$post = Post::findOrFail($id);
$categories_html = '';
$pcategories = ParentCategory::whereHas('children')->orderBy('name', 'asc')->get();
$categories = Category::where('parent', 0)->orderBy('name', 'asc')->get();
if(count($pcategories) > 0) {
foreach($pcategories as $item) {
$categories_html.='<optgroup label="'.$item->name.'">';
foreach($item->children as $category) {
$selected = $category->id == $post->category ? 'selected' : '';
$categories_html.='<option value="'.$category->id.'">'.$category->name.'</option>';
}
$categories_html.='</optgroup>';
}
}
if(count($categories) > 0) {
foreach($categories as $item) {
$selected = $item->id == $post->category ? 'selected' : '';
$categories_html.='<option value="'.$item->id.'" '.$selected.'>'.$item->name.'</option>';
}
}
$data = [
'pageTitle' => 'Post Bearbeiten',
'post'=> $post,
'categories_html' => $categories_html
];
return view('back.pages.edit_post', $data);
}
/**
* Aktualisiert einen bestehenden Blogbeitrag (inkl. optionalem Beitragsbild).
*
* ROUTE: /admin/posts/{id}
* METHOD: PUT/PATCH
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function updatePost(Request $request)
{
$post = Post::findOrFail($request->post_id);
$featured_image_name = $post->featured_image;
$request->validate([
'title' => 'required|unique:posts,title,' . $post->id,
'content' => 'required',
'category' => 'required|exists:categories,id',
'featured_image' => 'nullable|mimes:png,jpg,jpeg',
], [
'title.required' => 'Titel wird benötigt',
'title.unique' => 'Titel bereits vergeben',
'content.required' => 'Content wird benötigt',
'category.required' => 'Kategorie wird benötigt',
'category.exists' => 'Kategorie existiert nicht',
'featured_image.mimes' => 'Es sind nur png,jpg,jpeg erlaubt',
]);
if ($request->hasFile('featured_image')) {
$old_featured_image = $post->featured_image;
// Basis-Pfade
$relativePath = 'images/posts/'; // relativ zu /public
$basePath = public_path($relativePath); // absoluter Pfad
$file = $request->file('featured_image');
$filename = $file->getClientOriginalName();
$newFilename = time() . '_' . $filename;
// Ordner für Original sicherstellen
if (!File::isDirectory($basePath)) {
File::makeDirectory($basePath, 0777, true, true);
}
$upload = $file->move($basePath, $newFilename);
if ($upload) {
$originalPath = $basePath . DIRECTORY_SEPARATOR . $newFilename;
// resized-Ordner sicherstellen
$resizedDirRelative = $relativePath . 'resized/';
$resizedDir = public_path($resizedDirRelative);
if (!File::isDirectory($resizedDir)) {
File::makeDirectory($resizedDir, 0777, true, true);
}
// Thumbnail (1:1) v3: read + cover
$thumbPath = $resizedDir . DIRECTORY_SEPARATOR . 'thumb_' . $newFilename;
Image::read($originalPath)
->cover(250, 250)
->save($thumbPath);
// Resized (512x320)
$resizedPath = $resizedDir . DIRECTORY_SEPARATOR . 'resized_' . $newFilename;
Image::read($originalPath)
->cover(512, 320)
->save($resizedPath);
// Alte Bilder löschen
if ($old_featured_image) {
$oldOriginal = public_path($relativePath . $old_featured_image);
$oldResized = public_path($resizedDirRelative . 'resized_' . $old_featured_image);
$oldThumb = public_path($resizedDirRelative . 'thumb_' . $old_featured_image);
if (File::exists($oldOriginal)) {
File::delete($oldOriginal);
}
if (File::exists($oldResized)) {
File::delete($oldResized);
}
if (File::exists($oldThumb)) {
File::delete($oldThumb);
}
}
$featured_image_name = $newFilename;
} else {
return response()->json([
'status' => 0,
'message' => 'Leider gab es ein Fehler beim Hochladen von Image',
]);
}
}
$sendEmailToSubscribers = ($post->visibility == 0 && $post->is_notified == 0 && $request->visibility == 1) ? true : false;
// Post-Daten aktualisieren
$post->author_id = auth()->id();
$post->category = $request->category;
$post->title = $request->title;
$post->content = $request->content;
$post->featured_image = $featured_image_name;
$post->tags = $request->tags;
$post->meta_keywords = $request->meta_keywords;
$post->meta_description = $request->meta_description;
$post->visibility = $request->visibility;
$post->structured_data = $request->structured_data;
$saved = $post->save();
if ($saved) {
/**
* Send Mail Newsletter to Subscribers
*/
if($sendEmailToSubscribers) {
$currentPost = Post::findOrFail($request->post_id);
if(NewsletterSubscriber::count() > 0) {
$subscribers = NewsLetterSubscriber::pluck('email');
foreach($subscribers as $subscriber_email) {
SendNewsletterJob::dispatch($subscriber_email, $currentPost);
}
$currentPost->is_notified = true;
$currentPost->save();
}
}
return response()->json(['status' => 1, 'message' => 'Post wurde erfolgreich bearbeitet']);
}
return response()->json(['status' => 0, 'message' => 'Fehler beim Bearbeiten von Post']);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Spatie\Sitemap\Sitemap;
use Spatie\Sitemap\Tags\Url;
use App\Models\Post;
use App\Models\Category;
use App\Models\User;
class SitemapController extends Controller
{
public function index()
{
$sitemap = Sitemap::create();
$sitemap->add(
Url::create('/')
->setPriority(1.0)
->setChangeFrequency(Url::CHANGE_FREQUENCY_DAILY)
);
$sitemap->add(
Url::create('/contact')
->setPriority(0.8)
->setChangeFrequency(Url::CHANGE_FREQUENCY_MONTHLY)
);
// Nur Posts
$posts = Post::where('published', true)->get(); // falls du ein Feld dafür hast
foreach ($posts as $post) {
$url = route('read_post', $post->slug);
$sitemap->add(
Url::create($url)
->setPriority(0.7)
->setChangeFrequency(Url::CHANGE_FREQUENCY_WEEKLY)
->setLastModificationDate($post->updated_at)
);
}
return response($sitemap->render(), 200)
->header('Content-Type', 'application/xml');
}
}