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,reviewhiçbir düğümde yazılmaz (kurum kendi puanını işaretleyemez);FAQPage,SearchAction,AggregateOfferyazılmaz.ProductyalnızproductDetailsayfasında ve yalnız tamsa (name,image, Offerprice> 0 +priceCurrency);availability,shippingDetails,hasMerchantReturnPolicyyalnız veri verilirse yazılır. Para birimi, stok, kargo, iade uydurulmaz.TVSeries,Movie,Personadsız yazılmaz; gün tarihi alanıYYYY-MM-DD/YYYYdeğ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;geoen az 5 ondalık; LocalBusiness alt tipiaddressyoksa yazılmaz; telefon E.164 (+902423225060,phone_e164()); Türkiye numarası ya da+ile yazılmış uluslararası numara dışındaki değer düşer.noindexsayfa 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:urlzorunlu;og:description,og:site_name(<title>veWebSite.nameile aynı ad),og:locale(DEFAULT_LANGUAGE),og:image:secure_url/type/width/height/alt(dosya biliniyorsa),<html prefix="og: https://ogp.me/ns#">.og:typesayfa 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 sayfayaX-Robots-Tagbaşlığı YAZILMAZ (başlık yalnız HTML dışı dosyalar içindir). - Sayfa görseli bildirimi:
SeoService::setImage($path, $alt),setType($type);blogPost/newsArticle/productDetailtürü bunu kendisi yapar (newsArticle→og:type=article;tvSeries,movie,personwebsitekalı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 ÖNCEgtag('consent','default', {4 parametre: denied})yazar; kayıtlı seçim varsaupdategönderir.ad_user_datavead_personalizationframework kodunda HİÇBİR yerdegrantedyapılmaz.- Reklam kodu (
google_adsense_code) ve serbesthead_scripts/body_scriptsayarlarının<script>blokları rıza gelene kadar çalışmaz (type="text/plain",ConsentProvider::gate());rbnDom.jsseç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 (tekrbn-cookie-btnsınıfı, eşit ızgara sütunu, 48 px; her düğmederi-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 kenarsafe-areaile 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 listesiUPGRADING.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.id32 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_barile 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, paneldensetupadsenseile tohumlanır):cmp_provider(ad, ≤60 karakter, etiketsiz) vecmp_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;
ConsentProvideryalnız ikicmp_*anahtarını okur. - Framework kendi bandını "sertifikalı CMP" diye ADLANDIRMAZ (
riza-cmp.mdkural 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.mdkural 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-consentile sunucuya yazılır (ConsentController→ConsentService::record()→ConsentLogRepository::record()→ConsentLogModel, tabloz_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ü; bilinmeyenother),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 karar422, yanlışbanner_versionreddedilir. - Kurulum:
php rbn consent:install --project=<anahtar> [--apply](plan;--applytabloyu kurar). Tablo yoksa karar kaydedilemez; çalışma zamanında "tablo var mı" dalı yoktur. Sütun sırası kuralı: son iki sütunupdated_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\.agentspaceraclarw-regresyonirimw_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).