WidgetManagement Modülü

Kapsamlı Sistem Mimarisi & Veri Akışı Analizi

📊 DETAYLI RAPOR
📅 13 Ocak 2026 🔍 151 PHP Dosya + 42 Blade View 📁 Multi-Tenant Mimarisi

📝 Basit Anlatım (Herkes İçin)

WidgetManagement modülü, web sitelerinde "bloklardan" oluşan sayfalar tasarlamayı sağlar. Tıpkı Lego gibi, önceden tasarlanmış bileşenleri (hero başlık, card listesi, form vb.) sürükle-bırak yöntemiyle sayfaya ekleyebilir, özelleştirebilirsiniz.

Widget nedir? Hazır bir bileşen şablonudur. Örneğin "Hero" widget'ı büyük başlıklı bir banner, "Card Grid" widget'ı ise çok sayıda kartı yan yana gösteren bir bloktur.

Nasıl çalışır?

  1. Admin panelinde widget galerisi açılır (hazır şablonlar görülür)
  2. "Hero" widget'ı seçilir ve sayfaya eklenir (TenantWidget oluşur)
  3. Widget kopyalanabilir, ayarları değiştirilebilir (başlık, renkler vb.)
  4. Widget'ın "item'leri" vardır (örneğin "3 item'li card widget" → 3 kart)
  5. Sayfaya açılır, ziyaretçiler göz atabilir, form doldurabilir vb.

💡 Neden önemli? Bu sistem sayesinde kodlamacı olmayan insanlar da sayfa tasarlayabilir, ürün kataloğu oluşturabilir, haber sayfaları düzenleyebilir.

🔧 Teknik Detaylar (Geliştiriciler İçin)

Modül Dosya Yapısı

Modules/WidgetManagement/
├── App/
│   ├── Http/
│   │   ├── Controllers/      # Preview, FormBuilder controller'ları
│   │   └── Livewire/         # Widget yönetim UI bileşenleri (9 component)
│   ├── Models/               # Widget, TenantWidget, WidgetItem, WidgetCategory
│   ├── Services/             # WidgetService, WidgetItemService, Render services
│   ├── Support/              # ShortcodeParser, HandlebarsRenderer
│   ├── Jobs/                 # BulkDeleteWidgets, BulkUpdateWidgets
│   └── Helpers/              # WidgetHelper.php (15+ helper function)
├── database/
│   ├── migrations/           # Central DB migrations (Widget, Category, Module)
│   ├── migrations/tenant/    # Tenant DB migrations (TenantWidget, WidgetItem)
│   └── seeders/              # Demo widget seeder'ları
├── config/
│   └── config.php
├── routes/
│   ├── admin.php             # Admin panel yönlendirmeler
│   └── web.php               # (boş - frontend route yok)
├── resources/views/
│   ├── blocks/               # Widget şablon dosyaları (40+ folder)
│   │   ├── hero/             # Hero banner widget'ları
│   │   ├── layout/           # Layout widget'ları (grid, columns)
│   │   ├── cards/            # Card widget'ları
│   │   ├── form/             # Form widget'ları
│   │   ├── modules/          # Module veri kaynağı (Portfolio, Blog vb.)
│   │   └── ...
│   ├── livewire/             # Admin UI view'ları
│   └── widget/               # Widget render helper view'ları
└── lang/                     # Türkçe ve İngilizce dil dosyaları

📊 Veritabanı Mimarisi

Central Database (tuufi_4ekim)

Tablo Amaç Kritik Alanlar
widget_categories Widget kategorilerine ayırma title, slug, is_active, parent_id (hierarchical)
widgets Widget şablonları (master) name, slug, type, content_html, settings_schema, has_items
widget_modules Hangi modülde hangi widget var widget_id (FK), module_id (FK)
tenant_widgets ⚠️ Kiraları tarafından oluşturulan widget instance'ları widget_id (FK to central), order, settings (JSON), position
widget_items ⚠️ Widget item'leri (dinamik widget'lar) tenant_widget_id (FK), content (JSON), order

Tenant Database (her kiracı)

