RBN Core - Framework v0.9.7 76 belge

Yapısal veri (JSON-LD), <head> etiketleri, bağlantı rel, izin (consent)

Ölçüt: E:\localhost\docs\ bilgi bankası. Framework bu talimatlara BİREBİR uyar; proje yalnız veri verir, çıktıyı framework üretir.
Projede istisna, açma anahtarı, geriye dönük uyum YOKTUR.

1. Sayfa türü → JSON-LD

Tek yol: View::schema(<tür>, <veri>) → SchemaProvider::build() → SchemaPreset::execute() → SchemaBuilder → SchemaProvider::render() (tek @graph, SchemaPolicy kapısından geçer).

Tür Blok BreadcrumbList
corporate Organization (+ LocalBusiness alt tipi) + WebSite —
home WebPage yok
about, faq, legal, guide, page WebPage Anasayfa → sayfa
contact ContactPage Anasayfa → sayfa
blogList CollectionPage Anasayfa → Blog
blogCategory CollectionPage Anasayfa → Blog → kategori
blogPost BlogPosting (og:type=article) Anasayfa → Blog → kategori → yazı
productList CollectionPage + ItemList Anasayfa → … → liste
productDetail Product + Offer Anasayfa → Ürünler → ürün
service Service Anasayfa → hizmet
newsArticle NewsArticle (og:type=article) Anasayfa → Haberler → haber
tvSeries TVSeries Anasayfa → Diziler → dizi
movie Movie Anasayfa → Filmler → film
person Person Anasayfa → Oyuncular → kişi

FrontendBaseController::render() her sayfaya corporate türünü ekler (kurum + WebSite her sayfada). Kurum verisi: company/contact/social ayarları, üstüne controller'ın $localBusinessData anahtarları (SchemaBuilder::organization() açıklaması: id, type, name, url, logo, image, telephone, address, locality, region, postalCode, mapLocation, socialLinks, sameAs, brand, siteName, …).

