# Sayfa Ekranları

https://dev.wisecp.com/tr/sayfa-yuzeyleri

Her genel ve müşteri sayfası temanızın içindeki tek bir view dosyasına çözülür; burada adresten dosyaya tam eşleme var.

## Genel Bakış

Bir controller asla şablon dosyası adı vermez. Bir *view* adı verir, yani uzantısız ve bölü işaretiyle ayrılmış bir yol; motor onu temanızın içindeki dosyaya çevirir: `render("account/services")` bir Smarty temasında `views/account/services.tpl`, Twig temasında `views/account/services.twig` olur. Uzantıyı tema manifestiyle seçer, controller hangi motorun çalıştığını hiç öğrenmez.

Temayı taşınabilir yapan tek kural budur. Tam bir temanın ihtiyaç duyduğu 69 view dosyasını verirseniz her sayfa cevap verir. Eksik verirseniz controller o adresler için 404 döner, çünkü site controller'larının neredeyse hepsi view'ı önceden denetler.

## Yapı

### Views Ağacı

View'lar alana göre gruplanır, tek klasöre düz yığılmaz. Aşağıdaki sayımlar ürünle gelen temaların dosya dosya ölçümüdür: **Basic** ve **WStyle** 69'ar view taşır.

| Klasör | Basic / WStyle | Orada ne yaşar |
| --- | --- | --- |
| `views/` (kök) | 6 | Doğal bir ailesi olmayan bağımsız sayfalar: ana sayfa, alan adı arama, SMS tanıtım sayfası, lisans doğrulama, 404, bakım |
| `views/products/` | 3 | Katalog: kategori sayfası, yazılım detay sayfası, yazılım mağazası |
| `views/checkout/` | 14 | Yapılandırma, sepet, ödeme adımı ve bunların paylaştığı parçalar |
| `views/account/` | 25 | Giriş yapmış müşteri paneli, panodan alt hesaplara kadar |
| `views/auth/` | 6 | Giriş, kayıt, parola sıfırlama, hesap etkinleştirme, davet |
| `views/content/` | 14 | Editoryal sayfalar: blog, duyuru, referans, bilgi bankası, CMS sayfaları, iletişim |
| `views/page/` | 1 | Dosya tabanlı sayfalar: buradaki bir view, controller ve rota olmadan bir adres yayımlar |

### Layout, Partial ve Component

View'lar yalnız sayfa gövdesini tutar. Etraflarındaki kabuk üç kardeş klasörde yaşar ve tema kökünden referans verilir, view'a göre değil.

