# Yapısal veri (JSON-LD), `` 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(, )` → `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](../Services/Blog-Yazar-Kunyesi.md)), `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. `` - `canonical`: tek, mutlak, `url(path)` (sitemap/feed ile aynı yapıcı, sorgusuz); yalnız HTML ``'de. - Open Graph: `og:title`, `og:type`, `og:image`, `og:url` zorunlu; `og:description`, `og:site_name` (`` 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. ## 4. İzin (consent), analitik, reklam Ö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. ### 4.1 Consent Mode ve kapılı kod - `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).