Tablo Amaç Yapı
tenant_widgets Kiracı tarafından oluşturulan widget instance'ları widget_id (cross-DB FK), settings (JSON), custom_html, custom_js, position
widget_items Widget'ın dinamik item'leri tenant_widget_id (FK), content (JSON), order
⚠️ Önemli Not: tenant_widgets ve widget_items BOTH merkezi ve tenant DB'de var! Merkezi DB'deki neden? Cross-database foreign key desteği için. Tenant DB'si gerçek veri.
🗄️ Model İlişkileri & Veri Akışı

1. Widget (Central Model)

class Widget extends Model {
    use CentralConnection, HasMediaManagement;
    
    // Temel özellikler
    - id: PK
    - name, slug, type (file|module|standard)
    - content_html, content_css, content_js
    - item_schema (JSON - dinamik item'ler için)
    - settings_schema (JSON - widget ayarları)
    - has_items: boolean
    - is_active, is_core
    
    // İlişkiler
    - hasMany('TenantWidget')        ← Bunu kaç kere kullandı
    - belongsTo('WidgetCategory')
    - belongsToMany('Module')        ← Hangi modüllerde aktif
}

İndeksler: type, is_active, has_items, composite (is_active + type)

2. TenantWidget (Tenant Model)

class TenantWidget extends Model {
    // Kiracı tarafından oluşturulan widget örneği
    - id: PK
    - widget_id: FK → Widget (merkezi DB'de)
    - order: sıralama
    - position: sayfa bölümü (header, footer, sidebar vb.)
    - settings: JSON (başlık, renkler, kontrol ayarları)
    - display_title: widget başlığı
    
    // Custom HTML/CSS/JS için
    - is_custom: bool
    - custom_html, custom_css, custom_js
    - is_active: bool
    
    // İlişkiler
    - belongsTo('Widget')            ← Merkezi şablona bağlantı
    - hasMany('WidgetItem')          ← Dinamik item'ler
}

İndeksler: widget_id, is_active, position, composite (widget_id + is_active)

3. WidgetItem (Tenant Model)

class WidgetItem extends Model {
    // Widget'ın dinamik item'leri (örn: Card Grid widget'ında her kart)
    - id: PK
    - tenant_widget_id: FK → TenantWidget
    - content: JSON (title, description, image, URL vb.)
    - order: sıralama
    
    // İlişkiler
    - belongsTo('TenantWidget')
}

İndeksler: tenant_widget_id, order

4. WidgetCategory (Central Model)

class WidgetCategory extends Model {
    use Sluggable, CentralConnection;
    
    // Widget'ları kategorilendirme
    - widget_category_id: PK
    - title, slug, icon
    - parent_id: Alt kategori için
    - is_active: bool
    
    // İlişkiler
    - hasMany('Widget')
    - hasMany('WidgetCategory', 'parent_id')  ← Hierarchical
    - belongsTo('WidgetCategory', 'parent_id') ← Parent kategori
}

İlişki Türü: Hierarchical (alt kategoriler)

⚙️ Widget Render Süreci (Ayrıntılı Akış)

Frontend Render Akışı: @widget(tenantWidgetId)

1. Blade'de: @widget(5)
2. Helper: widget_by_id(5) → WidgetService.renderSingleWidget()
3. Cache kontrol: Redis'te var mı? EVET → Return
4. Cache miss: TenantWidget::with('widget', 'items').find(5)
5. Render:

