Add Custom Header and Footer Blade Selection: Implemented options for selecting custom Blade files for header and footer templates in the Page settings. Enhanced the PageController to render these templates dynamically, improving modularity and user experience. Updated relevant views to support this new functionality, ensuring better localization and flexibility in template management.

This commit is contained in:
Ümit Tunç
2026-01-01 10:54:38 +03:00
parent 10f2e9c9bb
commit 12441517f6
20 changed files with 587 additions and 256 deletions
@@ -10,6 +10,8 @@ use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Illuminate\Support\Facades\File;
class FooterTemplateSection
{
public static function make(): Section
@@ -17,9 +19,35 @@ class FooterTemplateSection
return Section::make(__('pages.form_section_footer_template'))
->description(__('pages.form_section_footer_template_desc'))
->schema([
Select::make('custom_footer_blade')
->label('Custom Footer Blade')
->options(function () {
$files = File::glob(resource_path('views/partials/*.blade.php'));
$options = [];
foreach ($files as $file) {
$name = basename($file, '.blade.php');
$options["partials.{$name}"] = "Partial: {$name}";
}
// Add other directories if needed
$componentFiles = File::glob(resource_path('views/components/front/*.blade.php'));
foreach ($componentFiles as $file) {
$name = basename($file, '.blade.php');
$options["components.front.{$name}"] = "Component: {$name}";
}
return $options;
})
->searchable()
->preload()
->helperText('Select a physical Blade file to use as footer instead of a database template.')
->live()
->columnSpanFull(),
Select::make('footer_template_id')
->label(__('pages.footer_template_field'))
->options(FooterTemplate::where('is_active', true)->pluck('title', 'id'))
->visible(fn (Get $get) => empty($get('custom_footer_blade')))
->searchable()
->live()
->afterStateUpdated(function ($state, Set $set, Get $get) {
@@ -10,6 +10,8 @@ use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Illuminate\Support\Facades\File;
class HeaderTemplateSection
{
public static function make(): Section
@@ -17,9 +19,35 @@ class HeaderTemplateSection
return Section::make(__('pages.form_section_header_template'))
->description(__('pages.form_section_header_template_desc'))
->schema([
Select::make('custom_header_blade')
->label('Custom Header Blade')
->options(function () {
$files = File::glob(resource_path('views/partials/*.blade.php'));
$options = [];
foreach ($files as $file) {
$name = basename($file, '.blade.php');
$options["partials.{$name}"] = "Partial: {$name}";
}
// Add other directories if needed
$componentFiles = File::glob(resource_path('views/components/front/*.blade.php'));
foreach ($componentFiles as $file) {
$name = basename($file, '.blade.php');
$options["components.front.{$name}"] = "Component: {$name}";
}
return $options;
})
->searchable()
->preload()
->helperText('Select a physical Blade file to use as header instead of a database template.')
->live()
->columnSpanFull(),
Select::make('header_template_id')
->label(__('pages.header_template_field'))
->options(HeaderTemplate::where('is_active', true)->pluck('title', 'id'))
->visible(fn (Get $get) => empty($get('custom_header_blade')))
->searchable()
->live()
->afterStateUpdated(function ($state, Set $set, Get $get) {
@@ -69,13 +69,14 @@ class PageSettingsSection
'landing' => __('pages.template_landing'),
'blog' => __('pages.template_blog'),
'contact' => __('pages.template_contact'),
'home' => 'Home',
])
->default('default')
->columnSpanFull(),
TextInput::make('sort_order')
->label(__('pages.sort_order_field'))
->numeric()
TextInput::make('sort_order')
->label(__('pages.sort_order_field'))
->numeric()
->default(0)
->helperText(__('pages.sort_order_helper_text'))
->columnSpanFull(),
@@ -18,14 +18,14 @@ class ProductCategoriesTable
->columns([
TextColumn::make('title')
->label(__('product_categories.title'))
->formatStateUsing(fn ($state) => is_array($state) ? ($state[app()->getLocale()] ?? reset($state)) : $state)
->formatStateUsing(fn ($state, $record) => $record->translate('title'))
->searchable()
->sortable(),
TextColumn::make('slug')
->label(__('product_categories.slug'))
->searchable(),
TextColumn::make('parent.title') // This might fail if parent title is JSON
->formatStateUsing(fn ($state) => is_array($state) ? ($state[app()->getLocale()] ?? reset($state)) : $state)
TextColumn::make('parent.title')
->formatStateUsing(fn ($state, $record) => $record->parent?->translate('title'))
->label(__('product_categories.parent')),
TextColumn::make('sort_order')
->label(__('product_categories.sort_order'))
@@ -10,6 +10,9 @@ use Filament\Forms\Components\FileUpload;
use Filament\Schemas\Components\Section;
use App\Filament\Admin\Resources\Components\TranslationTabs;
use App\Models\ProductCategory;
use Illuminate\Support\Str;
use Filament\Forms\Get;
use Filament\Forms\Set;
class ProductForm
{
@@ -19,6 +22,16 @@ class ProductForm
->schema([
Section::make(__('products.general_section'))
->schema([
TextInput::make('title')
->label(__('products.title'))
->required()
->live(onBlur: true)
->afterStateUpdated(function (Get $get, Set $set, ?string $state) {
$set('slug', Str::slug($state));
$set('translations.' . app()->getLocale() . '.title', $state);
})
->formatStateUsing(fn ($record) => $record?->translate('title'))
->dehydrated(false),
TextInput::make('slug')
->label(__('products.slug'))
->required()
@@ -41,6 +54,7 @@ class ProductForm
FileUpload::make('hero_image')
->label(__('products.hero_image'))
->image()
->disk('public')
->directory('products'),
TextInput::make('view_template')
->label(__('products.view_template'))
@@ -21,7 +21,7 @@ class ProductsTable
->label(__('products.hero_image')),
TextColumn::make('title')
->label(__('products.title'))
->formatStateUsing(fn ($state) => is_array($state) ? ($state[app()->getLocale()] ?? reset($state)) : $state)
->formatStateUsing(fn ($state, $record) => $record->translate('title'))
->searchable()
->sortable(),
TextColumn::make('type')
@@ -38,7 +38,7 @@ class ProductsTable
default => $state,
}),
TextColumn::make('category.title')
->formatStateUsing(fn ($state) => is_array($state) ? ($state[app()->getLocale()] ?? reset($state)) : $state)
->formatStateUsing(fn ($state, $record) => $record->category?->translate('title'))
->label(__('products.category')),
ToggleColumn::make('is_active')
->label(__('products.is_active')),
+148 -109
View File
@@ -143,52 +143,66 @@ class PageController extends Controller
// Render Header Template
$renderedHeader = null;
$headerTemplate = $page->headerTemplate;
// Eğer sayfa için header seçilmemişse, en son aktif header template'i kullan
if (!$headerTemplate) {
$defaultHeaderId = $settings->default_header ?? null;
if ($defaultHeaderId) {
$headerTemplate = HeaderTemplate::find($defaultHeaderId);
}
}
if ($headerTemplate) {
// Merge template defaults with page data (page data overrides defaults)
$templateDefaults = $headerTemplate->default_data ?? [];
$pageData = $page->header_data ?? [];
$mergedHeaderData = array_merge($templateDefaults, $pageData);
// 1. Önce özel blade dosyası var mı kontrol et
if (!empty($page->custom_header_blade) && view()->exists($page->custom_header_blade)) {
$renderedHeader = view($page->custom_header_blade, ['page' => $page])->render();
} else {
// 2. Yoksa Header Template sistemini kullan
$headerTemplate = $page->headerTemplate;
$renderedHeader = TemplateService::replacePlaceholders(
$headerTemplate->html_content,
$mergedHeaderData,
$page
);
// Eğer sayfa için header seçilmemişse, en son aktif header template'i kullan
if (!$headerTemplate) {
$defaultHeaderId = $settings->default_header ?? null;
if ($defaultHeaderId) {
$headerTemplate = HeaderTemplate::find($defaultHeaderId);
}
}
if ($headerTemplate) {
// Merge template defaults with page data (page data overrides defaults)
$templateDefaults = $headerTemplate->default_data ?? [];
$pageData = $page->header_data ?? [];
$mergedHeaderData = array_merge($templateDefaults, $pageData);
$renderedHeader = TemplateService::replacePlaceholders(
$headerTemplate->html_content,
$mergedHeaderData,
$page
);
}
}
// Render Footer Template
$renderedFooter = null;
$footerTemplate = $page->footerTemplate;
// Eğer sayfa için footer seçilmemişse, en son aktif footer template'i kullan
if (!$footerTemplate) {
$defaultFooterId = $settings->default_footer ?? null;
if ($defaultFooterId) {
$footerTemplate = FooterTemplate::find($defaultFooterId);
}
}
if ($footerTemplate) {
// Merge template defaults with page data (page data overrides defaults)
$templateDefaults = $footerTemplate->default_data ?? [];
$pageData = $page->footer_data ?? [];
$mergedFooterData = array_merge($templateDefaults, $pageData);
// 1. Önce özel blade dosyası var mı kontrol et
if (!empty($page->custom_footer_blade) && view()->exists($page->custom_footer_blade)) {
$renderedFooter = view($page->custom_footer_blade, ['page' => $page])->render();
} else {
// 2. Yoksa Footer Template sistemini kullan
$footerTemplate = $page->footerTemplate;
$renderedFooter = TemplateService::replacePlaceholders(
$footerTemplate->html_content,
$mergedFooterData,
$page
);
// Eğer sayfa için footer seçilmemişse, en son aktif footer template'i kullan
if (!$footerTemplate) {
$defaultFooterId = $settings->default_footer ?? null;
if ($defaultFooterId) {
$footerTemplate = FooterTemplate::find($defaultFooterId);
}
}
if ($footerTemplate) {
// Merge template defaults with page data (page data overrides defaults)
$templateDefaults = $footerTemplate->default_data ?? [];
$pageData = $page->footer_data ?? [];
$mergedFooterData = array_merge($templateDefaults, $pageData);
$renderedFooter = TemplateService::replacePlaceholders(
$footerTemplate->html_content,
$mergedFooterData,
$page
);
}
}
return view($view, [
@@ -240,37 +254,48 @@ class PageController extends Controller
$renderedFooter = null;
if ($page) {
// Render Header Template
$headerTemplate = $page->headerTemplate;
if (!$headerTemplate) {
$defaultHeaderId = $settings->default_header ?? null;
if ($defaultHeaderId) {
$headerTemplate = HeaderTemplate::find($defaultHeaderId);
}
}
if ($headerTemplate) {
$templateDefaults = $headerTemplate->default_data ?? [];
$pageData = $page->header_data ?? [];
$mergedHeaderData = array_merge($templateDefaults, $pageData);
$renderedHeader = TemplateService::replacePlaceholders($headerTemplate->html_content, $mergedHeaderData, $page);
// 1. Önce özel blade dosyası var mı kontrol et
if (!empty($page->custom_header_blade) && view()->exists($page->custom_header_blade)) {
$renderedHeader = view($page->custom_header_blade, ['page' => $page])->render();
} else {
// 2. Yoksa Header Template sistemini kullan
$headerTemplate = $page->headerTemplate;
if (!$headerTemplate) {
$defaultHeaderId = $settings->default_header ?? null;
if ($defaultHeaderId) {
$headerTemplate = HeaderTemplate::find($defaultHeaderId);
}
}
if ($headerTemplate) {
$templateDefaults = $headerTemplate->default_data ?? [];
$pageData = $page->header_data ?? [];
$mergedHeaderData = array_merge($templateDefaults, $pageData);
$renderedHeader = TemplateService::replacePlaceholders($headerTemplate->html_content, $mergedHeaderData, $page);
}
}
// Render Footer Template
$footerTemplate = $page->footerTemplate;
if (!$footerTemplate) {
$defaultFooterId = $settings->default_footer ?? null;
if ($defaultFooterId) {
$footerTemplate = FooterTemplate::find($defaultFooterId);
}
}
if ($footerTemplate) {
$templateDefaults = $footerTemplate->default_data ?? [];
$pageData = $page->footer_data ?? [];
$mergedFooterData = array_merge($templateDefaults, $pageData);
$renderedFooter = TemplateService::replacePlaceholders($footerTemplate->html_content, $mergedFooterData, $page);
// Footer Render
// 1. Önce özel blade dosyası var mı kontrol et
if (!empty($page->custom_footer_blade) && view()->exists($page->custom_footer_blade)) {
$renderedFooter = view($page->custom_footer_blade, ['page' => $page])->render();
} else {
// 2. Yoksa Footer Template sistemini kullan
$footerTemplate = $page->footerTemplate;
if (!$footerTemplate) {
$defaultFooterId = $settings->default_footer ?? null;
if ($defaultFooterId) {
$footerTemplate = FooterTemplate::find($defaultFooterId);
}
}
if ($footerTemplate) {
$templateDefaults = $footerTemplate->default_data ?? [];
$pageData = $page->footer_data ?? [];
$mergedFooterData = array_merge($templateDefaults, $pageData);
$renderedFooter = TemplateService::replacePlaceholders($footerTemplate->html_content, $mergedFooterData, $page);
}
}
}
@@ -324,52 +349,66 @@ class PageController extends Controller
// Render Header Template
$renderedHeader = null;
$headerTemplate = $page->headerTemplate;
// Eğer sayfa için header seçilmemişse, en son aktif header template'i kullan
if (!$headerTemplate) {
$defaultHeaderId = $settings->default_header ?? null;
if ($defaultHeaderId) {
$headerTemplate = HeaderTemplate::find($defaultHeaderId);
}
}
if ($headerTemplate) {
// Merge template defaults with page data (page data overrides defaults)
$templateDefaults = $headerTemplate->default_data ?? [];
$pageData = $page->header_data ?? [];
$mergedHeaderData = array_merge($templateDefaults, $pageData);
// 1. Önce özel blade dosyası var mı kontrol et
if (!empty($page->custom_header_blade) && view()->exists($page->custom_header_blade)) {
$renderedHeader = view($page->custom_header_blade, ['page' => $page])->render();
} else {
// 2. Yoksa Header Template sistemini kullan
$headerTemplate = $page->headerTemplate;
$renderedHeader = TemplateService::replacePlaceholders(
$headerTemplate->html_content,
$mergedHeaderData,
$page
);
// Eğer sayfa için header seçilmemişse, en son aktif header template'i kullan
if (!$headerTemplate) {
$defaultHeaderId = $settings->default_header ?? null;
if ($defaultHeaderId) {
$headerTemplate = HeaderTemplate::find($defaultHeaderId);
}
}
if ($headerTemplate) {
// Merge template defaults with page data (page data overrides defaults)
$templateDefaults = $headerTemplate->default_data ?? [];
$pageData = $page->header_data ?? [];
$mergedHeaderData = array_merge($templateDefaults, $pageData);
$renderedHeader = TemplateService::replacePlaceholders(
$headerTemplate->html_content,
$mergedHeaderData,
$page
);
}
}
// Render Footer Template
$renderedFooter = null;
$footerTemplate = $page->footerTemplate;
// Eğer sayfa için footer seçilmemişse, en son aktif footer template'i kullan
if (!$footerTemplate) {
$defaultFooterId = $settings->default_footer ?? null;
if ($defaultFooterId) {
$footerTemplate = FooterTemplate::find($defaultFooterId);
}
}
if ($footerTemplate) {
// Merge template defaults with page data (page data overrides defaults)
$templateDefaults = $footerTemplate->default_data ?? [];
$pageData = $page->footer_data ?? [];
$mergedFooterData = array_merge($templateDefaults, $pageData);
// 1. Önce özel blade dosyası var mı kontrol et
if (!empty($page->custom_footer_blade) && view()->exists($page->custom_footer_blade)) {
$renderedFooter = view($page->custom_footer_blade, ['page' => $page])->render();
} else {
// 2. Yoksa Footer Template sistemini kullan
$footerTemplate = $page->footerTemplate;
$renderedFooter = TemplateService::replacePlaceholders(
$footerTemplate->html_content,
$mergedFooterData,
$page
);
// Eğer sayfa için footer seçilmemişse, en son aktif footer template'i kullan
if (!$footerTemplate) {
$defaultFooterId = $settings->default_footer ?? null;
if ($defaultFooterId) {
$footerTemplate = FooterTemplate::find($defaultFooterId);
}
}
if ($footerTemplate) {
// Merge template defaults with page data (page data overrides defaults)
$templateDefaults = $footerTemplate->default_data ?? [];
$pageData = $page->footer_data ?? [];
$mergedFooterData = array_merge($templateDefaults, $pageData);
$renderedFooter = TemplateService::replacePlaceholders(
$footerTemplate->html_content,
$mergedFooterData,
$page
);
}
}
return view($view, [
+58 -4
View File
@@ -3,6 +3,10 @@
namespace App\Http\Controllers;
use App\Models\Product;
use App\Models\Setting;
use App\Models\HeaderTemplate;
use App\Models\FooterTemplate;
use App\Services\TemplateService;
use Illuminate\Http\Request;
class ProductController extends Controller
@@ -13,11 +17,61 @@ class ProductController extends Controller
->where('is_active', true)
->with('category')
->firstOrFail();
if ($product->view_template && view()->exists($product->view_template)) {
return view($product->view_template, compact('product'));
// Get Settings
$settings = new \stdClass();
$allSettings = Setting::where('is_active', true)->get();
foreach ($allSettings as $setting) {
$settings->{$setting->key} = $setting->value;
}
return view('front.products.show', compact('product'));
// --- Header Logic ---
$renderedHeader = null;
$defaultHeaderId = $settings->default_header ?? null;
if ($defaultHeaderId) {
$headerTemplate = HeaderTemplate::find($defaultHeaderId);
if ($headerTemplate) {
$templateDefaults = $headerTemplate->default_data ?? [];
$productData = []; // Ürünlere özel header datası eklenebilir
$mergedHeaderData = array_merge($templateDefaults, $productData);
$renderedHeader = TemplateService::replacePlaceholders(
$headerTemplate->html_content,
$mergedHeaderData,
$product // Model context
);
}
}
// --- Footer Logic ---
$renderedFooter = null;
$defaultFooterId = $settings->default_footer ?? null;
if ($defaultFooterId) {
$footerTemplate = FooterTemplate::find($defaultFooterId);
if ($footerTemplate) {
$templateDefaults = $footerTemplate->default_data ?? [];
$productData = []; // Ürünlere özel footer datası eklenebilir
$mergedFooterData = array_merge($templateDefaults, $productData);
$renderedFooter = TemplateService::replacePlaceholders(
$footerTemplate->html_content,
$mergedFooterData,
$product // Model context
);
}
}
$meta = [
'title' => $product->translate('title'),
'description' => \Str::limit(strip_tags($product->translate('content')), 160),
];
if ($product->view_template && view()->exists($product->view_template)) {
return view($product->view_template, compact('product', 'settings', 'renderedHeader', 'renderedFooter', 'meta'));
}
return view('front.products.show', compact('product', 'settings', 'renderedHeader', 'renderedFooter', 'meta'));
}
}
+2
View File
@@ -34,8 +34,10 @@ class Page extends Model
// Template System
'header_template_id',
'header_data',
'custom_header_blade',
'footer_template_id',
'footer_data',
'custom_footer_blade',
'sections_data',
];