Veri anahtarları: name, description, image (yalnız sayfada görünen görsel: kapak, ürün fotoğrafı), parents (Anasayfa ile sayfa arasındaki gerçek, 200 dönen basamaklar: [['name'=>'Blog','url'=>…]]), türe özgü alanlar (headline, datePublished, dateModified, author = ['name','jobTitle'] (title de olur: içerik servisinin $post['author'] değeri olduğu gibi geçer; Blog-Yazar-Kunyesi), items = [['name','url']], price, currency, sku, brand, serviceType, areaServed). Yeni türlerin alanları (FRW-30; yalnız SAYFADA GÖRÜNEN gerçek veri verilir): newsArticle = blogPost ile aynı (yazar yalnız sayfada görünüyorsa); tvSeries = startDate, numberOfSeasons, numberOfEpisodes, genre (ad listesi), actor ([['name','url']]); movie = dateCreated, genre, director, actor; person = birthDate, deathDate, birthPlace. Gün tarihleri FormatHelper::toIsoDate() tek kaynağından YYYY-MM-DD ya da YYYY yazılır; datePublished/dateModified toIso8601() ile saat dilimli. Google: Movie zengin sonucu yalnız karusel içindedir; TVSeries ve Person için zengin sonuç yoktur ama geçerli Schema.org düğümüdür (docs'ta bu üç tür için madde yoktur). Bu türlerde de puan yazılmaz. Düğüm url değeri HER ZAMAN sayfanın canonical adresidir. Breadcrumb URL yolundan TÜRETİLMEZ.

Politika kapısı (SchemaPolicy) — proje ayarıyla açılamaz

  • aggregateRating, review hiçbir düğümde yazılmaz (kurum kendi puanını işaretleyemez); FAQPage, SearchAction, AggregateOffer yazılmaz.
  • Product yalnız productDetail sayfasında ve yalnız tamsa (name, image, Offer price > 0 + priceCurrency); availability, shippingDetails, hasMerchantReturnPolicy yalnız veri verilirse yazılır. Para birimi, stok, kargo, iade uydurulmaz.
  • TVSeries, Movie, Person adsız yazılmaz; gün tarihi alanı YYYY-MM-DD/YYYY değilse düşer.
  • BreadcrumbList en az 2 öğe; ana sayfada yok.
  • Tarih ISO 8601 + saat dilimi; çözülemeyen tarih düşer; teknik güncelleme tarihi (updated_at) hiçbir çıktıda yok.
  • priceRange < 100 karakter ve yalnız verilirse; geo en az 5 ondalık; LocalBusiness alt tipi address yoksa yazılmaz; telefon E.164 (+902423225060, phone_e164()); Türkiye numarası ya da + ile yazılmış uluslararası numara dışındaki değer düşer.
  • noindex sayfa JSON-LD basmaz (yapısal veri içeren sayfa noindex ile engellenmez).

2. <head>

  • canonical: tek, mutlak, url(path) (sitemap/feed ile aynı yapıcı, sorgusuz); yalnız HTML <head>'de.
  • Open Graph: og:title, og:type, og:image, og:url zorunlu; og:description, og:site_name (<title> ve WebSite.name ile aynı ad), og:locale (DEFAULT_LANGUAGE), og:image:secure_url/type/width/height/alt (dosya biliniyorsa), <html prefix="og: https://ogp.me/ns#">. og:type sayfa türünden: blog yazısı article. Görsel https mutlak adres.
  • X (Twitter) kartı üretilmez (resmi belge okunamadı: docs DOĞRULANMADI). hreflang üretilmez (docs maddesi yok).
  • Robots: HTML sayfada yalnız <meta name="robots">; yönergeler docs tablosundaki adlardan (SeoResolver::robotsDirective() bilinmeyeni düşürür). HTML sayfaya X-Robots-Tag başlığı YAZILMAZ (başlık yalnız HTML dışı dosyalar içindir).
  • Sayfa görseli bildirimi: SeoService::setImage($path, $alt), setType($type); blogPost/newsArticle/productDetail türü bunu kendisi yapar (newsArticle → og:type=article; tvSeries, movie, person website kalır).

3. Bağlantı rel

Core\Support\Definitions\Render\LinkRel::attr($tür, $yeniSekme) TEK tanımdır: ödemeli/reklam → sponsored; yorum/kullanıcı içeriği → ugc; diğer ve istenmeyen (framework imza bağlantısı, paylaşım düğmeleri) → nofollow; editoryal (kendi sosyal profili, kardeş site) → Google niteleyicisi yok. target="_blank" için noopener noreferrer eklenir.

Ölçüt: docs/adsense/riza-cmp.md, docs/analytics/ga4-tuketici-onayi.md, docs/yasal/kvkk-web-sitesi-gereksinimleri.md. Proje yalnız veri verir; kuralları framework uygular, proje KAPATAMAZ.

  • ConsentProvider::headHtml() her sayfada, ölçüm kodundan ÖNCE gtag('consent','default', {4 parametre: denied}) yazar; kayıtlı seçim varsa update gönderir. ad_user_data ve ad_personalization framework kodunda HİÇBİR yerde granted yapılmaz.
  • Reklam kodu (google_adsense_code) ve serbest head_scripts/body_scripts ayarlarının <script> blokları rıza gelene kadar çalışmaz (type="text/plain", ConsentProvider::gate()); rbnDom.js seçimden sonra etkinleştirir. Serbest scriptler "analitik" amacına bağlıdır. AdSense yuvaları (adsense_status + adsense_client_id) da reklam sayılır: banner ve kapı bunlar için de çalışır. Kapı tek yerden karar verir: ConsentProvider::decision() = (analitik VEYA reklam ayarı dolu) VE site noindex değil; noindex sitede bant, kod, rıza kaydı ve ilgili yasal bölümler yoktur (kavramlar/06-riza-kapisi.md).
  • Banner (cookie_notice.rbn.php, LegalProvider): "Hepsini Kabul Et / Hepsini Reddet / Tercihler" eşit boyut ve renk (tek rbn-cookie-btn sınıfı, eşit ızgara sütunu, 48 px; her düğmede ri- Remix Icon, düğme yazısı <span> içinde); düzen her genişlikte aynı: başlık (ikon + yazı) ve metin (çerez politikası bağlantısı dahil) yatayda ORTALI (metin satırı ≤ 72 karakter, ortalanmış blok), düğmeler bant içinde ortalı; metin üstte (gövde ≥ 18 px, başlık ≥ 22 px, satır aralığı 1.6), üç eşit düğme altta (yazı 16 px, ≥ 48 px; ≤ 640 px alt alta tam genişlik), yan yana düzen YOKTUR; bant ≤ 1100 px ortalı, yüksekliği ekranın ≤ %40'ı (Tercihler açıkken ≤ %85, ≤ 640 px'te ≤ %75; taşan içerik bant içinde kaydırılır), alt kenar safe-area ile tam görünür; Tercihler panelinde etiketler gövdeyle aynı punto, seçim kutusu satırı ≥ 48 px, "Seçimi Kaydet" üst satırdaki düğmelerle aynı boyut; odak halkası :focus-visible; renkler yalnız --rbn-cookie-* token'ı (proje bileşeni yeniden yazmaz; token listesi UPGRADING.md); amaç ve taraf (Google LLC) yazılı; çerez politikası bağlantısı; "kullanarak kabul etmiş sayılırsınız" YOK. Banner yalnız rıza kapısı açıksa basılır (ConsentProvider::decision()['open']); routemap'te elle kapatma anahtarı yoktur. Gizli banner sayfanın altını kapatmaz (visibility:hidden, pointer-events:none).
  • Tercih çerezi rbn_consent ({id, v, ts, analytics, ads}, 180 gün). v = ConsentProvider::BANNER_VERSION: metin ya da amaçlar değişince sürüm artırılır, eski sürümün kaydı yeniden sorulur. id 32 haneli rastgele ziyaretçi kimliğidir (kişisel veriden türetilmez; kararları birbirine bağlar). Yeniden açma/geri alma: alt bilgideki "Çerez Tercihleri" bağlantısı (footer.rbn.php, banner varsa görünür) ya da [data-rbn-cookie-settings] tıklaması / window.rbnConsent.open(); izin geri alınınca sayfa yenilenir. no_bottom_bar ile alt çubuğu kapatan sayfa, data-rbn-cookie-settings öznitelikli kendi bağlantısını koymak zorundadır.

