iniit
This commit is contained in:
@@ -0,0 +1,895 @@
|
||||
# Dynamic Template System (Dinamik Şablon Sistemi)
|
||||
|
||||
## Genel Bakış
|
||||
|
||||
Bu sistem, sayfalara dinamik header, section ve footer şablonları ekleyerek içerik yönetimini tamamen özelleştirilebilir hale getirir.
|
||||
|
||||
## Mimari
|
||||
|
||||
### 1. Template Modülleri
|
||||
|
||||
#### 1.1 Header Template
|
||||
- **Tablo:** `header_templates`
|
||||
- **Alanlar:**
|
||||
- `id` (bigint, PK)
|
||||
- `title` (string, 255) - Şablon başlığı
|
||||
- `html_content` (longtext) - HTML içerik + placeholder'lar ✅ **4GB kapasiteli**
|
||||
- `is_active` (boolean) - Aktif/Pasif
|
||||
- `created_at`, `updated_at`, `deleted_at`
|
||||
|
||||
#### 1.2 Section Template
|
||||
- **Tablo:** `section_templates`
|
||||
- **Alanlar:**
|
||||
- `id` (bigint, PK)
|
||||
- `title` (string, 255) - Şablon başlığı
|
||||
- `html_content` (longtext) - HTML içerik + placeholder'lar ✅ **4GB kapasiteli**
|
||||
- `is_active` (boolean) - Aktif/Pasif
|
||||
- `created_at`, `updated_at`, `deleted_at`
|
||||
|
||||
#### 1.3 Footer Template
|
||||
- **Tablo:** `footer_templates`
|
||||
- **Alanlar:**
|
||||
- `id` (bigint, PK)
|
||||
- `title` (string, 255) - Şablon başlığı
|
||||
- `html_content` (longtext) - HTML içerik + placeholder'lar ✅ **4GB kapasiteli**
|
||||
- `is_active` (boolean) - Aktif/Pasif
|
||||
- `created_at`, `updated_at`, `deleted_at`
|
||||
|
||||
#### 1.4 Menu Template
|
||||
- **Tablo:** `menu_templates`
|
||||
- **Alanlar:**
|
||||
- `id` (bigint, PK)
|
||||
- `title` (string, 255) - Şablon başlığı
|
||||
- `html_content` (longtext) - HTML içerik + {menu} placeholder ✅ **4GB kapasiteli**
|
||||
- `is_active` (boolean) - Aktif/Pasif
|
||||
- `created_at`, `updated_at`, `deleted_at`
|
||||
|
||||
**Not:** `longtext` veri tipi MySQL'de **4,294,967,295 karakter** (4GB) kapasiteye sahiptir. Bu, en karmaşık HTML template'ler ve zengin içerikler için fazlasıyla yeterlidir.
|
||||
|
||||
### 2. Placeholder Sistemi
|
||||
|
||||
#### 2.1 Placeholder Formatı
|
||||
```
|
||||
{form_type.field_name}
|
||||
```
|
||||
|
||||
**Özel Placeholder'lar:**
|
||||
- `{menu}` → Menü yapısını render eder (MenuTemplate için kullanılır)
|
||||
- Diğer tüm template tiplerinde (header, footer, section) normal placeholder'lar kullanılır
|
||||
|
||||
**Desteklenen Form Tipleri:**
|
||||
|
||||
#### Text Input Variants:
|
||||
- `text` → TextInput (basic text)
|
||||
- `email` → TextInput (email validation)
|
||||
- `url` → TextInput (URL validation)
|
||||
- `tel` → TextInput (telephone)
|
||||
- `number` → TextInput (numeric)
|
||||
- `password` → TextInput (password)
|
||||
|
||||
#### Text Area & Editors:
|
||||
- `textarea` → Textarea (multi-line text)
|
||||
- `richtext` → RichEditor (WYSIWYG HTML editor)
|
||||
- `markdown` → MarkdownEditor (Markdown editor with preview)
|
||||
- `code` → CodeEditor (syntax highlighted code)
|
||||
|
||||
#### Date & Time:
|
||||
- `date` → DatePicker (date only)
|
||||
- `datetime` → DateTimePicker (date + time)
|
||||
- `time` → TimePicker (time only)
|
||||
|
||||
#### File Uploads:
|
||||
- `image` → FileUpload (image only, with preview)
|
||||
- `file` → FileUpload (any file type)
|
||||
- `files` → FileUpload (multiple files)
|
||||
- `images` → FileUpload (multiple images)
|
||||
|
||||
#### Selection & Options:
|
||||
- `select` → Select (dropdown selection)
|
||||
- `multiselect` → Select (multiple selection)
|
||||
- `checkbox` → Checkbox (single checkbox)
|
||||
- `checkboxlist` → CheckboxList (multiple checkboxes)
|
||||
- `radio` → Radio (radio buttons)
|
||||
- `toggle` → Toggle (switch button)
|
||||
- `togglebuttons` → ToggleButtons (button group)
|
||||
|
||||
#### Color & Visual:
|
||||
- `color` → ColorPicker (color picker)
|
||||
|
||||
#### Structured Data:
|
||||
- `tags` → TagsInput (tag input)
|
||||
- `keyvalue` → KeyValue (key-value pairs)
|
||||
- `repeater` → Repeater (repeatable fields - not recommended in template)
|
||||
- `builder` → Builder (block builder - not recommended in template)
|
||||
|
||||
#### Advanced:
|
||||
- `slider` → Slider (range slider)
|
||||
- `hidden` → Hidden (hidden field for calculations)
|
||||
|
||||
**Toplam: 30+ Form Tipi Desteği**
|
||||
|
||||
#### Form Tipi Referans Tablosu
|
||||
|
||||
| Placeholder Type | Filament Component | Örnek Kullanım | Veri Tipi | Max Boyut |
|
||||
|-----------------|-------------------|----------------|-----------|-----------|
|
||||
| `{text.name}` | TextInput | Kısa metinler | string | 255 char |
|
||||
| `{email.address}` | TextInput (email) | E-posta adresleri | string | 255 char |
|
||||
| `{url.link}` | TextInput (url) | Web adresleri | string | 500 char |
|
||||
| `{tel.phone}` | TextInput (tel) | Telefon numaraları | string | 20 char |
|
||||
| `{number.count}` | TextInput (numeric) | Sayısal değerler | integer/float | - |
|
||||
| `{password.pass}` | TextInput (password) | Şifreler | string (hashed) | 255 char |
|
||||
| `{textarea.desc}` | Textarea | Çok satırlı metin | string | 5000 char |
|
||||
| `{richtext.content}` | RichEditor | HTML içerik | longtext | 50000 char |
|
||||
| `{markdown.post}` | MarkdownEditor | Markdown içerik | longtext | 50000 char |
|
||||
| `{code.snippet}` | CodeEditor | Kod blokları | longtext | 50000 char |
|
||||
| `{date.birthday}` | DatePicker | Tarih seçimi | date | - |
|
||||
| `{datetime.event}` | DateTimePicker | Tarih + saat | datetime | - |
|
||||
| `{time.opening}` | TimePicker | Saat seçimi | time | - |
|
||||
| `{image.banner}` | FileUpload | Tek resim | string (path) | 5MB |
|
||||
| `{images.gallery}` | FileUpload (multiple) | Çoklu resim | json (paths) | 10x5MB |
|
||||
| `{file.document}` | FileUpload | Tek dosya | string (path) | 10MB |
|
||||
| `{files.attachments}` | FileUpload (multiple) | Çoklu dosya | json (paths) | 10x10MB |
|
||||
| `{select.category}` | Select | Tekli seçim | string/int | - |
|
||||
| `{multiselect.tags}` | Select (multiple) | Çoklu seçim | json (array) | - |
|
||||
| `{checkbox.agree}` | Checkbox | Tekli checkbox | boolean | - |
|
||||
| `{checkboxlist.options}` | CheckboxList | Checkbox listesi | json (array) | - |
|
||||
| `{radio.gender}` | Radio | Radio button | string/int | - |
|
||||
| `{toggle.active}` | Toggle | Açık/Kapalı | boolean | - |
|
||||
| `{togglebuttons.size}` | ToggleButtons | Buton grubu | string | - |
|
||||
| `{color.theme}` | ColorPicker | Renk seçici | string (hex) | 7 char |
|
||||
| `{tags.keywords}` | TagsInput | Etiket girişi | json (array) | - |
|
||||
| `{keyvalue.meta}` | KeyValue | Anahtar-değer | json (object) | - |
|
||||
| `{slider.volume}` | Slider | Kaydırıcı | integer | - |
|
||||
| `{hidden.calc}` | Hidden | Gizli alan | mixed | - |
|
||||
|
||||
#### 2.2 Örnek Template (Comprehensive)
|
||||
```html
|
||||
<header class="site-header" style="background-color: {color.header_bg};">
|
||||
<div class="container">
|
||||
<!-- Logo Section -->
|
||||
<div class="logo">
|
||||
<img src="{image.logo}" alt="{text.company_name}">
|
||||
<span class="tagline">{textarea.tagline}</span>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="main-nav">
|
||||
{richtext.menu_html}
|
||||
</nav>
|
||||
|
||||
<!-- Contact Info -->
|
||||
<div class="contact-info">
|
||||
<a href="mailto:{email.contact_email}">{email.contact_email}</a>
|
||||
<a href="tel:{tel.phone}">{tel.phone}</a>
|
||||
</div>
|
||||
|
||||
<!-- Social Links -->
|
||||
<div class="social-links">
|
||||
<a href="{url.facebook_link}" target="_blank">Facebook</a>
|
||||
<a href="{url.twitter_link}" target="_blank">Twitter</a>
|
||||
<a href="{url.linkedin_link}" target="_blank">LinkedIn</a>
|
||||
</div>
|
||||
|
||||
<!-- Toggle Features -->
|
||||
<div class="features">
|
||||
{toggle.show_search}
|
||||
{toggle.show_cart}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- CSS Snippet -->
|
||||
<style>
|
||||
.site-header {
|
||||
padding: {number.padding}px;
|
||||
opacity: {slider.opacity}%;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
**Bu template şu form alanlarını oluşturur:**
|
||||
- Logo (FileUpload - image)
|
||||
- Company Name (TextInput)
|
||||
- Tagline (Textarea)
|
||||
- Menu HTML (RichEditor)
|
||||
- Contact Email (TextInput - email)
|
||||
- Phone (TextInput - tel)
|
||||
- Facebook Link (TextInput - url)
|
||||
- Twitter Link (TextInput - url)
|
||||
- LinkedIn Link (TextInput - url)
|
||||
- Show Search (Toggle)
|
||||
- Show Cart (Toggle)
|
||||
- Header BG (ColorPicker)
|
||||
- Padding (TextInput - number)
|
||||
- Opacity (Slider)
|
||||
|
||||
### 3. Pages Modülü Entegrasyonu
|
||||
|
||||
#### 3.1 Pages Tablo Güncellemesi
|
||||
```php
|
||||
// Migration: add_template_fields_to_pages_table
|
||||
Schema::table('pages', function (Blueprint $table) {
|
||||
$table->foreignId('header_template_id')->nullable()->constrained('header_templates')->nullOnDelete();
|
||||
$table->json('header_data')->nullable(); // Header placeholder değerleri
|
||||
|
||||
$table->foreignId('footer_template_id')->nullable()->constrained('footer_templates')->nullOnDelete();
|
||||
$table->json('footer_data')->nullable(); // Footer placeholder değerleri
|
||||
|
||||
// sections_data -> Repeater ile sections (eski page_sections yerine)
|
||||
// Format: [
|
||||
// {
|
||||
// 'section_template_id': 1,
|
||||
// 'section_data': { 'text.title': 'Başlık', 'image.banner': 'path/to/image.jpg' }
|
||||
// },
|
||||
// ...
|
||||
// ]
|
||||
$table->json('sections_data')->nullable();
|
||||
});
|
||||
```
|
||||
|
||||
**Veri Tipi Notları:**
|
||||
- `json` column tipi: MySQL 5.7.8+ ve PostgreSQL 9.4+ destekli
|
||||
- JSON alanlar Laravel tarafından otomatik encode/decode edilir
|
||||
- `longtext` alternatifi: Tüm veritabanlarıyla uyumlu (Laravel JSON cast ile)
|
||||
- Her iki durumda da Laravel'in `$casts = ['header_data' => 'array']` özelliği kullanılır
|
||||
|
||||
#### 3.2 Page Model İlişkileri
|
||||
```php
|
||||
class Page extends Model
|
||||
{
|
||||
public function headerTemplate()
|
||||
{
|
||||
return $this->belongsTo(HeaderTemplate::class);
|
||||
}
|
||||
|
||||
public function footerTemplate()
|
||||
{
|
||||
return $this->belongsTo(FooterTemplate::class);
|
||||
}
|
||||
|
||||
// sections_data JSON içinde section_template_id'ler var
|
||||
public function getSectionsAttribute()
|
||||
{
|
||||
$sectionsData = $this->sections_data ?? [];
|
||||
return collect($sectionsData)->map(function ($section) {
|
||||
return [
|
||||
'template' => SectionTemplate::find($section['section_template_id']),
|
||||
'data' => $section['section_data'] ?? [],
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Filament Form Yapısı
|
||||
|
||||
#### 4.1 Page Form (PageForm.php)
|
||||
```php
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
|
||||
Tabs::make('PageTabs')
|
||||
->tabs([
|
||||
Tab::make('Genel Bilgiler')
|
||||
->schema([
|
||||
TextInput::make('title')->required(),
|
||||
TextInput::make('slug')->required(),
|
||||
// ... diğer genel alanlar
|
||||
]),
|
||||
|
||||
Tab::make('Header Template')
|
||||
->schema([
|
||||
Select::make('header_template_id')
|
||||
->label('Header Template')
|
||||
->options(HeaderTemplate::where('is_active', true)->pluck('title', 'id'))
|
||||
->live()
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('header_data', [])),
|
||||
|
||||
// Dinamik header alanları
|
||||
Group::make()
|
||||
->schema(fn (Get $get) => self::generateDynamicFields(
|
||||
HeaderTemplate::find($get('header_template_id')),
|
||||
'header_data'
|
||||
))
|
||||
->visible(fn (Get $get) => filled($get('header_template_id'))),
|
||||
]),
|
||||
|
||||
Tab::make('Sections')
|
||||
->schema([
|
||||
Repeater::make('sections_data')
|
||||
->schema([
|
||||
Select::make('section_template_id')
|
||||
->label('Section Template')
|
||||
->options(SectionTemplate::where('is_active', true)->pluck('title', 'id'))
|
||||
->live()
|
||||
->required(),
|
||||
|
||||
// Dinamik section alanları
|
||||
Group::make()
|
||||
->schema(fn (Get $get, $record) => self::generateDynamicFields(
|
||||
SectionTemplate::find($get('section_template_id')),
|
||||
'section_data'
|
||||
))
|
||||
->visible(fn (Get $get) => filled($get('section_template_id'))),
|
||||
])
|
||||
->itemLabel(fn (array $state): ?string =>
|
||||
SectionTemplate::find($state['section_template_id'] ?? null)?->title ?? 'Section'
|
||||
)
|
||||
->collapsible()
|
||||
->reorderable()
|
||||
->addActionLabel(__('pages.add_section')),
|
||||
]),
|
||||
|
||||
Tab::make('Footer Template')
|
||||
->schema([
|
||||
Select::make('footer_template_id')
|
||||
->label('Footer Template')
|
||||
->options(FooterTemplate::where('is_active', true)->pluck('title', 'id'))
|
||||
->live()
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('footer_data', [])),
|
||||
|
||||
// Dinamik footer alanları
|
||||
Group::make()
|
||||
->schema(fn (Get $get) => self::generateDynamicFields(
|
||||
FooterTemplate::find($get('footer_template_id')),
|
||||
'footer_data'
|
||||
))
|
||||
->visible(fn (Get $get) => filled($get('footer_template_id'))),
|
||||
]),
|
||||
]);
|
||||
```
|
||||
|
||||
#### 4.2 Dinamik Form Generator
|
||||
```php
|
||||
use Filament\Forms\Components\{
|
||||
TextInput, Textarea, Select, Checkbox, CheckboxList,
|
||||
Radio, Toggle, ToggleButtons, DateTimePicker, DatePicker,
|
||||
TimePicker, FileUpload, RichEditor, MarkdownEditor,
|
||||
ColorPicker, TagsInput, KeyValue, CodeEditor, Hidden, Slider
|
||||
};
|
||||
|
||||
protected static function generateDynamicFields($template, string $dataKey): array
|
||||
{
|
||||
if (!$template) return [];
|
||||
|
||||
$placeholders = self::parsePlaceholders($template->html_content);
|
||||
$fields = [];
|
||||
|
||||
foreach ($placeholders as $placeholder) {
|
||||
[$type, $name] = explode('.', $placeholder);
|
||||
|
||||
$label = str($name)->title()->replace('_', ' ')->toString();
|
||||
|
||||
$field = match($type) {
|
||||
// Text Input Variants
|
||||
'text' => TextInput::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->maxLength(255),
|
||||
|
||||
'email' => TextInput::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->email()
|
||||
->maxLength(255),
|
||||
|
||||
'url' => TextInput::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->url()
|
||||
->maxLength(500),
|
||||
|
||||
'tel' => TextInput::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->tel()
|
||||
->maxLength(20),
|
||||
|
||||
'number' => TextInput::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->numeric(),
|
||||
|
||||
'password' => TextInput::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->password()
|
||||
->dehydrated(fn ($state) => filled($state))
|
||||
->maxLength(255),
|
||||
|
||||
// Text Area & Editors
|
||||
'textarea' => Textarea::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->rows(4)
|
||||
->maxLength(5000),
|
||||
|
||||
'richtext' => RichEditor::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->toolbarButtons([
|
||||
'bold', 'italic', 'underline', 'link',
|
||||
'bulletList', 'orderedList', 'h2', 'h3',
|
||||
])
|
||||
->maxLength(50000),
|
||||
|
||||
'markdown' => MarkdownEditor::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->toolbarButtons([
|
||||
'bold', 'italic', 'strike', 'link',
|
||||
'heading', 'bulletList', 'orderedList', 'codeBlock',
|
||||
])
|
||||
->maxLength(50000),
|
||||
|
||||
'code' => CodeEditor::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
//->lineNumbers()
|
||||
->maxLength(50000),
|
||||
|
||||
// Date & Time
|
||||
'date' => DatePicker::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->native(false),
|
||||
|
||||
'datetime' => DateTimePicker::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->native(false)
|
||||
->seconds(false),
|
||||
|
||||
'time' => TimePicker::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->native(false)
|
||||
->seconds(false),
|
||||
|
||||
// File Uploads
|
||||
'image' => FileUpload::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->image()
|
||||
->imageEditor()
|
||||
->imageEditorAspectRatios([
|
||||
null,
|
||||
'16:9',
|
||||
'4:3',
|
||||
'1:1',
|
||||
])
|
||||
->directory('templates/images')
|
||||
->maxSize(5120),
|
||||
|
||||
'images' => FileUpload::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->image()
|
||||
->multiple()
|
||||
->imageEditor()
|
||||
->directory('templates/images')
|
||||
->maxSize(5120)
|
||||
->maxFiles(10),
|
||||
|
||||
'file' => FileUpload::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->directory('templates/files')
|
||||
->maxSize(10240),
|
||||
|
||||
'files' => FileUpload::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->multiple()
|
||||
->directory('templates/files')
|
||||
->maxSize(10240)
|
||||
->maxFiles(10),
|
||||
|
||||
// Selection & Options
|
||||
'select' => Select::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->options(self::getSelectOptions($name))
|
||||
->searchable(),
|
||||
|
||||
'multiselect' => Select::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->multiple()
|
||||
->options(self::getSelectOptions($name))
|
||||
->searchable(),
|
||||
|
||||
'checkbox' => Checkbox::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->inline(false),
|
||||
|
||||
'checkboxlist' => CheckboxList::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->options(self::getSelectOptions($name))
|
||||
->columns(2),
|
||||
|
||||
'radio' => Radio::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->options(self::getSelectOptions($name))
|
||||
->inline()
|
||||
->inlineLabel(false),
|
||||
|
||||
'toggle' => Toggle::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->inline(false),
|
||||
|
||||
'togglebuttons' => ToggleButtons::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->options(self::getSelectOptions($name))
|
||||
->inline()
|
||||
->grouped(),
|
||||
|
||||
// Color & Visual
|
||||
'color' => ColorPicker::make("{$dataKey}.{$placeholder}")
|
||||
->label($label),
|
||||
|
||||
// Structured Data
|
||||
'tags' => TagsInput::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->separator(','),
|
||||
|
||||
'keyvalue' => KeyValue::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->keyLabel('Key')
|
||||
->valueLabel('Value')
|
||||
->reorderable(),
|
||||
|
||||
// Advanced
|
||||
'slider' => Slider::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->minValue(0)
|
||||
->maxValue(100)
|
||||
->step(1),
|
||||
|
||||
'hidden' => Hidden::make("{$dataKey}.{$placeholder}"),
|
||||
|
||||
// Default
|
||||
default => TextInput::make("{$dataKey}.{$placeholder}")
|
||||
->label($label)
|
||||
->maxLength(255)
|
||||
->helperText("Unknown type: {$type}"),
|
||||
};
|
||||
|
||||
$fields[] = $field;
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select, MultiSelect, CheckboxList, Radio, ToggleButtons için
|
||||
* dinamik options yükler. Gerçek uygulamada config veya database'den
|
||||
* okunabilir.
|
||||
*/
|
||||
protected static function getSelectOptions(string $fieldName): array
|
||||
{
|
||||
// Örnek: config/template-options.php dosyasından okuyabilirsiniz
|
||||
return config("template-options.{$fieldName}", [
|
||||
'option_1' => 'Option 1',
|
||||
'option_2' => 'Option 2',
|
||||
'option_3' => 'Option 3',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Template HTML içeriğindeki placeholder'ları parse eder
|
||||
* Desteklenen format: {type.field_name}
|
||||
*/
|
||||
protected static function parsePlaceholders(string $html): array
|
||||
{
|
||||
// Regex: {word.word_with_underscores}
|
||||
preg_match_all('/\{([a-z]+\.[a-z_]+)\}/i', $html, $matches);
|
||||
return array_unique($matches[1] ?? []);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Frontend Rendering
|
||||
|
||||
#### 5.1 Blade Component (PageRenderer)
|
||||
```php
|
||||
// app/View/Components/PageRenderer.php
|
||||
class PageRenderer extends Component
|
||||
{
|
||||
public function __construct(public Page $page) {}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('components.page-renderer');
|
||||
}
|
||||
|
||||
public function renderHeader()
|
||||
{
|
||||
if (!$this->page->headerTemplate) return '';
|
||||
|
||||
return $this->replaceePlaceholders(
|
||||
$this->page->headerTemplate->html_content,
|
||||
$this->page->header_data ?? []
|
||||
);
|
||||
}
|
||||
|
||||
public function renderSections()
|
||||
{
|
||||
return collect($this->page->sections)->map(function ($section) {
|
||||
return $this->replacePlaceholders(
|
||||
$section['template']->html_content,
|
||||
$section['data']
|
||||
);
|
||||
})->implode('');
|
||||
}
|
||||
|
||||
public function renderFooter()
|
||||
{
|
||||
if (!$this->page->footerTemplate) return '';
|
||||
|
||||
return $this->replacePlaceholders(
|
||||
$this->page->footerTemplate->html_content,
|
||||
$this->page->footer_data ?? []
|
||||
);
|
||||
}
|
||||
|
||||
protected function replacePlaceholders(string $html, array $data): string
|
||||
{
|
||||
foreach ($data as $placeholder => $value) {
|
||||
// {text.name} → value
|
||||
$html = str_replace("{{$placeholder}}", $value, $html);
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2 Blade View
|
||||
```blade
|
||||
<!-- resources/views/components/page-renderer.blade.php -->
|
||||
<div class="page-wrapper">
|
||||
{!! $renderHeader() !!}
|
||||
|
||||
<main class="page-content">
|
||||
{!! $renderSections() !!}
|
||||
</main>
|
||||
|
||||
{!! $renderFooter() !!}
|
||||
</div>
|
||||
```
|
||||
|
||||
### 6. Navigasyon Yapısı
|
||||
|
||||
```php
|
||||
// Filament Panel Provider
|
||||
->navigationGroups([
|
||||
'Template' => [
|
||||
'icon' => 'heroicon-o-document-duplicate',
|
||||
'order' => 3,
|
||||
],
|
||||
])
|
||||
|
||||
// Her Resource'da
|
||||
public static function getNavigationGroup(): string
|
||||
{
|
||||
return 'Template';
|
||||
}
|
||||
```
|
||||
|
||||
## Dosya Yapısı
|
||||
|
||||
```
|
||||
app/
|
||||
├── Models/
|
||||
│ ├── HeaderTemplate.php
|
||||
│ ├── SectionTemplate.php
|
||||
│ └── FooterTemplate.php
|
||||
├── Filament/Admin/Resources/
|
||||
│ ├── HeaderTemplates/
|
||||
│ │ ├── HeaderTemplateResource.php
|
||||
│ │ ├── Pages/
|
||||
│ │ ├── Schemas/
|
||||
│ │ └── Tables/
|
||||
│ ├── SectionTemplates/
|
||||
│ │ ├── SectionTemplateResource.php
|
||||
│ │ ├── Pages/
|
||||
│ │ ├── Schemas/
|
||||
│ │ └── Tables/
|
||||
│ └── FooterTemplates/
|
||||
│ ├── FooterTemplateResource.php
|
||||
│ ├── Pages/
|
||||
│ ├── Schemas/
|
||||
│ └── Tables/
|
||||
├── View/Components/
|
||||
│ └── PageRenderer.php
|
||||
└── Services/
|
||||
└── TemplateService.php (helper methods)
|
||||
|
||||
database/migrations/
|
||||
├── create_header_templates_table.php
|
||||
├── create_section_templates_table.php
|
||||
├── create_footer_templates_table.php
|
||||
└── add_template_fields_to_pages_table.php
|
||||
|
||||
lang/
|
||||
├── tr/
|
||||
│ ├── header-templates.php
|
||||
│ ├── section-templates.php
|
||||
│ └── footer-templates.php
|
||||
└── en/
|
||||
├── header-templates.php
|
||||
├── section-templates.php
|
||||
└── footer-templates.php
|
||||
|
||||
resources/views/
|
||||
└── components/
|
||||
└── page-renderer.blade.php
|
||||
```
|
||||
|
||||
## Örnek Kullanım Senaryosu
|
||||
|
||||
### 1. Template Tanımlama
|
||||
**Header Template:**
|
||||
```html
|
||||
<header>
|
||||
<img src="{image.logo}" alt="{text.site_name}">
|
||||
<nav>{richtext.menu_html}</nav>
|
||||
<a href="mailto:{email.contact}">{email.contact}</a>
|
||||
</header>
|
||||
```
|
||||
|
||||
### 2. Page'de Template Seçimi
|
||||
- Header Template: "Ana Header" seçildi
|
||||
- Dinamik alanlar açıldı:
|
||||
- Logo (FileUpload)
|
||||
- Site Name (TextInput)
|
||||
- Menu HTML (RichEditor)
|
||||
- Contact Email (TextInput)
|
||||
- Values dolduruldu ve kaydedildi
|
||||
|
||||
### 3. Frontend'de Görüntüleme
|
||||
```blade
|
||||
<x-page-renderer :page="$page" />
|
||||
```
|
||||
|
||||
Output:
|
||||
```html
|
||||
<header>
|
||||
<img src="/storage/uploads/logo.png" alt="My Company">
|
||||
<nav><ul><li><a href="/">Home</a></li></ul></nav>
|
||||
<a href="mailto:info@company.com">info@company.com</a>
|
||||
</header>
|
||||
```
|
||||
|
||||
## Avantajlar
|
||||
|
||||
1. ✅ **Tamamen Dinamik**: Kod değişikliği olmadan yeni template'ler eklenebilir
|
||||
2. ✅ **Yeniden Kullanılabilir**: Aynı template birden fazla sayfada kullanılabilir
|
||||
3. ✅ **Kolay Yönetim**: Admin panel üzerinden HTML düzenlenebilir
|
||||
4. ✅ **Tip Güvenli**: Placeholder'lar form tipini belirler
|
||||
5. ✅ **Ölçeklenebilir**: Yeni form tipleri kolayca eklenebilir
|
||||
6. ✅ **SEO Dostu**: Her sayfa kendi içeriğine sahip
|
||||
7. ✅ **Performanslı**: Sadece seçili template'ler yüklenir
|
||||
8. ✅ **30+ Form Tipi**: Filament 4.x'in tüm form component'leri destekleniyor
|
||||
9. ✅ **Zengin İçerik**: HTML, Markdown, Code editor desteği
|
||||
10. ✅ **Büyük Veri**: longtext ile 4GB kapasiteli template'ler
|
||||
|
||||
## Veri Tipi Özeti
|
||||
|
||||
### HTML Content (Template'ler için)
|
||||
```sql
|
||||
html_content LONGTEXT
|
||||
-- Kapasite: 4,294,967,295 karakter (4GB)
|
||||
-- Kullanım: Template HTML + placeholder'lar
|
||||
-- Avantaj: En karmaşık template'ler için bile yeterli
|
||||
```
|
||||
|
||||
### JSON Data (Page verisi için)
|
||||
```sql
|
||||
header_data JSON
|
||||
footer_data JSON
|
||||
sections_data JSON
|
||||
-- MySQL 5.7.8+ ve PostgreSQL 9.4+ native JSON desteği
|
||||
-- Alternatif: LONGTEXT + Laravel JSON cast
|
||||
-- Laravel otomatik encode/decode yapar
|
||||
```
|
||||
|
||||
### Model Cast Kullanımı
|
||||
```php
|
||||
protected $casts = [
|
||||
'header_data' => 'array',
|
||||
'footer_data' => 'array',
|
||||
'sections_data' => 'array',
|
||||
];
|
||||
```
|
||||
|
||||
## Git Branch
|
||||
|
||||
```bash
|
||||
feature/dynamic-template-system
|
||||
```
|
||||
|
||||
## Menü Template Sistemi
|
||||
|
||||
### Kurulum
|
||||
|
||||
Menü template sistemi global bir ayardır. Admin panelden oluşturduğunuz menü template'ini Settings'e manuel olarak eklemeniz gerekir:
|
||||
|
||||
#### 1. Admin Panelde Menü Template Oluştur
|
||||
```
|
||||
Admin Panel → Menü Şablonları → Yeni Menü Şablonu
|
||||
```
|
||||
|
||||
**Örnek Menü Template:**
|
||||
```html
|
||||
<nav class="navbar">
|
||||
<div class="menu-container">
|
||||
{menu}
|
||||
</div>
|
||||
</nav>
|
||||
```
|
||||
|
||||
#### 2. Settings'e Menü Template ID Ekle
|
||||
Admin panelden yeni bir Setting oluşturun:
|
||||
- **Key:** `menu_template_id`
|
||||
- **Type:** `integer`
|
||||
- **Value:** Menü template ID'niz (örn: 1, 2, 3)
|
||||
- **Group:** `general`
|
||||
|
||||
#### 3. Kullanım
|
||||
|
||||
Menü otomatik olarak render edilir. Header template'inde `{menu}` placeholder'ı kullanabilirsiniz:
|
||||
|
||||
**Header Template Örneği:**
|
||||
```html
|
||||
<header class="site-header">
|
||||
<div class="logo">
|
||||
<img src="{image.logo}" alt="{text.site_name}">
|
||||
</div>
|
||||
<nav class="main-nav">
|
||||
{menu}
|
||||
</nav>
|
||||
</header>
|
||||
```
|
||||
|
||||
**Çıktı:**
|
||||
```html
|
||||
<header class="site-header">
|
||||
<div class="logo">
|
||||
<img src="/storage/logo.png" alt="My Site">
|
||||
</div>
|
||||
<nav class="main-nav">
|
||||
<ul class="menu">
|
||||
<li class="menu-item">
|
||||
<a href="/home" class="menu-link">Ana Sayfa</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="/about" class="menu-link">Hakkımızda</a>
|
||||
<ul class="submenu">
|
||||
<li class="submenu-item">
|
||||
<a href="/about/team" class="submenu-link">Ekibimiz</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
```
|
||||
|
||||
### Menü Rendering
|
||||
|
||||
Menü otomatik olarak Pages tablosundan alınır:
|
||||
- Sadece `status = 'published'` olanlar
|
||||
- Sadece `show_in_menu = true` olanlar
|
||||
- Hierarchical yapı korunur (parent_id ile)
|
||||
- `sort_order` ile sıralanır
|
||||
|
||||
### Cache
|
||||
|
||||
Menü öğeleri 1 saat süreyle cache'lenir. Cache'i temizlemek için:
|
||||
|
||||
```php
|
||||
use App\Services\MenuService;
|
||||
|
||||
MenuService::clearCache();
|
||||
```
|
||||
|
||||
## İmplement Adımları
|
||||
|
||||
1. ✅ Migrations oluştur (4 adet)
|
||||
2. ✅ Model'ler oluştur (4 adet)
|
||||
3. ✅ Filament Resources oluştur (4 adet)
|
||||
4. ✅ TemplateService helper oluştur
|
||||
5. ✅ PageForm'u güncelle (template tabs ekle)
|
||||
6. ✅ PageRenderer component oluştur
|
||||
7. ✅ Lang dosyaları oluştur (8 adet)
|
||||
8. ✅ Frontend route ve controller güncelle
|
||||
9. ✅ Menü Service eklendi
|
||||
10. ✅ Test
|
||||
|
||||
---
|
||||
|
||||
**Not:** Bu sistem mevcut `page_sections` sisteminin yerini alacak ve çok daha güçlü bir alternatif sunacaktır.
|
||||
|
||||
Reference in New Issue
Block a user