Bundles/Internal/Webhub — Frontend kimlik, SEO, navigasyon, entegrasyon ve SSS
Doğrulanan kod tabanı: d49b4413 (dal feat/fw-license-master) · Tarih: 2026-10-05 · Yayın: 0.9.7 = bu commit + sonrası; belge yalnız doğrulama anındaki kodu anlatır
Kaynak klasör: Bundles/Internal/Webhub/ — 43 *.php + 19 *.rbn.php görünüm.
Envanter: 43 php dosyasının 43'ü bu belgelerde anlatıldı.
1. Ne işe yarar, kimler kullanır
Webhub, ziyaretçinin gördüğü siteyi yöneten modüldür: marka kimliği
(firma adı, slogan, iletişim), SEO ayarları ve SEO puanlama motoru, frontend
menüleri (navbar/footer), SSS ve
harici entegrasyonlar (Analytics, AdSense, head/body/footer script).
Kimler çağrır: geliştirici paneli (WebhubController üzerinde
#[Module(panel:'developer', context:'panel')], WebhubController.php:8).
Modülün verisi proje kapsamlıdır: z_settings (project.settings) ve
z_frontend_menus, z_pages, z_faqs tabloları.
2. Alt dallar ve belgeleri
| Alt dal |
*.php |
Belge |
Kısa görev |
Controllers/ |
7 |
(bu belge §2.1) |
Kök + 6 alt modül kontrolörü |
Models/ |
3 |
(bu belge §2.2) |
ModuleData, WebhubMap, SeoConfig |
Services/ |
6 |
(bu belge §2.3) |
6 servis, ikisi SettingsService türevi |
Providers/ |
3 |
(bu belge §2.4) |
Ayar kayıt köprüsü, frontend menü, sayfa/kural |
Handlers/ |
5 |
Seo.md |
SEO tarama pipeline'ı (4 tarayıcı + 1 danışman) |
Views/ |
19 |
(aşağıda listeli) |
Panel arayüzü |
| Toplam |
43 |
|
find Bundles/Internal/Webhub -name '*.php' \| wc -l = 43 |
2.1 Controllers/ (7)
| Dosya |
Görev |
Önemli public yöntemler |
WebhubController.php |
Modül kökü; #[Module(name:'webhub', data:ModuleData::class, service:'webhub', panel:'developer', context:'panel', icon:'bi-globe2')]. filterSettings() yardımcısı tüm alt modüllerin ortak daraltıcısıdır. |
index() (:19), filterSettings(array $settings,array $allowedKeys): array (:27 protected) |
IdentityController.php |
#[SubModule(entity:'identity', entityName:'setting', bulkInputKey:'settings')]. Firma kimliği: değer ekranı (index, grup company + active()) ve yapı ekranı (manage, tümü). Yeni alan group_id => 1 ile yazılır. |
index(): void (:24), manage(): void (:40), create(): void (:55), update(): void (:75) |
SeoController.php |
#[SubModule(entity:'seo', entityName:'setting', bulkInputKey:'settings')]. SEO alanları + puanlama ekranları (score, report, scan). Yeni alan group_id => 2. |
index() (:25), score() (:41), report() (:55), scan() (:72), manage() (:89), create() (:104), update() (:123) |
IntegrationsController.php |
#[SubModule(entity:'integrations', …)]. Script/etiket yönetimi ve AdSense kurulumu. Yeni alan group_id => 10. |
index() (:25), manage() (:44), save() (:62), setupAdsense() (:90), adsense() (:96) |
NavigationController.php |
#[SubModule(entity:'navigation', service:'frontendMenu')]. Frontend menü ağacı. |
index(): void (:22), getModalData($id): array (:35 protected), save(): void (:52) |
FaqController.php |
#[SubModule(entity:'faq', service:'faq', provider:'faq')]. SSS kayıtları; doğrulama kuralları burada. |
index() (:25), rules(): array (:40 protected), save(): void (:53) |
2.2 Models/ (3)
| Dosya |
Görev |
Önemli public yöntemler |
ModuleData.php |
#[Bundle(name:'webhub', context:'developer', map:WebhubMap::MAP)]. 5 servis + 4 handler + 2 provider alias'ı; 6 rota grubu (kimlik, seo, navigasyon, faq, entegrasyon, dashboard). |
registerMap(): array (:27), registerRoutes(): void (:56) |
WebhubMap.php |
Menü haritası: 1 kimlik + 6 alt modül, 3'ünde alt alt modül (seo.score, seo.report, integrations.manage, integrations.adsense). integrations dalında required_role: 'developer' var. |
const MAP (:18) |
SeoConfig.php |
Puan → renk/ikon/metin eşlemesi (4 kademe). Görünüm tek yerden renk alır. |
getScoreDisplay(int $score): array (:17) |
2.3 Services/ (6)
| Dosya |
Görev |
Önemli public yöntemler |
WebhubService.php |
Ayar odaklı akıcı servis. boot() içinde provider('webhub')'a bağlanır (:30); read()/withKey() ölçüt biriktirir, save()/action() çağırıp ölçütü sıfırlar (:69,:81). |
boot(): void (:26), read(?string $groupKey=null): self (:38), withKey(string $key): self (:50), save(mixed $id=null,array $data=[]): self (:60), action(?string $name=null,array $payload=[]): self (:77) |
SeoScannerService.php |
Tarama pipeline yürütücüsü: 3 handler'ı sırayla çalıştırır, seo.advice handler'ıyla tavsiye üretir. getReport() kayıtlı seo-report/seo-score ayarlarını view-ready diziye çevirir. |
scan(): array (:18), getReport(): array (:55) |
FrontendMenuService.php |
Menü servisi; getList() aktif menüyü hesaplar (is_current/active). |
parents(?int $excludeId=null): array (:20), getList(?int $parentId=null,bool $onlyActive=true): array (:28), getTree(): array (:48), getNested(int $parentId=0): array (:56) |
IntegrationsService.php |
SettingsService türevi. setupAdsense() SettingsConfig::DEFAULT_SETTINGS içindeki adsense_*/ads_* anahtarlarını projeye tohumlar (yoksa ekler). |
setupAdsense(): bool (:22) |
FaqService.php |
22 satır, tek gövdesiz. Yalnız protected $targetModel='project.faq' ve protected array $cacheKeys=['faq'] beyan eder; tüm CRUD BaseService'ten gelir. |
— |
2.4 Providers/ (3)
| Dosya |
Görev |
Önemli public yöntemler |
WebhubProvider.php |
Ayar kayıt köprüsü. save() anahtarı yoksa label_en → label_tr → setting_name sırasıyla slug üretir; value alanını setting_value'ya eşler; field_options boşsa '[]' yapar; sonra settings_group_ önekli önbelleği düşürür. actionScan() SEO sonuçlarını dört ayara yazar. |
save(array $data): bool\|int (:30), purgeCache(): void (:95 private), actionScan(string $name,array $payload): mixed (:108) |
FrontendMenuProvider.php |
Menü sorgu/onarım uzmanı. getTree() iki kipte çalışır: düz + girinti (indent_level) ve iç içe (children). getParents() düzenleme dalını ve tüm alt dalını listeden çıkarır (döngüsel hiyerarşi koruması). destroy() alt menü varsa silmeyi reddeder. |
getFrontendMenus(?int $parentId=null,bool $onlyActive=true,bool $onlyParents=false): array (:17), getTree(int $parentId=0,int $depth=0,?array $elements=null,bool $nested=false): array (:44), save(array $data): bool\|int (:78), getShowInOptions(): array (:89), destroy(int $id): bool (:102), getParents($excludeId=null): array (:116), findDescendants(int,array,array&): void (:146 private) |
2.5 Views/ (19)
| Görünüm |
Sağlayan uç |
Görev |
webhubdash.rbn.php |
WebhubController::index |
Modül ana ekranı |
Identity/identity.rbn.php |
IdentityController::index |
Firma kimliği değer kartları |
Identity/manage.rbn.php |
IdentityController::manage |
Alan listesi + sıralama |
Identity/Partials/modal.rbn.php |
IdentityController::modal |
Alan ekle/düzenle; label_en yazılınca anahtar slug'lanır (:92-95) |
Seo/index.rbn.php |
SeoController::index |
Aktif SEO alanları |
Seo/manage.rbn.php |
SeoController::manage |
SEO alan şeması |
Seo/score.rbn.php |
SeoController::score |
Tek sayı + renk/ikon (SeoConfig::getScoreDisplay) |
Seo/report.rbn.php |
SeoController::report |
Kriter listesi + kural tabanlı tavsiye + Gemini metni |
Seo/Partials/modal.rbn.php |
SeoController::modal |
Alan ekle/düzenle |
Navigation/index.rbn.php |
NavigationController::index |
Menü listesi |
Navigation/Partials/modal.rbn.php |
NavigationController::modal |
Üst menü seçimi + show_in seçenekleri |
Faq/index.rbn.php |
FaqController::index |
SSS listesi |
Faq/Partials/modal.rbn.php |
FaqController::modal |
Soru/cevap düzenleyici |
Integrations/index.rbn.php |
IntegrationsController::index |
5 genel script ayarı |
Integrations/manage.rbn.php |
IntegrationsController::manage |
Entegrasyon alan şeması |
Integrations/adsense.rbn.php |
IntegrationsController::adsense |
8 AdSense anahtarı (durum + slot) |
Integrations/Partials/modal.rbn.php |
IntegrationsController::modal |
Entegrasyon alanı ekle/düzenle |
Kapsama: 43/43.
3. Akış — SEO taraması (modülün en ağır yolu)
POST webhub/seo/scan
→ SeoController::scan() SeoController.php:72
→ $this->service('seoScanner') :75
→ SeoScannerService::scan() :18
├─ base_url = https? + $_SERVER['HTTP_HOST'] :20-22
├─ executePipeline(['seo.settings','seo.core','seo.dom'], $params) :30-34
│ → her handler execute() → [score, report]; skorlar TOPLANIR :105-111
│ · SettingsScannerHandler : execute() (25 puan)
│ · CoreScannerHandler : execute() (30 puan)
│ · DomScannerHandler : execute() (45 puan)
│ → score = min(100, Σ) :114
└─ handler('seo.advice')->execute([score, report]) :37-41
→ [advice (kural tabanlı), gemini (AI)] AdviceHandler.php:18
→ provider('webhub')->actionScan('save_seo_scan', $result) :78
→ dört ayara yazılır: seo-score, seo-report, seo-advice,
seo-gemini-advice WebhubProvider.php:111
→ purgeCache() (settings_group_*) :117
→ handleResult(true, …, 'seo/report') :82
Ayar kayıt akışı (Kimlik/SEO/Entegrasyon ortak):
POST webhub/seo/save
→ SeoController::update() SeoController.php:123
→ request->form([id, label_tr, setting_key]) :125-130
→ $this->service->action()->withId($id)->save($data) :131
├─ criteria['setting_key'] = … WebhubService.php:56
├─ provider->save(array_merge($data,$criteria)) :68
│ ├─ anahtar yoksa: label_en → label_tr → setting_name slug'ı :36-39
│ ├─ 'value' → 'setting_value' eşlemesi :45-49
│ ├─ field_options boşsa '[]' :52-56
│ ├─ var mı? update(id) : create() :65-83
│ └─ purgeCache() :86
└─ criteria = [] (akıcı döngü sıfırlama) :69
4. Yapılandırma / ayar anahtarları
| Öğe |
Yer |
Değer |
Not |
WebhubService::$cacheKeys |
WebhubService.php:21 |
['webhub_settings'] |
Bildiriliyor; yazma sonrası düşürülen önek settings_group_'dir (WebhubProvider.php:98) — farklı anahtarlar |
FaqService::$targetModel / $cacheKeys |
FaqService.php:20 |
project.faq / ['faq'] |
Kayıtlı alias (SystemPhysicalMapTrait.php:54) |
FrontendMenuService::$targetModel |
FrontendMenuService.php:15 |
FrontendMenus |
Registry'de frontendMenu alias'ı var; bu ad beyan olarak kullanılır |
IntegrationsService::$targetModel |
IntegrationsService.php:16 |
settings |
SettingsService türevi; anahtar önekli |
| SEO tarama puan tavanı |
SeoScannerService.php:114 |
min(100, Σ) |
Üç handler toplamı 25+30+45 = 100 tam puan |
seo-score / seo-report / seo-advice / seo-gemini-advice |
z_settings |
— |
actionScan() yazar, getReport() okur (WebhubProvider.php:112, SeoScannerService.php:60) |
show_in seçenekleri |
FrontendMenuProvider::getShowInOptions() |
0 üst+alt · 1 yalnız alt · 2 alt sütun başlığı · 3 gizli |
Sabit dizi, kodda değil (FrontendMenuProvider.php:89) |
adsense tohumlama |
IntegrationsService::setupAdsense() |
grup id varsayılanı 5 |
Grup bulunamazsa 5 kullanılır (:29-33) |
| Alan grupları (sabit id) |
IdentityController:66 1 · SeoController:113 2 · EmailController:51 3 · IntegrationsService:29 5 · IntegrationsController:82 10 |
— |
Grup id'leri koda gömülü; grup silinip yeniden oluşturulursa yanlış gruba yazar |
5. Tuzaklar ve kurallar (kodda görülen, ölçülmüş)
group_id sabitleri kırılgandır. create() çağrıları group_id => 1/2/10 yazar
(IdentityController.php:66, SeoController.php:113, IntegrationsController.php:82).
Grup id'leri veritabanında yeniden üretilirse yeni alan yanlış gruba düşer.
request->rawAll() denetleyicide. NavigationController::save() kural dizisi tanımlamak yerine
ham girdi kullanır ve bunu bilinçli yapar (yorum: FW-ALTYAPI-2 / B, NavigationController.php:54).
Beyaz liste CrudControllerTrait::crudInput() yolunda çalışır, bu yol trait'i kullanmıyor.
Denetleyici unset($data['project']) ile proje anahtarını düşürür — yazma kapsamı modelin beyanına bırakılır.
IntegrationsController::save() WebhubProvider::save()'ı hiç çağırmaz. Servis
IntegrationsService extends SettingsService → CrudServiceTrait; create()/update()
ikisi de save()'a düşer (CrudServiceTrait.php:128,140) ve save() önce
component('model') dener (:399-405) — SettingsModel çözüldüğü için
provider->save() dalına (:420-426) hiç girilmez. Sonuç: WebhubProvider'ın üç
koruması — slug türetme (:36-39), field_options JSON koruması (:52-56) ve
settings_group_ önbellek düşürme (:95-101) — bu yolda çalışmaz. Aynı desen
IdentityController::create/update ve SeoController::create/update için de geçerlidir
(withId()->save() → WebhubService::save() → provider->save(), orada çalışır);
fark, WebhubService'in $this->provider'ı boot()'ta bağladığı için (:30)
provider dalına düşmesidir.
getTree() iki kip, iki çağrı. getNested() iç içe ($nested=true),
getTree() düz+girinti (indent_level). getParents() yalnız düz kipi okur (:118) —
iç içe ağaçta indent_level olmadığı için seçenekler girintisiz görünürdü.
- Menü silme guard'ı sessiz başarısızlık.
destroy() alt menü varsa false döner
(FrontendMenuProvider.php:105); delete/{id} rotası bu false'u kullanıcıya
"silinemedi" diye ayırt etmeden gösterir. Backstage'deki karşılığı da aynı desende
(SidebarHandler::deleteCategory).
- Ölçüt sıfırlama akıcı döngüyü kısa ömürlü kılar.
WebhubService::save() ve action()
çalıştıktan sonra $this->criteria = [] yapar (:69,:81). Aynı servis örneği üzerinde
ikinci bir save() önceki withKey/withGroup filtresini kaybetmiş olur.
FaqController::index() dönüş tipi tutarsız. Diğerleri void/render çağırırken
bu bir return $this->render(...) yapar (:32-34) ve $this->paginate($faqs)->to('faqs')
zincirlemesi kullanır — to() çağrısının dönüş değeri atanır ama faqs olarak da geçirilir.
- SEO taraması kendi sunucusunu HTTP ile yoklar.
CoreScannerHandler ve DomScannerHandler
base_url'e remote->get() atar (CoreScannerHandler.php:46, DomScannerHandler.php:23).
$_SERVER['HTTP_HOST'] doğrudan kullanıldığı için tarama panelin erişildiği host'u
tarar; Host başlığı değiştirilmiş bir istek yanlış siteyi ölçebilir.
SSL doğrulaması kapalıdır (CURLOPT_SSL_VERIFYPEER => false, :50-51 / :23).
scan() puanı zaman içinde kaydedilen bir sayıdır. getReport() veritabanındaki
seo-report JSON'unu sayar; tarama yapılmamışsa seo-score 0 gelir ve
SeoConfig::getScoreDisplay(0) "Taranmadı" döner (SeoConfig.php:19) —
yani score=0 "kritik" değil "ölçülmedi" anlamına gelir.
- AdSense tohumlama
project_key'i elle yazar. setupAdsense() save() çağrısında
'project_key' => $projectKey alanını açıkça koyar (IntegrationsService.php:51);
diğer yollarda bu alan bilinçli olarak yazılmıyor (kapsam modelde).
FaqService boş bir sınıftır. 22 satır, yalnız iki protected beyan; herhangi bir
metot yoktur (FaqService.php:17). Tüm davranış BaseService + project.faq
repository'sinden gelir.
6. Örnek (gerçek koddan)
// Bundles/Internal/Webhub/Providers/FrontendMenuProvider.php:116-140 (özetlenmiş)
public function getParents($excludeId = null): array
{
$tree = $this->getTree();
$options = [];
// Düzenleme yapılıyorsa: kendisi + tüm alt dalı listeden çıkar (döngüsel koruması)
$forbiddenIds = [];
if ($excludeId) {
$forbiddenIds[] = (int) $excludeId;
$this->findDescendants((int) $excludeId, $tree, $forbiddenIds);
}
foreach ($tree as $item) {
if (in_array((int) $item['id'], $forbiddenIds)) {
continue;
}
$prefix = $item['indent_level'] > 0 ? str_repeat(' ', $item['indent_level'] * 2) . '↳ ' : '';
$options[] = ['id' => $item['id'], 'title' => $prefix . $item['title']];
}
return $options;
}
7. İlgili belgeler