# Content Blocks: Yeni Nesil İçerik Elemanları

TYPO3 v13 ile gelen Content Blocks API, özel içerik elemanları oluşturmanın modern ve standart yoludur. Bu dokümanda kurulum, yapılandırma, alan türleri, Fluid şablonları ve proje özelinde kullanım detaylı anlatılmaktadır.

---

## İçindekiler

1. [Content Blocks Nedir?](#content-blocks-nedir)
2. [Kurulum](#kurulum)
3. [Dizin Yapısı](#dizin-yapısı)
4. [config.yaml Detayları](#configyaml-detayları)
5. [Özel Grup: Block Element](#özel-grup-block-element)
6. [Alan Türleri (Detaylı)](#alan-türleri-detaylı)
7. [Fluid Şablonları](#fluid-şablonları)
8. [Backend Önizleme](#backend-önizleme)
9. [Collection (Tekrarlayan Alanlar)](#collection-tekrarlayan-alanlar)
10. [Temel Örnek: Kart Elemanı](#temel-örnek-kart-elemanı)
11. [Projede Mevcut Content Block'lar](#projede-mevcut-content-blocklar)
12. [Yeni Block Eklerken Kontrol Listesi](#yeni-block-eklerken-kontrol-listesi)
13. [Sorun Giderme](#sorun-giderme)
14. [Content Blocks vs Diğer Yaklaşımlar](#content-blocks-vs-diğer-yaklaşımlar)
15. [Kaynaklar](#kaynaklar)

---

## Content Blocks Nedir?

Content Blocks, TYPO3 v13'te tanıtılan **YAML tabanlı** içerik elemanı API'sidir. Tek bir `config.yaml` ile TCA, veritabanı alanları, Fluid şablonları ve dil dosyaları için gerekli yapı otomatik türetilir.

**Avantajları:**

| Özellik                | Açıklama                                                      |
| ---------------------- | ------------------------------------------------------------- |
| **YAML yapılandırma**  | PHP TCA yazmadan alan tanımı                                  |
| **Otomatik TCA & SQL** | Extension yüklendiğinde tablolar ve form alanları oluşturulur |
| **Backend preview**    | İçerik düzenlerken önizleme şablonu                           |
| **Tip güvenliği**      | Alan türleri net tanımlı                                      |
| **Standart dizin**     | Tüm bloklar aynı klasör yapısında                             |
| **Fluid template**     | Frontend ve backend için ayrı HTML şablonları                 |

**Sınırlamalar:**

- Çok karmaşık TCA (ör. özel renderType'lar) için ek TCA override gerekebilir.
- İlişkiler (Relation) için hedef tablo yapılandırması gerekir.

---

## Kurulum

```bash
ddev composer require friendsoftypo3/content-blocks
ddev exec vendor/bin/typo3 extension:setup
ddev exec vendor/bin/typo3 cache:flush
```

- `extension:setup`: Content Blocks, site paketindeki `ContentBlocks/ContentElements/*` klasörlerini tarar ve TCA/SQL üretir.
- Yeni bir content block ekledikten veya `config.yaml` değiştirdikten sonra **cache flush** yapmak gerekir.

---

## Dizin Yapısı

Her content block **tek bir klasörde** toplanır; klasör adı backend’de görünmez, sadece `config.yaml` içindeki `name` ve `title` kullanılır.

```
packages/my_site_package/ContentBlocks/
└── ContentElements/
    └── my-element/                    # Klasör adı: herhangi bir benzersiz isim
        ├── assets/
        │   └── icon.svg               # Backend "Yeni içerik öğesi" ikonu
        ├── config.yaml                # Alan tanımları ve blok meta bilgisi
        ├── language/
        │   └── labels.xlf             # Backend alan etiketleri (XLF)
        └── templates/
            ├── frontend.html          # Sayfada render edilen şablon
            └── backend-preview.html   # Backend önizleme alanı
```

**Önemli:** `config.yaml` içindeki `name` değeri **extension key + / + benzersiz isim** olmalıdır (örn. `testproject/product-slider`). Bu değer `tt_content.CType` için kullanılır.

---

## config.yaml Detayları

### Üst Seviye Anahtarlar

| Anahtar        | Zorunlu | Açıklama                                                                                                 |
| -------------- | ------- | -------------------------------------------------------------------------------------------------------- |
| `name`         | Evet    | Blok tanımlayıcısı. Format: `vendorOrExtKey/blockName` (örn. `testproject/banner`).                      |
| `title`        | Evet    | Backend’de "Yeni içerik öğesi" listesinde görünen başlık.                                                |
| `description`  | Hayır   | Aynı listede başlığın altında kısa açıklama.                                                             |
| `group`        | Evet    | İçerik öğesi grubu. Sihirbazda hangi sekmede listeleneceğini belirler (örn. `common`, `blockelement`).   |
| `prefixFields` | Hayır   | `true` ise alanlar TCA’da extension’a özel prefix ile saklanır; çakışma azalır. Genelde `true` önerilir. |
| `icon`         | Hayır   | Özel ikon adı; yoksa `assets/icon.svg` kullanılır.                                                       |
| `fields`       | Evet    | Alan listesi (aşağıda detay).                                                                            |

### name Kuralları

- Sadece küçük harf, rakam ve tire (`-`) kullanın.
- Her blok için proje genelinde **benzersiz** olmalı.
- Örnek: `testproject/hero-parallax`, `testproject/product-slider`.

### group Değerleri

- **Varsayılan TYPO3 grupları:** `common`, `menu`, `special`, `forms`, `lists`, `plugins` vb.
- **Özel grup:** Kendi anahtarınızı (örn. `blockelement`) kullanıp TCA override ile etiket atayabilirsiniz (bkz. [Özel Grup: Block Element](#özel-grup-block-element)).

### Örnek config.yaml (minimal)

```yaml
name: testproject/banner
title: Banner
description: Parallax arka planlı banner — başlık ve buton
group: blockelement
prefixFields: true
fields:
  - identifier: banner_headline
    type: Text
  - identifier: banner_link
    type: Link
```

---

## Özel Grup: Block Element

Projede tüm özel bloklar **"Block Element"** grubu altında toplanır. Bunun için iki adım gerekir.

### 1. config.yaml içinde group

Her content block’ta:

```yaml
group: blockelement
```

### 2. TCA override ile grup etiketi

Grup adının backend’de "Block Element" olarak görünmesi için site paketinde TCA override tanımlanır:

**Dosya:** `Configuration/TCA/Overrides/tt_content.php`

```php
<?php

declare(strict_types=1);

defined('TYPO3') or die();

$GLOBALS['TCA']['tt_content']['columns']['CType']['config']['itemGroups']['blockelement'] = 'Block Element';
```

Bu sayede "Yeni içerik öğesi" sihirbazında **Block Element** sekmesi açılır ve bu gruba ait bloklar orada listelenir.

---

## Alan Türleri (Detaylı)

### Metin Alanları

| type       | Açıklama  | Sık Kullanılan Özellikler                          |
| ---------- | --------- | -------------------------------------------------- |
| `Text`     | Tek satır | `max: 255`, `required: true`, `placeholder: '...'` |
| `Textarea` | Çok satır | `rows: 5`, `enableRichtext: true`, `placeholder`   |

- **enableRichtext: true:** RTE (zengin metin) kullanılır; Fluid’de `f:format.html` ile çıktı alınır.
- **enableRichtext: false (varsayılan):** Düz metin; satır sonları için Fluid’de `f:format.nl2br` ve gerekirse `f:format.raw` kullanılır.

### Sayı ve Tarih

| type       | Açıklama   | Örnek Özellikler             |
| ---------- | ---------- | ---------------------------- |
| `Number`   | Sayı       | `range.lower`, `range.upper` |
| `DateTime` | Tarih/saat | `dbType: date` / `datetime`  |

### Dosya ve Medya

| type   | Açıklama    | Özellikler                                                  |
| ------ | ----------- | ----------------------------------------------------------- |
| `File` | Dosya/Resim | `maxitems: 1`, `allowed: common-image-types` veya `image/*` |

**allowed değerleri:**

- `common-image-types`: TYPO3’ün yaygın resim türleri (jpg, png, gif, webp vb.)
- `image/*`: Tüm resim MIME türleri
- Belirli uzantılar da verilebilir.

Fluid’de erişim: `{data.field_name.0}` (ilk dosya). Örnek: `f:image image="{data.banner_image.0}"`.

### Bağlantı

| type   | Açıklama                                              |
| ------ | ----------------------------------------------------- |
| `Link` | Sayfa, URL, e-posta, dosya linki (TYPO3 link wizard). |

Fluid’de: `f:link.typolink parameter="{data.banner_link}"` (parameter, link alanının ham değeridir).

### Seçim Alanları

| type       | Açıklama        | Özellikler                                                  |
| ---------- | --------------- | ----------------------------------------------------------- |
| `Select`   | Açılır liste    | `items: [{ label: '...', value: '...' }]`, `default: '...'` |
| `Radio`    | Radyo butonları | `items`                                                     |
| `Checkbox` | Onay kutusu     | `items`                                                     |

**Select için:** Çoğu durumda `renderType: selectSingle` kullanılır. Örnek:

```yaml
- identifier: section_layout
  type: Select
  renderType: selectSingle
  default: image_left
  items:
    - label: "Görsel solda, metin sağda"
      value: image_left
    - label: "Görsel sağda, metin solda"
      value: image_right
```

### Diğer Türler

| type         | Kullanım                                                                        |
| ------------ | ------------------------------------------------------------------------------- |
| `Email`      | E-posta alanı (doğrulama)                                                       |
| `Color`      | Renk seçici (HEX)                                                               |
| `Category`   | TYPO3 kategori ilişkisi                                                         |
| `Relation`   | Başka tabloya ilişki (`foreign_table` gerekir)                                  |
| `Collection` | Tekrarlayan alt alan grubu (bkz. [Collection](#collection-tekrarlayan-alanlar)) |

---

## Fluid Şablonları

### Namespace ve data

- Her iki şablonda da (frontend ve backend-preview) **Fluid** kullanılır.
- Alanlara **`{data.alan_adi}`** ile erişilir (prefixFields kullanılsa bile şablonda `data` ile verilir).

Örnek:

```html
<html
  xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
  data-namespace-typo3-fluid="true"
>
  <h2>{data.slider_title}</h2>
  <p>{data.slider_subtitle}</p>
</html>
```

### Koşullar

- Alan dolu mu: `f:if condition="{data.banner_headline}"` … `f:if`
- Karşılaştırma: `f:if condition="{data.section_layout} == 'image_right'"` (then/else ile farklı HTML çıktısı)

### Sık Kullanılan ViewHelper'lar

| Amaç                    | Örnek                                                                                     |
| ----------------------- | ----------------------------------------------------------------------------------------- |
| Resim göstermek         | `f:image image="{data.banner_image.0}" alt="..." class="w-full" loading="lazy"`           |
| Resim URI (örn. bg)     | `f:uri.image(image: data.banner_image.0)`                                                 |
| Link                    | `f:link.typolink parameter="{data.banner_link}" class="btn-1">VIEW MORE</f:link.typolink` |
| HTML (RTE)              | `f:format.html>{data.section_text}</f:format.html`                                        |
| Satır sonu → &lt;br&gt; | `f:format.raw><f:format.nl2br>{data.banner_headline}</f:format.nl2br></f:format.raw`      |
| Kırpma (önizleme)       | `f:format.crop maxCharacters="100">{data.card_description}</f:format.crop`                |

### Collection (liste) kullanımı

Collection alanları şablonda **dizi** olarak gelir; `f:for` ile dönülür. Alt alanlar tekil nesne üzerinden okunur.

```html
<f:for each="{data.slider_products}" as="product">
  <div>
    <f:if condition="{product.product_image.0}">
      <f:image
        image="{product.product_image.0}"
        alt="{product.product_title}"
      />
    </f:if>
    <span>{product.product_price}</span>
    <strong>{product.product_title}</strong>
    <f:if condition="{product.product_link}">
      <f:link.typolink parameter="{product.product_link}"
        >ADD TO CART</f:link.typolink
      >
    </f:if>
  </div>
</f:for>
```

### Layout / sıra değişimi

"Görsel solda/sağda" gibi seçenekler için iki yaygın yöntem:

1. **HTML sırasını değiştirmek:** `section_layout == 'image_right'` ise önce metin bloğu, sonra görsel bloğu basılır; grid’de doğal olarak metin solda, görsel sağda olur.
2. **CSS order:** Aynı HTML sırasında kalıp `md:!order-1` / `md:!order-2` ile sıra değiştirilebilir (Fluid koşulunun doğru çalıştığından emin olun).

---

## Backend Önizleme

`backend-preview.html` backend’de içerik öğesi kaydının altında küçük bir önizleme kutusunda render edilir. Amaç, editörün hangi bloğu düzenlediğini ve kısa özetini görmesidir.

- Aynı `{data.*}` yapısı kullanılır.
- Stil için inline `style` kullanılabilir; sayfa CSS’i yüklü olmayabilir.
- Uzun metinlerde `f:format.crop maxCharacters="50"` kullanmak okunabilirliği artırır.

Örnek:

```html
<div style="padding: 10px; border: 1px solid #ccc;">
  <strong>Banner</strong>
  <f:if condition="{data.banner_headline}">
    <br />Başlık: {data.banner_headline -> f:format.crop(maxCharacters: 40)}
  </f:if>
</div>
```

---

## Collection (Tekrarlayan Alanlar)

Collection, **tekrarlayan bir alt alan grubu** tanımlar (örn. ürün listesi, özellik listesi).

```yaml
- identifier: slider_products
  type: Collection
  maxitems: 20
  fields:
    - identifier: product_image
      type: File
      maxitems: 1
      allowed: common-image-types
    - identifier: product_title
      type: Text
    - identifier: product_price
      type: Text
    - identifier: product_link
      type: Link
```

- **maxitems:** Maksimum kayıt sayısı.
- **fields:** Her öğede hangi alanların olacağı.

Fluid’de `{data.slider_products}` bir dizi; her eleman `product_image`, `product_title` vb. anahtarlara sahip. Erişim: `{product.product_title}`, `{product.product_image.0}`.

---

## Temel Örnek: Kart Elemanı

### config.yaml

```yaml
name: my-site/card
title: Kart Elemanı
description: Başlık, metin, resim ve bağlantıdan oluşan kart
group: blockelement
prefixFields: true
fields:
  - identifier: card_title
    type: Text
    required: true
    max: 255
  - identifier: card_description
    type: Textarea
    enableRichtext: true
    rows: 5
  - identifier: card_image
    type: File
    maxitems: 1
    allowed:
      - image/*
  - identifier: card_link
    type: Link
  - identifier: card_style
    type: Select
    renderType: selectSingle
    default: "default"
    items:
      - label: Varsayılan
        value: default
      - label: Vurgulu
        value: highlighted
      - label: Gölgeli
        value: shadow
```

### frontend.html

```html
<html
  xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
  xmlns:cb="http://typo3.org/ns/TYPO3/CMS/ContentBlocks/ViewHelpers"
  data-namespace-typo3-fluid="true"
>
  <div class="cb-card cb-card--{data.card_style}">
    <f:if condition="{data.card_image}">
      <div class="cb-card__image">
        <f:for each="{data.card_image}" as="image">
          <f:image
            image="{image}"
            maxWidth="600"
            class="w-full h-48 object-cover rounded-t-lg"
            loading="lazy"
          />
        </f:for>
      </div>
    </f:if>

    <div class="cb-card__body p-6">
      <h3 class="text-xl font-bold mb-2">{data.card_title}</h3>

      <f:if condition="{data.card_description}">
        <div class="text-gray-600 mb-4">
          <f:format.html>{data.card_description}</f:format.html>
        </div>
      </f:if>

      <f:if condition="{data.card_link}">
        <cb:link
          field="card_link"
          class="text-primary font-medium hover:underline"
        >
          Detay →
        </cb:link>
      </f:if>
    </div>
  </div>
</html>
```

### backend-preview.html

```html
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      data-namespace-typo3-fluid="true">

<div style="padding:10px;">
    <strong>Kart:</strong> {data.card_title}
    <f:if condition="{data.card_description}">
        <br><small><f:format.crop maxCharacters="100">{data.card_description}</f:format.crop></small>
    </f:if>
</div>

</html>
```

### labels.xlf

```xml
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
    <file source-language="en" datatype="plaintext" original="labels">
        <header/>
        <body>
            <trans-unit id="title">
                <source>Card Element</source>
            </trans-unit>
            <trans-unit id="description">
                <source>A card with title, text, image and link</source>
            </trans-unit>
            <trans-unit id="card_title.label">
                <source>Card Title</source>
            </trans-unit>
            <trans-unit id="card_description.label">
                <source>Description</source>
            </trans-unit>
            <trans-unit id="card_image.label">
                <source>Image</source>
            </trans-unit>
            <trans-unit id="card_link.label">
                <source>Link</source>
            </trans-unit>
            <trans-unit id="card_style.label">
                <source>Card Style</source>
            </trans-unit>
        </body>
    </file>
</xliff>
```

---

## Yeni Block Eklerken Kontrol Listesi

1. **Klasör:** `ContentBlocks/ContentElements/my-block-name/` oluşturuldu mu?
2. **config.yaml:** `name`, `title`, `group: blockelement`, `fields` tanımlı mı?
3. **templates/frontend.html:** `data-namespace-typo3-fluid="true"` ve `{data.*}` kullanımı doğru mu?
4. **templates/backend-preview.html:** Kısa önizleme var mı?
5. **language/labels.xlf:** Tüm alanlar için `trans-unit id="field_id.label"` var mı?
6. **assets/icon.svg:** Backend ikonu mevcut mu?
7. **Cache:** `typo3 cache:flush` veya Install Tool üzerinden flush yapıldı mı?

---

## Sorun Giderme

| Belirti                         | Olası neden             | Çözüm                                                                                                                                     |
| ------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Blok sihirbazda görünmüyor      | Extension setup / cache | `typo3 extension:setup` ve `typo3 cache:flush`                                                                                            |
| Alan değişikliği yansımıyor     | TCA/SQL cache           | Cache flush; gerekirse DB’de yeni kolonları kontrol et                                                                                    |
| Layout (solda/sağda) değişmiyor | Fluid koşulu yanlış     | Şablonda `{data.section_layout} == 'image_right'` ile then/else’i kontrol et; gerekirse HTML sırasını değiştir (önce metin, sonra görsel) |
| Resim/link çıkmıyor             | Boş alan                | Şablonda `f:if condition="{data.field.0}"` veya `f:if condition="{data.link}"` kullan                                                     |
| Backend’de "Block Element" yok  | TCA override eksik      | `Configuration/TCA/Overrides/tt_content.php` içinde `itemGroups['blockelement'] = 'Block Element'` tanımlı mı kontrol et                  |

---

## Content Blocks vs Diğer Yaklaşımlar

| Özellik        | Content Blocks | Mask    | Manuel TCA |
| -------------- | -------------- | ------- | ---------- |
| Yapılandırma   | YAML           | JSON/UI | PHP        |
| Otomatik TCA   | Evet           | Evet    | Hayır      |
| Otomatik SQL   | Evet           | Evet    | Hayır      |
| v13 standardı  | Evet           | Hayır   | Hayır      |
| Öğrenme eğrisi | Düşük          | Düşük   | Yüksek     |
| Esneklik       | Orta–Yüksek    | Orta    | Çok yüksek |

---

## Kaynaklar

- [Content Blocks Resmi Dokümantasyon](https://docs.typo3.org/p/friendsoftypo3/content-blocks/main/en-us/)
- [Content Blocks GitHub](https://github.com/FriendsOfTYPO3/content-blocks)
- [TCA Override ile Content Block Genişletme](https://docs.typo3.org/p/friendsoftypo3/content-blocks/main/en-us/Guides/ExtendContentBlockTCA/Index.html)