4.2 Sertifikalı CMP zorlaması (merkezî)

Kişiselleştirilmiş reklam yalnız Google sertifikalı, TCF entegre bir CMP'den rıza alınan trafikte açılır (riza-cmp.md kural 1-3). Framework bunu tek yerden zorlar (ConsentProvider):

Durum Davranış
CMP tanımlı değil ya da tanımı geçersiz HER reklam isteği kişiselleştirilmemiş: <head> içinde adsbygoogle.requestNonPersonalizedAds = 1; ad_user_data/ad_personalization denied; framework bandında "Kişiselleştirilmiş reklam kapalıdır".
CMP tanımlı (cmp_provider + cmp_script_url) CMP betiği, Consent Mode default'tan ve ölçüm/reklam kodundan ÖNCE yüklenir (senkron <script src data-rbn-cmp>); reklam kodu TCF API sinyalini (__tcfapi('addEventListener', 2): tcloaded/useractioncomplete, gdprApplies=false ya da amaç 1 onaylı) bekler; kişiselleştirme CMP'nin onay dizgisine bırakılır (framework bayrağı koymaz). Reklam izni CMP'de kalır: framework bandında reklam onay kutusu yoktur, sunucu kaydına ads=0, cmp_active=1 yazılır.
  • Proje verisi (ayar grubu integrations, SettingsConfig, panelden setupadsense ile tohumlanır): cmp_provider (ad, ≤60 karakter, etiketsiz) ve cmp_script_url (https, noktalı host, kimlik bilgisi/boşluk/tırnak yok, ≤512 karakter). Eksik ya da geçersiz değer "CMP yok" demektir (kişiselleştirilmemiş mod). Hesap/sağlayıcı seçimi projenin işidir.
  • Kapatılamaz: kişiselleştirilmemiş modu açan/kapatan hiçbir ayar yoktur; ConsentProvider yalnız iki cmp_* anahtarını okur.
  • Framework kendi bandını "sertifikalı CMP" diye ADLANDIRMAZ (riza-cmp.md kural 5: Google CMP'yi tam denetlemez, sorumluluk yayıncıdadır); bant yalnız KVKK ve GA4 rıza kurallarını karşılar.
  • Gerçek bir sertifikalı CMP hesabı framework dışıdır: sertifika ve TCF dizgisinin doğruluğu yayıncı sorumluluğundadır (riza-cmp.md kural 5). Framework tarafı (yükleme sırası, sinyal bekleme, kişiselleştirilmemiş varsayılan, doğrulama) tamdır.

4.3 Sunucu tarafı rıza kaydı (yasal/kvkk… kural 2)

  • Her karar (kabul, ret, tercih, geri alma) POST /fw-consent ile sunucuya yazılır (ConsentController → ConsentService::record() → ConsentLogRepository::record() → ConsentLogModel, tablo z_log_consents). Kayıt eklemelidir (append-only): geri alma yeni satırdır, eski satır değişmez.
  • Sütunlar: project_key, consent_id (çerezdeki rastgele kimlik), decision (accept/reject/custom), analytics, ads, withdrawn (önceki satırda verilmiş bir amaç şimdi reddedildi; sunucu önceki satırdan HESAPLAR, istemciye güvenilmez), cmp_active (sunucu ayardan hesaplar), banner_version, page_type (şema sayfa türü; bilinmeyen other), updated_at, created_at (karar anı, sunucu saati). IP, kullanıcı aracısı, hesap kimliği YAZILMAZ (veri minimizasyonu).
  • Koruma: oturumsuz anonim uç, CSRF yok; Origin denetimi, hız sınırı (consent_record: saat başına 30) ve sıkı girdi doğrulaması. Geçersiz karar 422, yanlış banner_version reddedilir.
  • Kurulum: php rbn consent:install --project=<anahtar> [--apply] (plan; --apply tabloyu kurar). Tablo yoksa karar kaydedilemez; çalışma zamanında "tablo var mı" dalı yoktur. Sütun sırası kuralı: son iki sütun updated_at, created_at.
  • Saklama süresi: docs'ta rıza kaydı saklama süresi yazmıyor (DOĞRULANMADI); kayıtlar framework tarafından silinmez.

5. Doğrulanmayan konular (kural uydurulmadı)

X kartları, platform özel OG boyutları, article:* OG alanları, logo SVG desteği, Service zengin sonucu, analitik çerezlerin rıza gerektirip gerektirmediği (KVKK rehberi 5.9), rıza kaydının saklama süresi, CMP'nin TCF dizgisinin doğruluğu (Google CMP'yi denetlemez), requestNonPersonalizedAds bayrağının docs'ta yazılı olmayan AdSense uygulama ayrıntısı olması. Ayrıntı: E:\localhost\docs\…\DOĞRULANMADI bölümleri.

6. Kapı testi

E:\AgentSpace\.agentspaceraclar w-regresyonirim w_frw9_schema_head_politika.php (pilot sitelerde sayfa türü başına JSON-LD + head + rel; politika/builder/consent birimleri; proje kodunun çıktı üretmediği taraması) ve fw_frw10_cmp_riza_kaydi.php (CMP zorlaması G-cmp-01..08, sunucu rıza kaydı G-kayit-01..09; --ihlal bayrağı kapıları bilerek ihlal eden örneklerle koşar, hepsi KALDI vermelidir).