  • İf custom (is_custom=true): custom_html render et
  • Else: Widget.content_html render et
  • Items varsa: {{#each items}} template işle
  • Settings varsa: {{variable}} değişkenlerini değiştir
  • Module widget: ilişkili tablo verilerini çek (Handlebars)
6. CSS/JS toplama: Global static array'e ekle
7. HTML output → Sayfa'ya embed
8. Cache'e kaydet (1440 dakika)

Admin Panel Yönetim Akışı

Admin: /admin/widgetmanagement

WidgetComponent (Livewire) → Widget listesi + Filtreler

createInstance(widgetId):

  • Widget::find(widgetId) → Şablonu getir
  • TenantWidget::create([widget_id, order, settings])
  • Cache temizle: clearWidgetCache()

Widget ayarları: /admin/widgetmanagement/settings/{tenantWidgetId}

WidgetSettingsComponent (Livewire) → Form başlığı, renkler vb. güncelle

Widget item'leri: /admin/widgetmanagement/items/{tenantWidgetId}

WidgetItemComponent (Livewire) → Her item düzenle/ekle/sil

Sil/Güncelle → Cache temizle → Frontend yenile

🛠️ Helper Functions & Blade Directives

Frontend (View'larda Kullanım)

@widget(tenantWidgetId)

ID'ye göre widget render et

@widgetblock('slug')

Slug'a göre widget render et

@widgets('position')

Belirli pozisyondaki tüm widget'ları render et

@parsewidgets($content)

İçerikteki widget shortcode'ları işle (örn: [[widget:hero param1=val1]])

@modulewidget(moduleWidgetId)

Module widget'ını render et

PHP Helper Functions

widget_by_id(id)              // ID'ye göre render
widget_by_slug(slug, params)   // Slug'a göre, params ile
widgets_by_position(position)  // Pozisyon'a göre render
parse_widget_shortcodes(html)  // Shortcode'ları işle
widget_cached(id, ttl, params) // Cache ile render
module_widget_by_id(id)        // Module widget render
module_widgets_by_module(modId)// Module'daki tüm module widget'ları
widget_file_by_id(id)          // File widget render
🔌 Service Layer & Business Logic

WidgetService (Ana Service)

Singletonized: app('widget.service')

Temel metodlar:
- getAllWidgets()              → Tüm widget'lar
- getActiveWidgets()           → Aktif widget'lar
- getWidgetsByType(type)       → Türe göre
- getWidgetsForModule(modId)   → Modüle göre
- getTenantWidgets(position)   → Kiracı widget'ları
- renderWidgetsInPosition()    → Pozisyon'daki widget'ları render
- renderSingleWidget()         → Tekil widget render (cache ile)
- processWidget()              → Widget HTML işleme
- renderWidgetHtml()           → Handlebars şablon işleme
- clearWidgetCache()           → Cache temizle

Cache stratejisi:
- Key format: widget_{tenantId}_widget_{widgetId}
- TTL: 1440 dakika (24 saat)
- Kullanım: settings, CSS/JS, item'ler değişirse otomatik sıfırlanır

WidgetRenderService (Şablon İşleme)

Sorumlulukları:
- processVariables()           → {{title}} gibi değişkenleri değiştir
- processItems()               → {{#each items}} döngüleri işle
- processModuleData()          → Module verilerini Handlebars'la işle
- processConditionalBlocks()   → {{#if condition}} blokları işle

Template Syntax:
- Değişkenler: {{variable}}, {{widget.title}}, {{items.0.name}}
- Koşullar: {{#if condition}}...{{/if}}
- Döngüler: {{#each items}}...{{/each}}
- Handlebars desteği: LitHTML/Handlebars.js

WidgetItemService (Item Yönetimi)

- getItemsForWidget()  → Widget'ın item'lerini al
- addItem()            → Item ekle (auto UUID, is_active=true)
- updateItem()         → Item güncelle
- deleteItem()         → Item sil
- reorderItems()       → Item'leri sırala

ShortcodeParser (İçerik Parse)

Desteklenen syntaxlar:
[[widget:slug param1=val1 param2=val2]]
[[widget:slug]]İçerik[[/widget:slug]]
[[module:moduleId]]
[[file:fileId]]

Örnek:
[[widget:hero title="Başlık" subtitle="Alt başlık"]]
[[widget:cards]]
  [[widget:card title="Card 1"]]
[[/widget:cards]]
👥 Multi-Tenant Mimarisi

Database Dağılımı

Central Database (tuufi_4ekim)
  • Widget (CentralConnection trait) - Widget şablonları
  • WidgetCategory (CentralConnection trait) - Kategoriler
  • widget_modules - Widget-Module ilişkisi
  • tenant_widgets ⚠️ - Tüm kiracılar için (cross-DB FK destekleme)
  • widget_items ⚠️ - Tüm kiracılar için (cross-DB FK destekleme)
Tenant Database (her kiracı, örn: tenant_muzibu_1528d0)
  • tenant_widgets - Kiracının oluşturduğu widget instance'ları
  • widget_items - Kiracının widget item'leri
  • ⚠️ widget, widget_categories, widget_modules YOK! (merkezi DB'den okunur)
Veri Akışı:

1. Kiracı, admin panelinde /admin/widgetmanagement'e gider
2. TenantAware middleware kiracıyı set eder
3. Widget::all() → Merkezi DB'den (şablonlar)
4. TenantWidget::all() → Tenant DB'den (kiracının widget instance'ları)
5. Widget şablonu render: Merkezi DB'deki content_html + Tenant DB'deki settings
6. Cross-DB relationship: TenantWidget.widget_id → Widget.id (merkezi DB)

🧩 Livewire Components (Admin Panel)
Component URL/Rota Fonksiyonu
WidgetComponent /admin/widgetmanagement Widget listesi, ara, filtrele, instance oluştur
WidgetManageComponent /admin/widgetmanagement/manage/{id} Widget şablonunu edit (HTML/CSS/JS), schema oluştur
WidgetGalleryComponent /admin/widgetmanagement/gallery Widget galerisi, görsel önizleme
WidgetSettingsComponent /admin/widgetmanagement/settings/{tenantWidgetId} Widget ayarları düzenle (title, colors, custom values)
WidgetItemComponent /admin/widgetmanagement/items/{tenantWidgetId} Widget item'lerini listele, drag-drop sırala
WidgetItemManageComponent /admin/widgetmanagement/manage/item/{tenantWidgetId}/{itemId} Tekil item düzenle (title, description, image vb.)
WidgetCodeEditorComponent /admin/widgetmanagement/code-editor/{id} Widget'ın HTML/CSS/JS kodunu editör'de düzenle
WidgetCategoryComponent /admin/widgetmanagement/category Widget kategorilerini listele ve yönet
FileWidgetListComponent /admin/widgetmanagement/file-widgets File widget'ları listele (view dosyası yükleme)
ModuleWidgetListComponent /admin/widgetmanagement/modules Module widget'ları listele (Blog, Portfolio vb.)
WidgetFormBuilderComponent /admin/widgetmanagement/form-builder/{widgetId}/items Item ve Settings schema oluştur (drag-drop form designer)
🚦 Routes & Permission Kontrolü

Admin Rota Yapısı

Prefix: /admin/widgetmanagement
Middleware: ['admin', 'tenant']

Public (view):
- GET / → Widget listesi
- GET /gallery → Galeri
- GET /items/{tenantWidgetId} → Item listesi
- GET /preview/template/{widgetId} → Widget önizlemesi
- GET /preview/instance/{tenantWidgetId} → Instance önizlemesi

Protected (root role):
- GET /manage/{id?} → Widget editör
- GET /code-editor/{id} → Kod editör
- GET /file-widgets → File widget'ları
- GET /modules → Module widget'ları

Protected (update permission):
- GET /settings/{tenantWidgetId} → Widget ayarları
- GET /manage/item/{tenantWidgetId}/{itemId} → Item editör
- POST /form-builder/{widgetId}/save/{schemaType} → Schema kaydet

Preview endpoints:
- GET /preview/embed/{tenantWidgetId}
- GET /preview/embed/json/{tenantWidgetId}
- GET /api/module/{moduleId}
📦 Widget Türleri

Widget Type'ları

File Widget (type: 'file')
  • Blade view dosyasından render edilir
  • file_path: 'blocks/hero/simple/view'
  • View: widgetmanagement::blocks.hero.simple.view
  • Statik widget'lar (değişebilir ama item yok)
Module Widget (type: 'module')
  • Diğer modüllerden veri çeker (Blog, Portfolio vb.)
  • data_source: 'ModuleName/ClassName'
  • Handlebars şablon ile render
  • Dinamik veri işleme (loop, condition)
Standard Widget
  • HTML/CSS/JS içeriği içeren widget'lar
  • content_html, content_css, content_js
  • Item'ler olabilir (dinamik item'ler)
  • Settings ile özelleştirilebilir
Custom Widget (is_custom: true)
  • TenantWidget'ın kendi HTML/CSS/JS'i
  • custom_html, custom_css, custom_js
  • Widget şablonunu override eder
  • Her kiracı kendi custom widget'ını oluşturabilir

Mevcut Widget Şablonları (40+)

Hero widget'ları:
- blocks/hero/simple → Basit başlık
- blocks/hero/hero-1 → İmaj ile
- blocks/hero/ixtif-hero → İxtif özel

Layout widget'ları:
- blocks/layout/one-column → 1 kolon
- blocks/layout/two-columns → 2 eşit
- blocks/layout/three-columns → 3 eşit
- blocks/layout/four-columns → 4 eşit
- blocks/layout/masonry-grid → Masonry
- blocks/layout/asymmetric-grid → Asimetrik
- blocks/layout/sidebar-main-sidebar → Kenar çubuğu

Card widget'ları:
- blocks/cards/basic → Basit kartlar
- blocks/cards/grid → Grid düzeni

Form widget'ları:
- blocks/form/contact-form → İletişim formu
- blocks/form/contact-assistant → AI asistan formu
- blocks/form/muzibu-contact → Muzibu özel

Module widget'ları:
- blocks/modules/portfolio/list → Portfolio listesi
- blocks/modules/portfolio/detail → Portfolio detay
- blocks/modules/page/home → Ana sayfa
- blocks/modules/page/recent → Yeni sayfalar
- blocks/modules/announcement/list → Duyuru listesi

Diğer:
- blocks/testimonials/basic → Testimoniallar
- blocks/features/basic → Özellikler
- blocks/media/image → Resim
- blocks/content/hero → İçerik
- blocks/content/text → Metin
⚡ Cache Stratejisi & Performans

Cache Sistemi

✅ Cache AÇIK (Varsayılan)

WidgetService::$useCache = true
TTL: 1440 dakika (24 saat)
Key: widget_{tenantId}_widget_{tenantWidgetId}

Cache İnvalidasyon (Otomatik)

TenantWidget değişirse:
- update() → clearWidgetCache() çağrılır
- delete() → clearWidgetCache() çağrılır
- Livewire save → clearWidgetCache()

WidgetItem değişirse:
- create/update/delete → clearWidgetCache()

Position değişirse:
- TenantWidget::where('position', $pos) → Cache temizle

Settings değişirse:
- TenantWidget.update(['settings' => ...]) → Cache temizle

Static Assets Cache (CSS/JS)

Widget render sırasında:
- WidgetService::$loadedCssFiles[] → Benzersiz dosyaları topla
- WidgetService::$loadedJsFiles[] → Benzersiz dosyaları topla
- Sayfanın footer'ında:
  {{ WidgetService::getStylesOutput() }}
  {{ WidgetService::getScriptsOutput() }}

Avantaj:
- Tekrarlanan CSS/JS yüklenmez
- Performance optimize edilir
- Sayfa yükleme süresi azalır
📁 Media Management & Storage

Media Yönetimi (Spatie MediaLibrary)

Widget Model Media Collections

Widget, Spatie MediaLibrary trait'ini kullanır
Collections: Dinamik (field name bazında)
Disk: Tenant-aware (merkezi vs tenant storage)
Example: Widget.registerMediaCollections() → boş (runtime'da field name'e göre belirlenir)

Storage Disk Yapısı

Widget Model getMediaDisk() metodu:
- Tenant context varsa → tenant disk kullanılır
- Central context varsa → public disk kullanılır
- Disk path: storage/app/public/
- URL: /storage/tenant{tenantId}/ (tenant context)

Örnek:
- Merkezi: /storage/widgets/hero-bg.jpg
- Tenant 2 (ixtif): /storage/tenant2/widgets/hero-bg.jpg
- Tenant 1001 (muzibu): /storage/tenant1001/widgets/hero-bg.jpg
⚠️ Önemli Teknik Notlar
❌ Cross-Database Foreign Key

TenantWidget.widget_id → Widget (merkezi DB)
MySQL'de kısıtlama yoktur, Laravel açıkça FK belirtmemişti
İndeksler eklenmiş (lookup hızlandırma) ✅

⚠️ Widget Şablon Düzenleme

Widget.content_html düzenlenmesi → Tüm TenantWidget'ları etkiler!
Yeni TenantWidget'lar yeni HTML alır
Eski TenantWidget'lar eski HTML'i kullanmaya devam eder (is_custom=false ise)
Çözüm: Widget versiyonlama sistemi (ileride eklenebilir)

ℹ️ Handlebars vs Regular Template

Regular: {{variable}} → Server-side PHP tarafından değiştir
Handlebars: {{#if}}, {{#each}} → Browser'da Handlebars.js çalıştır
WidgetService::setHandlebarsUsage(false) → Regular mode
Module widget'ları Handlebars tercih eder (dinamik veri işleme)

🔒 Security (XSS Koruması)

WidgetRenderService::escapeHtml() → String değerleri escape et
HTML content'e DOMPurify.js eklenebilir (ileride)
Admin editör'de malicious code girilebilir ⚠️ → Güvenilir editor tarafından kontrol

✅ Performance Optimizasyonları
  • Widget query: with('widget', 'items') eager loading
  • Cache: 24 saat TTL, Redis tabanlı
  • İndeksler: widget_id, is_active, position, composite indices
  • Static CSS/JS: Benzerlik tespiti, duplikasyon önleme
  • Module data: Lazy loading, query optimization
🔄 Veri Akış Şeması (Detaylı)

Widget Instance Oluşturma Akışı

Admin Paneli
    ↓
WidgetComponent.createInstance(widgetId)
    ↓
Widget::find(widgetId) ← Merkezi DB'den şablonu getir
    ↓
TenantWidget::create([
    'widget_id' => $widgetId,
    'order' => $maxOrder + 1,
    'settings' => ['title' => $widget->name, 'unique_id' => UUID]
])
    ↓
Database: tenant_{kiracı}.tenant_widgets INSERT
    ↓
clearWidgetCache(tenant()->id, null) ← Kiracının tüm position cache'ini temizle
    ↓
Frontend'de @widgets('position') çağrılırsa:
    ↓
Yeni TenantWidget render edilir ✅

Widget Item Ekleme Akışı

Admin: /admin/widgetmanagement/manage/item/{tenantWidgetId}
    ↓
WidgetItemManageComponent.save()
    ↓
WidgetItemService.addItem(tenantWidgetId, {
    'title' => 'Yeni Card',
    'description' => '...',
    'image' => 'file.jpg'
})
    ↓
1. unique_id OTOMATIK oluştur (UUID)
2. is_active = true (VARSAYILAN)
3. order = max(order) + 1
4. Database: tenant_{kiracı}.widget_items INSERT
    ↓
5. clearWidgetCache(tenant()->id, tenantWidgetId)
    ↓
Frontend'de @widget(tenantWidgetId) çağrılırsa:
    ↓
    1. Cache miss (temizlendi)
    2. TenantWidget::with('widget', 'items').find(tenantWidgetId)
    3. Widget.content_html (şablon)
    4. WidgetItems processItems() ile {{#each items}}...{{/each}} işle
    5. New item görünür ✅

Widget Render (Frontend) Akışı

Blade: @widget(5)
    ↓
Helper: widget_by_id(5)
    ↓
Cache key: widget_{tenantId}_widget_5
    ↓
Cache varsa? EVET → Return (24 saat eski veri)
Cache miss? → Devam et
    ↓
TenantWidget::with('widget', 'items').find(5)
    ↓
TenantWidget.is_custom == true?
    ↓
    EVET: custom_html render et
    HAYIR:
        1. Widget.content_html al
        2. WidgetItems varsa: renderService.processItems()
        3. Settings varsa: renderService.processVariables()
        4. Module widget ise: getData() → renderService.processModuleData()
        5. CSS/JS dosyaları ekle
    ↓
Cache'e kaydet (TTL: 1440 dakika)
    ↓
HTML çıktı + CSS/JS global array'e ekle
    ↓
Page footer'da:
    WidgetService::getStylesOutput() →