- **layouts/**: Bir view'ın genişlettiği tam belgeler. WStyle dört tane taşır: `default` (genel ve müşteri kabuğu), `auth` (sahne ve form ikilisi), `checkout` (odaklı huni kabuğu), `invoice` (yazdırmaya uygun).
- **partials/**: Layout'un dahil ettiği kabuk parçaları, üç küme hâlinde. Genel: `header`, `topbar`, `drawer`, `footer`, `announcements`, `cookie-notice`. Müşteri paneli: `client-topbar`, `client-sidebar`, `client-footer`. Huni: `checkout-header`, `checkout-footer`.
- **components/**: Bir view'ın argümanla dahil ettiği yeniden kullanılabilir bloklar: plan ızgaraları, pano panelleri, ödeme yöntemi listeleri, yapılandırma alt kartları.
- **tables/**: Müşteri liste sayfaları (hizmetler, alan adları, faturalar, talepler) için düz PHP tablo presetleri; şablon değil, liste motoru okur.

## Referans

### Çözümleme API'si

Bir sayfanın var olup olmadığına ve nasıl gösterileceğine üç metot karar verir. Üçü de `Theme::active()`'in döndürdüğü tema örneğindedir.

```php
public static function active(): self;

// views/<view>.<uzantı> gerçek bir dosyaysa true. $view bölü ile ayrılır, uzantı yazılmaz.
// '' ve içinde '..' geçen her yol, diske hiç bakılmadan false döner.
public function viewExists(string $view): bool;

// Tek bir view'ı string'e render eder, istek bağlamından bağımsız (cron, mail, PDF).
public function render(string $view, array $data = []): string;

// Tek bir tema ayarını okur: config.php değeri ya da theme.php'deki şema varsayılanı.
public function setting(string $key): mixed;

// Manifestin meta bloğu, çekirdeğin okuduğu tema bayrakları dahil.
public function meta(): array;

// Temanın assets/ klasörü altında mutlak URL. {asset} şablon fonksiyonunun arkasındaki metot.
public function assetUrl(string $path = ''): string;
```

- **Theme::viewExists()**: Uzantı manifest motorundan gelir: `twig` için `.twig`, `php` için `.php`, diğer her durumda `.tpl`. Yalnız website controller'ları 41 kez çağırır ve bunların her biri bir sayfayı korur.
- **Theme::render()**: Bağlamdan bağımsız gösterim. Cron, mail ve PDF kodundan bunu kullanın; `View::chose("website")`, `CRON` ya da `ADMINISTRATOR` tanımlıyken temayı sessizce atlar.
- **View::render()**: Her controller'ın kullandığı istek içi yol. Adres değişkenlerini ekler, `filter:template.variables` kancasını koşturur, view'ın içerik scope'unu yükler ve son olarak biten işaretlemeyi filtreler.
- **TemplateEngine::render_file()**: İkisinin de altındaki motor dağıtıcısı. Her çağrıda taze bir Smarty ya da Twig örneği kurar ve view yoluna `views/` önekini ekler.

### Kök View'lar

| View | Gösteren | Adres |
| --- | --- | --- |
| `home` | `controllers/website/index.php` | `home` rotası, sitenin kökü |
| `domain` | `controllers/website/domain.php` | `domain` rotası, `/domain` |
| `sms-introduction` | `controllers/website/sms.php` | `international-sms` rotası, `/international-sms` |
| `license-verification` | `controllers/website/license.php` | `license` rotası, `/license-verify` |
| `404` | `Controllers::page_404` | rota yok: eşleşmeyen her adres ve düşen her view kapısı |
| `maintenance` | `controllers/system/maintenance.php` | rota yok: bakım modu açıkken her adres |

### Katalog View'ları

| View | Gösteren | Adres |
| --- | --- | --- |
| `products/category` | `controllers/website/products.php` | `products` ve `products-2` rotaları, `/{kategori}` ve `/{tür}/{kategori}` |
| `products/software` | `controllers/website/softwares.php` | `softwares` ve `softwares_cat` rotaları, `/softwares` ve `/softwares/{kategori}` |
| `products/detail` | `controllers/website/product-detail.php` | `software_detail` rotası, `/software/{slug}` |

### Ödeme Akışı View'ları

On dördün dördü bir parçadır: layout taşımaz, başka bir şablon tarafından içeri alınır ya da AJAX yanıtı olarak döner.

| View | Gösteren | Adres |
| --- | --- | --- |
| `checkout/configure` | `controllers/website/configure.php` | `configure`, `configure-p` rotaları, `/configure/{tür}/{id}` |
| `checkout/configure-addon` | `configure.php`, `page_addon()` | aynı yapılandırma adresinin eklenti dalı |
| `checkout/configure-domain` | `configure.php`, `page_edit_item()` | `configure-edit` rotası, `/configure/edit/{anahtar}` |
| `checkout/cart` | `controllers/website/cart.php` | `cart` ve `basket` rotaları, `/cart` |
| `checkout/checkout` | `controllers/website/checkout.php` | `checkout` rotası, `/checkout` |
| `checkout/order-complete` | `checkout.php`, `page_complete()` | `order-complete` rotası, `/checkout/complete/{id}` |
| `checkout/invoice-complete` | `invoices.php`, `page_complete()` | `invoice-complete` rotası, `/invoices/complete/{id}` |
| `checkout/pay` | `controllers/website/pay.php` | `pay` rotası, `/pay/{id}` |
| `checkout/pay-result` | `controllers/website/payment.php` | `pay-successful` ve `pay-failed` rotaları |
| `checkout/pay-choices` | `ClientCheckout`, `ClientInvoicePay`, `BalanceOps` operation'ları | parça: AJAX yanıtı, üç çağıran paylaşır |
| `checkout/section-account` | `checkout/checkout.tpl` | parça: ödeme gövdesi dahil eder |
| `checkout/section-billing` | `checkout/checkout.tpl` | parça: ödeme gövdesi dahil eder |
| `checkout/section-payment` | `checkout/checkout.tpl` | parça: ödeme gövdesi dahil eder |
| `checkout/section-rail-items` | `checkout/checkout.tpl` | parça: sipariş özeti rayı dahil eder |

### Müşteri Paneli View'ları

| View | Gösteren | Adres |
| --- | --- | --- |
| `account/dashboard-hero` | `controllers/website/dashboard.php` | `my-account` rotası, `/dashboard`, hero yerleşim varyantı |
| `account/dashboard-standard` | `dashboard.php` | aynı adres, standart yerleşim varyantı |
| `account/settings` | `controllers/website/account.php` | `info` rotası, `/account/info` |
| `account/services` | `services.php`, `page_list()` | `services` rotası, `/services` |
| `account/service-detail` | `services.php`, `page_detail()` | `service-detail` rotası, `/services/detail/{id}` |
| `account/service-transfer-approve` | `services.php`, `page_transfer_approve()` | `service-transfer-approve` rotası, yolda jeton taşır |
| `account/domains` | `domains.php`, `page_list()` | `domains` rotası, `/domains` |
| `account/domain-detail` | `domains.php`, `page_detail()` | `domain-detail` rotası, `/domain-detail/{id}` |
| `account/invoices` | `invoices.php`, `page_list()` | `invoices` rotası, `/invoices` |
| `account/invoice-detail` | `invoices.php`, `page_detail()` | `invoice-detail` rotası, `/invoices/detail/{id}` |
| `account/subscriptions` | `invoices.php`, `page_subscriptions()` | `invoice-subscriptions` rotası, `/invoices/subscriptions` |
| `account/bulk-pay` | `invoices.php`, `page_bulk_pay()` | `bulk-pay` rotası, `/invoices/bulk-pay` |
| `account/balance` | `controllers/website/balance.php` | `balance` rotası, `/balance` |
| `account/tickets` | `tickets.php`, `page_list()` | `tickets` rotası, `/tickets` |
| `account/ticket-detail` | `tickets.php`, `page_detail()` ve `page_guest_view()` | `ticket-detail` ve `ticket-guest` rotaları |
| `account/ticket-create` | `tickets.php`, `page_create()` | `ticket-create` rotası, `/tickets/create` |
| `account/ticket-msg` | `tickets.php` | parça: tek bir yanıt, AJAX yoklayıcısına döner |
| `account/sms` | `sms.php`, `page_panel()` | `sms` rotası, `/sms` |
| `account/sub-accounts` | `controllers/website/sub-accounts.php` | `sub-accounts` rotası, `/sub-accounts` |
| `account/api-credentials` | `controllers/website/api.php` | `api` rotası, `/api-credentials` |
| `account/affiliate` | `controllers/website/affiliate.php` | `affiliate` rotası, `/affiliate` |
| `account/reseller-program` | `reseller.php`, `page_program()` | `reseller` rotası, misafir ve bayi olmayan görünümü |
| `account/reseller` | `reseller.php`, `page_dashboard()` | `reseller` rotası, bayinin kendi paneli |
| `account/license-transfer-verify` | `verify-license-transfer.php` | `verify-license-transfer` rotası, yolda jeton taşır |
| `account/access-denied` | `Controllers`, izin kapısı | rota yok: alt hesabın açamayacağı her müşteri sayfası |

### Kimlik View'ları

Altısı da sign controller'ındaki tek bir özel yardımcıdan geçer; kapıyı, canonical bağlantısını ve sayfa başlığı konvansiyonunu bu yüzden paylaşırlar.

| View | Gösteren | Adres |
| --- | --- | --- |
| `auth/login` | `sign.php`, `page_in()` | `sign-in` rotası, `/login` |
| `auth/register` | `sign.php`, `page_up()` | `sign-up` rotası, `/register` |
| `auth/forget-password` | `sign.php`, `page_forget()` | `sign-forget` rotası, `/forget-password` |
| `auth/reset-password` | `sign.php`, `page_reset()` | `sign-reset` rotası, `/reset-password` |
| `auth/activate` | `sign.php`, `page_activate()` | `account-activation` rotası, `/account-activation` |
| `auth/accept-invite` | `sign.php`, `page_invite()` | `accept-invite` rotası, `/invitation/{jeton}` |

### İçerik View'ları

| View | Gösteren | Adres |
| --- | --- | --- |
| `content/blog` | `controllers/website/articles.php` | `articles` rotası, `/articles` |
| `content/blog-detail` | `page-detail.php` | `articles_detail` rotası, çıplak slug |
| `content/blog-comment-list` | `ClientBlogComments` operation'ı | parça: yorum listesi, AJAX ile yeniden yüklenir |
| `content/news` | `controllers/website/news.php` | `news` rotası, `/news` |
| `content/news-detail` | `page-detail.php` | `news_detail` rotası, çıplak slug |
| `content/references` | `controllers/website/references.php` | `references` rotası, `/references` |
| `content/references-detail` | `page-detail.php` | `references_detail` rotası, çıplak slug |
| `content/page-detail` | `page-detail.php` | `normal_detail` ve `contract_detail` rotaları, her CMS sayfası ve sözleşme |
| `content/knowledgebase` | `knowledgebase.php` | `kbase` rotası, `/knowledgebase` |
| `content/knowledgebase-category` | `knowledgebase.php` | `kbase_category` rotası, `/knowledgebase/category/{slug}` |
| `content/knowledgebase-article` | `knowledgebase.php` ve `knowledgebase_detail.php` | `kbase_detail` rotası, `/knowledgebase/article/{slug}` |
| `content/contact` | `controllers/website/contact.php` | `contact` rotası, `/contact` |
| `content/newsletter-unsubscribe` | `controllers/website/newsletter.php` | rota anahtarı yok: ilk URL parçası controller'a çözülür |
| `content/addon` | `controllers/website/addon.php` | rota anahtarı yok: `/addon/{ModulAdi}`, eklenti modülünün kendi sayfası |

### Dosya Tabanlı Sayfalar

`views/page/` altındaki bir view kendi başına bir adres yayımlar. Controller yok, rota kaydı yok, çekirdek düzenlemesi yok. Kayıtlı her rota eşleşmekte başarısız olduktan sonra en sonda bir yönlendirme filtresi çalışır ve dosya oradaysa isteği jenerik bir controller'a verir.

- **views/page/about.tpl**: `/about` adresinde yayımlanır. Slug `a-z 0-9 / _ -` ile sınırlıdır ve her `..` reddedilir; iki kez denetlenir: bir kez yönlendirme filtresinde, bir kez controller'da.
- **filter:routing.match**: Router'ın son yedeği. Kayıtlı bir rotayı asla gölgelemez, yani dosya tabanlı sayfa eklemek mevcut bir adresi hiçbir zaman bozamaz.
- **locale scope**: `page/` öneki locale'e yansımaz: `views/page/about.tpl` dosyası `locale/{dil}/about.php`'yi okur, çünkü scope klasörü değil genel URL'yi yansıtır.

### View Eksik Olduğunda Ne Olur

Kimin sorduğuna göre üç ayrı şey. Hangisine baktığınızı bilmek, hiç fırlatılmamış bir şablon hatasını aramakla geçecek bir saati kurtarır.

| Çağıran | Davranış | Gördüğünüz |
| --- | --- | --- |
| `viewExists()` ile koruyan controller | Onun yerine 404 sayfasını döner | Temanızın `404` view'ıyla HTTP 404, hiç hata yok |
| Kapısız bir view | Motor fırlatır, dağıtıcı yakalar ve boş string döner | Boş sayfa. Loga bir uyarı yazılır, geliştirme modunda mesaj biçimli bir blok içinde görünür |
| İsteğe bağlı bir özellik view'ı | Özellik kendini kapatır | Hata yok, farklı bir kod yolu çalışır (aşağıdaki PDF tuzağına bakın) |

## Örnek

Baştan sona tek bir sayfa: view'ı adlandıran controller satırı ve ona cevap veren view dosyası. İkisi birlikte gösteriliyor çünkü aralarındaki tek sözleşme view yoludur.

```php
// controllers/website/services.php, page_list()

// Önce kapı: hizmet listesi taşımayan bir tema boş sayfa yerine 404 döner.
if (!Theme::active()->viewExists("account/services")) return $this->page_404("website");

$this->addData("page_title", Language::gc("website/services/meta/title"));
$this->addData("canonical_link", LinkGenerator::client("services"));
$this->addData("meta_robots", "noindex, nofollow");

// Müşteri kabuğunu (kenar çubuğu, alt menü, bildirim zili) açar ve aktif sekmeyi işaretler.
$this->addData("show_client_subnav", true);
$this->addData("subnav_active", "services");

// Logo, şirket adı, sözleşme bağlantıları, para birimi listesi: müşteri kabuğu verisi.
$this->set_predefined_data("client");

// Uzantı yok, motor yok: "account/services" AKTİF temaya karşı çözülür.
return $this->view->chose("website")->render("account/services", $this->data, true);
```

```smarty
{extends file='layouts/default.tpl'}

{* Çekirdek JS'in KENDİSİNİN tükettiği stil ve kütüphaneler head'e girer. *}
{block name=head}
    <script src="{asset path='js/list.js'}" defer></script>
{/block}

{* Sayfa script'leri scripts'e, çekirdek paketten SONRA girer; yoksa window.WStyle hazır değildir. *}
{block name=scripts}
    <script src="{asset path='js/services.js'}" defer></script>
{/block}

{block name=content}
<section class="pt-4 pb-4">
    <div class="container list-page" data-services-page>
        {csrf form='services'}

        <nav aria-label="{lang key='website/services/breadcrumb-aria'}">
            <ol class="breadcrumb mb-1">
                <li class="breadcrumb-item"><a href="{link route='my-account'}">{lang key='website/index/subnav-dashboard'}</a></li>
                <li class="breadcrumb-item active" aria-current="page">{lang key='website/services/title'}</li>
            </ol>
        </nav>
    </div>
</section>
{/block}
```

## Tuzaklar

> **Eksik view bir istisna değil, boş sayfadır**
> 
> Motor dağıtıcısı her throwable'ı yakalar ve üretimde boş string döner. Bir sayfa hiç görünmüyorsa verinize bakmadan önce view dosyasını arayın. Geliştirme modunda yakalanan mesaj sayfada görünür; bunu doğrulamanın en hızlı yolu odur.

> **Arka plan çıktısı istek yolundan geçmemeli**
> 
> `View::chose("website")` temayı yalnız `CRON` ve `ADMINISTRATOR` tanımsızken çözer. Zamanlanmış bir görevden çağrıldığında sessizce düz PHP şablonlarına düşer, şablon dosyanızı bulamaz ve boş string döner. Cron, mail eki ve PDF üretiminde `Theme::active()->render()` kullanın.

> **Pano tek view'dır ya da iki, kararı tema verir**
> 
> Controller önce `account/dashboard`'u sorar. O dosya varsa temanın tek panosu vardır ve yerleşim ayarı tümüyle yok sayılır. Yalnız yokken controller tema ayarına göre `account/dashboard-hero` ya da `account/dashboard-standard`'a düşer. Basic ve WStyle ikiliyi taşır; bir tema bunun yerine tek view'ı da taşıyabilir.

> **İsteğe bağlı bir view koca bir motoru açabilir**
> 
> Fatura PDF üretimi başsız Chrome'u yalnız bir Chrome ikilisi çözülüyor *ve* tema `account/invoice-pdf` taşıyorsa seçer. Ürünle gelen üç temanın hiçbiri onu taşımıyor, yani bugün gerçekte gömülü üretici koşuyor. O tek dosyayı eklemek her faturanın çıktı motorunu değiştirir.

> **Bakım view'ı bir sayfa gövdesi değil, tam belgedir**
> 
> Layout genişletmeyen tek view odur: site kapalıdır, dolayısıyla hiçbir gezinme, sepet ya da hesap kabuğu oraya geri bağlanamaz. Kendi doctype'ı, kendi head'i ve kendi ilk boyama guard'ıyla açılır. Bakım view'ı taşımayan tema, giydirilmemiş olan miras sistem şablonuna düşer.

## İlgili Makaleler

- [Tema Anatomisi](https://dev.wisecp.com/tr/tema-anatomisi)
- [Şablon Değişkenleri](https://dev.wisecp.com/tr/sablon-degiskenleri)
- [Katalog ve Ürün Sayfaları](https://dev.wisecp.com/tr/katalog-ve-urun-sayfalari)
- [Sepet ve Ödeme](https://dev.wisecp.com/tr/sepet-ve-odeme)
- [Müşteri Paneli](https://dev.wisecp.com/tr/musteri-paneli)
- [Giriş ve Kayıt](https://dev.wisecp.com/tr/giris-ve-kayit)
- [Sistem Sayfaları](https://dev.wisecp.com/tr/sistem-sayfalari)
