# Rol Tabanlı Yetkilendirme Paneli

Bu kod örneği, PHP ve PDO ile yazılmış, hiçbir framework veya Composer paketi kullanmayan bir yönetim panelidir. Rol tabanlı yetkilendirme (RBAC) uygular: hangi rolün hangi işlemi yapabileceği tek bir tabloda tutulur ve yetki kontrolü sunucu tarafında yapılır, arayüzdeki menü gizleme yalnızca görsel bir kolaylıktır. Panel; giriş ve oturum yönetimi, kullanıcı ve rol yönetimi, sunucu taraflı DataTables listeleri, CSRF korumalı Ajax uçları, modal içinde CRUD, açık/koyu tema ve mobil uyumlu bir arayüz içerir. Kodun tamamı satır satır Türkçe açıklamalıdır; amaç kopyalamak değil, kararların gerekçesini göstermektir.

- **Adres:** https://cilginyazilim.com/kutuphane/rol-tabanli-yetkilendirme-paneli
- **Kategori:** Güvenlik
- **Seviye:** İleri
- **Teknoloji:** PHP 8, PDO, MySQL, Ajax, DataTables, Bootstrap 5, OOP
- **Etiketler:** Ajax, Bootstrap, DataTables, Güvenlik, MySQL, PDO, PHP, RBAC
- **Gereksinim:** PHP 8.0+ (pdo_mysql, mbstring, gd) · MySQL 5.7+ veya MariaDB 10.3+ · Apache/mod_rewrite
- **Kod deposu:** https://github.com/CilginYazilim/rbac-login-system
- **Lisans:** MIT

## Öne Çıkan Özellikler

- Rol tabanlı yetkilendirme: hangi rolün neyi yapabildiği tek tabloda, kontrol sunucuda
- Sıfır bağımlılık — Composer, framework ve CDN yok; SQL dosyasını içe aktarın, çalışır
- Katmanlı OOP mimari: yönlendirme, denetleyici, model ve görünüm ayrı
- Sunucu taraflı DataTables ile kullanıcı listesi, arama ve filtreler
- CSRF korumalı Ajax uçları ve modal içinde tam CRUD
- Oturum sertleştirme, kaba kuvvet koruması ve parola hashleme
- Açık/koyu tema ve mobil uyumlu arayüz; kod satır satır Türkçe açıklamalı

## Kullanım Senaryoları

- Küçük ve orta ölçekli projelerde hazır bir yönetim paneli temeli
- Rol ve yetki kavramının gerçek bir uygulama üzerinde öğrenilmesi
- Var olan bir panele rol tabanlı yetkilendirme eklerken referans alınması
- Müşteri paneli, bayi paneli veya iç kullanım araçlarının iskeleti
- Framework kullanmadan katmanlı mimari örneği arayan ekipler

## Kurulum

1. Proje dosyalarını web köküne kopyalayın (örn. XAMPP'ta htdocs veya kendi web sunucunuzun kök dizini)
2. cy_login.sql dosyasını içe aktarın — tabloları, rolleri ve demo hesapları oluşturur
3. config klasöründeki veritabanı bilgilerini kendi sunucunuza göre düzenleyin
4. upload klasörüne yazma izni verin (profil görselleri buraya kaydedilir)
5. Demo hesabıyla giriş yapın ve rollerin hangi menüleri açtığını karşılaştırın
6. Canlıya alırken hata gösterimini kapatın ve demo hesapların parolalarını değiştirin

## Ayrıntılı Anlatım

<img src="assets/images/logo.png" alt="Çılgın Yazılım" width="90">

## CY Admin — PHP PDO · MySQL · AJAX Yönetim Paneli

**Sıfır bağımlılıklı**, OOP mimarili, rol tabanlı yetkilendirmeye sahip
açık kaynak yönetim paneli örneği.

Rol tabanlı yetkilendirme · Sunucu taraflı DataTables · CSRF korumalı AJAX
Modal CRUD · Açık/koyu tema · Mobil uyumlu

[English](README.en.md) · [cilginyazilim.com](https://cilginyazilim.com) · MIT Lisansı

---

### Ekran Görüntüleri

**Tanıtım sayfası** — herkese açık, sistemin ne yaptığını anlatır

<img src="docs/screenshots/01-tanitim.png" alt="Tanıtım sayfası" width="820">

**Kontrol paneli** — özet kartlar, kayıt grafiği, role özel bölümler

<img src="docs/screenshots/03-kontrol-paneli.png" alt="Kontrol paneli" width="820">

**Kullanıcı yönetimi** — sunucu taraflı tablo, arama ve filtreler

<img src="docs/screenshots/04-kullanicilar.png" alt="Kullanıcı yönetimi" width="820">

**Roller ve yetkiler** — hangi rol neyi yapabilir, tek tabloda

<img src="docs/screenshots/05-roller.png" alt="Roller ve yetkiler" width="820">

<td width="50%"><b>Giriş ekranı</b><br><img src="docs/screenshots/02-giris.png" alt="Giriş ekranı"></td>
<td width="50%"><b>Koyu tema</b><br><img src="docs/screenshots/08-koyu-tema.png" alt="Koyu tema"></td>

<td><b>Hesabım</b><br><img src="docs/screenshots/06-hesabim.png" alt="Hesabım"></td>
<td><b>Sistem</b><br><img src="docs/screenshots/07-sistem.png" alt="Sistem sayfası"></td>

**Mobil görünüm**

<img src="docs/screenshots/09-mobil.png" alt="Mobil görünüm" width="300">

---

### İçindekiler

- [Bu proje nedir?](#bu-proje-nedir)
- [Öne çıkan özellikler](#öne-çıkan-özellikler)
- [Kurulum](#kurulum)
- [Demo hesaplar](#demo-hesaplar)
- [Klasör yapısı](#klasör-yapısı)
- [Mimari](#mimari)
- [Roller ve yetkiler](#roller-ve-yetkiler)
- [Güvenlik önlemleri](#güvenlik-önlemleri)
- [Yeni sayfa nasıl eklenir?](#yeni-sayfa-nasıl-eklenir)
- [Canlıya alma kontrol listesi](#canlıya-alma-kontrol-listesi)
- [Sık karşılaşılan sorunlar](#sık-karşılaşılan-sorunlar)
- [Katkı ve lisans](#katkı-ve-lisans)

---

### Bu proje nedir?

PHP öğrenirken en çok takılınan yer, "çalışan kod" ile "doğru kod" arasındaki
farktır. Bu depo, klasik bir CRUD örneğini alıp gerçek bir projede olması
gereken hâline getiriyor: katmanlı mimari, rol tabanlı yetkilendirme,
oturum yönetimi ve baştan sona güvenlik.

**Composer yok, framework yok, CDN yok.** İndirin, `cy_login.sql` dosyasını içe
aktarın, çalışır. Tüm kod satır satır Türkçe açıklamalıdır; amaç kopyalamak
değil, *neden öyle yazıldığını* anlamaktır.

> Bu depo daha önce tek dosyalık bir CRUD örneğiydi.
> Yeni sürümde tam bir yönetim paneline dönüştürüldü.
> Eski kurulumdan geçiş için `cy_login.sql` dosyasının sonundaki
> **"6) ESKİ SÜRÜMDEN YÜKSELTME"** bölümüne bakın.

---

### Öne çıkan özellikler

#### Mimari
- **OOP katmanlı yapı** — Core / Models / Repositories / Controllers / Views
- **PSR-4 mantığında otomatik yükleyici** (Composer'sız)
- **Tek giriş noktası** (front controller) + rota tablosu
- **Repository deseni** — SQL tek dosyada, denetleyiciler temiz
- **Ara katmanlar (middleware)** — yetki kontrolü rota tanımında

#### Kimlik ve oturum
- **Beni hatırla** — iki parçalı anahtar (selector + validator), sır veritabanında
  özetlenmiş saklanır, anahtar **tek kullanımlıktır** ve her otomatik girişte yenilenir
- Parola değişince **tüm cihazlardaki** kalıcı oturumlar iptal edilir
- Kaba kuvvet koruması, oturum sertleştirme, taze yetki kontrolü

#### Yönetim paneli
- **Herkese açık tanıtım sayfası** — kök adres, sistemin ne yaptığını anlatır
- **Roller ve Yetkiler sayfası** — yetki matrisi; her kullanıcı kendi sınırlarını görür
- **Role özel kontrol paneli** — yönetici/editör istatistik görür, üye kendi hesap özetini
- **Temiz adresler** — `/login`, `/users` *(mod_rewrite kapalıysa tek ayarla eski biçime döner)*
- **Açık/koyu tema hesaba bağlıdır** — başka cihazdan girseniz de tercihiniz gelir
- Daraltılabilir **sol menü** (masaüstünde ikon modu, mobilde çekmece)
- **Kontrol paneli**: özet kartlar, 14 günlük kayıt grafiği, işlem günlüğü
- **Kullanıcı yönetimi**: sunucu taraflı DataTables, canlı arama, rol/durum filtresi
- Modal tabanlı **ekle / düzenle / detay / sil** akışı
- **Hesabım** sayfası: profil, görsel, parola değiştirme
- **Sistem** sayfası: güvenlik denetimi, veri durumu ve yürürlükteki kurallar

#### Mobil
- Off-canvas menü, karartma katmanı, ESC ile kapatma
- 44×44 px dokunma hedefleri
- Modallar alttan açılan panel biçiminde
- Tabloda ikincil sütunlar gizlenir, bilgi ad hücresine taşınır *(veri kaybı yok)*
- iOS'ta form alanına dokununca otomatik yakınlaştırma engellenir
- Çentikli ekranlar için `safe-area` desteği

---

### Kurulum

#### Gereksinimler

| | En az | Önerilen |
|---|---|---|
| PHP | 8.1 | 8.2+ |
| MySQL / MariaDB | 5.7 / 10.3 | 8.0+ |
| PHP eklentileri | `pdo_mysql`, `mbstring` | + `gd` (görsel küçültme) |

#### 1. Dosyaları yerleştirin

```bash
# XAMPP kullanıyorsanız
cd C:/xampp/htdocs
git clone https://github.com/CilginYazilim/rbac-login-system.git
```

#### 2. Veritabanını oluşturun

```bash
mysql -u root -p < cy_login.sql
```

veya **phpMyAdmin › İçe Aktar › `cy_login.sql` › Başlat**

> Dosya `cy_login` veritabanını kendisi oluşturur; elle açmanıza gerek yoktur.
> Tablolar: `users`, `remember_tokens`, `login_attempts`, `activity_log`.

#### 3. Ayarları yapın

```bash
cp .env.example .env      # Windows: copy .env.example .env
```

`.env` dosyasını açıp veritabanı bilgilerinizi yazın:

```ini
APP_DEBUG=true
APP_PRETTY_URLS=true
DB_HOST=127.0.0.1
DB_NAME=cy_login
DB_USER=root
DB_PASS=
```

> `.env` yoksa uygulama XAMPP varsayılanlarıyla (root / boş parola) çalışır.
>
> `APP_PRETTY_URLS=true` temiz adresleri (`/login`) açar; kökteki `.htaccess` ve
> Apache'de `mod_rewrite` gerekir. Sunucunuzda yeniden yazma kapalıysa `false`
> yapın — **hiçbir kod değişmez**, adresler `index.php?r=login` biçimine döner.

#### 4. Klasör izinleri

`upload/` klasörü yazılabilir olmalıdır.

```bash
chmod 755 upload    # Linux / macOS
```

#### 5. Tarayıcıda açın

```
http://localhost/rbac-login-system/
```

---

### Demo hesaplar

| Rol | E-posta | Parola |
|---|---|---|
| **Yönetici** | `admin@cilginyazilim.com` | `Admin1234` |
| **Editör** | `editor@cilginyazilim.com` | `Editor1234` |
| **Üye** | `uye@cilginyazilim.com` | `Uye12345` |
| Örnek 50 kullanıcı | `ad.soyad@ornek.com` | `Demo1234` |

Giriş ekranındaki demo kartlarına tıklayarak formu tek hamlede doldurabilirsiniz.

> ⚠️ **Canlı sunucuda bu hesapları silin veya parolalarını değiştirin.**
> Giriş ekranındaki demo kartı bloğunu da kaldırın:
> `views/auth/login.php` içindeki `$demoAccounts` dizisi ve `cy-demo` bloğu.

---

### Klasör yapısı

```
├── index.php                  ← TEK giriş noktası (front controller)
├── .env.example               ← Ortam değişkeni şablonu
├── cy_login.sql               ← Veritabanı kurulum dosyası
│
├── config/
│   └── config.php             ← Tüm ayarlar tek dizide
│
├── routes/
│   └── web.php                ← Rota tablosu + yetki kuralları
│
├── app/
│   ├── Core/                  ← Çatı sınıfları
│   │   ├── Autoloader.php       Sınıf otomatik yükleyici
│   │   ├── Env.php              .env okuyucu
│   │   ├── Config.php           Nokta notasyonlu ayar erişimi
│   │   ├── Database.php         PDO bağlantısı (tembel tekil)
│   │   ├── Session.php          Sertleştirilmiş oturum
│   │   ├── Csrf.php             Sahte istek koruması
│   │   ├── Auth.php             Kimlik doğrulama
│   │   ├── Middleware.php       Rota öncesi kontroller
│   │   ├── Router.php           İstek yönlendirme
│   │   ├── Request.php          Girdi okuma ve temizleme
│   │   ├── Response.php         JSON / yönlendirme / güvenlik başlıkları
│   │   ├── Validator.php        Form doğrulama
│   │   ├── Uploader.php         Güvenli görsel yükleme
│   │   ├── RateLimiter.php      Kaba kuvvet koruması
│   │   ├── Flash.php            Tek seferlik bildirimler
│   │   └── View.php             Şablon motoru
│   │
│   ├── Models/
│   │   ├── User.php           ← Kullanıcı varlığı (entity)
│   │   └── Role.php           ← Roller ve yetki tablosu
│   │
│   ├── Repositories/
│   │   ├── UserRepository.php            ← users tablosunun TEK kapısı
│   │   ├── RememberTokenRepository.php   ← "Beni hatırla" anahtarları
│   │   └── ActivityRepository.php        ← İşlem günlüğü
│   │
│   ├── Http/
│   │   ├── Controller.php
│   │   └── Controllers/
│   │       ├── HomeController.php       ← Herkese açık tanıtım
│   │       ├── AuthController.php
│   │       ├── DashboardController.php
│   │       ├── UserController.php
│   │       ├── RoleController.php       ← Roller ve yetkiler
│   │       ├── ProfileController.php
│   │       ├── SettingsController.php
│   │       └── Api/
│   │           ├── UserApiController.php        ← AJAX CRUD
│   │           └── PreferenceApiController.php  ← Tema tercihi
│   │
│   └── Support/helpers.php    ← e(), url(), can(), icon() …
│
├── views/
│   ├── layouts/               ← admin · auth · public · plain
│   ├── partials/              ← sidebar · topbar
│   ├── home/ auth/ dashboard/ users/ roles/ profile/ settings/ errors/
│
├── assets/
│   ├── css/  cilginyazilim.css (tasarım kalıbı) · admin.css (panel)
│   └── js/   app.js (kabuk) · users.js (CRUD) · login.js
│
├── docs/screenshots/          ← README'deki ekran görüntüleri
└── upload/                    ← Yüklenen görseller (PHP çalıştırma kapalı)
```

---

### Mimari

#### İsteğin yolculuğu

```
Tarayıcı
   │
   ▼
index.php ────────── Autoloader · .env · Config · Session · Güvenlik başlıkları
   │
   ▼
routes/web.php ───── Rota eşleştirme
   │
   ▼
Middleware ───────── auth · csrf · can:users.delete   (başarısızsa DURUR)
   │
   ▼
Controller ───────── Girdiyi al · doğrula · yetkiyi kontrol et
   │
   ▼
Repository ───────── Prepared statement ile veritabanı
   │
   ▼
View / JSON ──────── Ekrana bas veya AJAX yanıtı döndür
```

#### Neden bu ayrım?

| Katman | Sorumluluk | İçinde ne **olmaz**? |
|---|---|---|
| **Controller** | Akışı yönetir | SQL, HTML |
| **Repository** | Veritabanı erişimi | Yetki kararı, HTML |
| **Model** | Veri + davranış | SQL |
| **View** | Ekrana basma | İş mantığı, sorgu |

Tüm SQL'in tek dosyada olması, güvenlik denetimini de tek dosyaya indirir.

#### Adres yapısı

Varsayılan **temiz adrestir**; kökteki `.htaccess` bununla birlikte gelir:

```
/login
/users
/api/users/list
```

Sunucunuzda `mod_rewrite` yoksa `.env` içinde `APP_PRETTY_URLS=false` yapın:

```
index.php?r=users
index.php?r=api/users/list
```

Bağlantılar `url()` yardımcısıyla ve **mutlak** üretilir (`Router::basePath()`),
bu yüzden proje bir alt klasörde (`htdocs/rbac-login-system`) de, alan adının kökünde
de aynı şekilde çalışır.

> `.htaccess` içinde bilerek `RewriteBase` **yoktur**. Alt klasör kurulumunda
> `RewriteBase /` yazmak, isteği yanlışlıkla `htdocs` kökündeki `index.php`'ye
> gönderir (XAMPP'ta karşılama sayfasına düşersiniz).

---

### Roller ve yetkiler

Yetkiler `app/Models/Role.php` içinde tek bir tabloda tanımlıdır.
Kodda **rol adı değil yetki adı** kullanılır:

```php
Auth::can('users.delete')           // ✔ doğru
Auth::user()->role === 'admin'      // ✘ kırılgan
```

| Yetki | Yönetici | Editör | Üye |
|---|:--:|:--:|:--:|
| Kontrol panelini görme | ✔ | ✔ | ✔ |
| Özet istatistikler | ✔ | ✔ | — |
| Kullanıcı listesi | ✔ | ✔ | — |
| Kullanıcı ekleme | ✔ | ✔ | — |
| Kullanıcı düzenleme | ✔ | ✔ | — |
| Kullanıcı silme | ✔ | — | — |
| Rol atama | ✔ | — | — |
| Hesap aktif/pasif | ✔ | — | — |
| Kendi profili | ✔ | ✔ | ✔ |
| Sistem sayfası | ✔ | — | — |

#### Ek iş kuralları

Rol tablosunun üstünde, kazaları ve yetki yükseltmeyi önleyen kurallar vardır:

- Yönetici olmayan biri **yönetici hesabını düzenleyemez/silemez**
- Kimse **kendi rolünü** değiştiremez, **kendini pasifleştiremez**, **kendini silemez**
- Sistemdeki **son aktif yönetici** silinemez ve pasifleştirilemez
- Yetkisi olmayan biri form üzerinden `role` gönderirse **değer sessizce yok sayılır**

Yeni rol eklemek için `Role::ABILITIES` dizisine bir satır eklemek ve
`cy_login.sql` içindeki `ENUM` değerini genişletmek yeterlidir.

---

### Güvenlik önlemleri

| Tehdit | Önlem | Dosya |
|---|---|---|
| **SQL Injection** | Tüm sorgular prepared statement; sıralama sütunu beyaz listeden | `UserRepository.php` |
| **XSS** | Her çıktı `e()` ile kaçışlanır; JS'te `.text()` kullanılır | `helpers.php` |
| **XSS (2. hat)** | Content-Security-Policy; satır içi `<script>` yok | `Response.php` |
| **CSRF** | Oturum bazlı anahtar, `hash_equals` ile sabit süreli karşılaştırma | `Csrf.php` |
| **Kaba kuvvet** | 5 hatalı denemede 15 dakika kilit; sayaç veritabanında | `RateLimiter.php` |
| **Kullanıcı sayımı** | Tek ve aynı hata mesajı + **geçerli** bir sahte bcrypt özetiyle oyalama (ölçüldü: her iki yol da ~115 ms) | `Auth.php` |
| **Kalıcı oturum çalınması** | "Beni hatırla" anahtarı iki parçalıdır; sır veritabanında SHA-256 özeti olarak durur, anahtar tek kullanımlıktır ve her girişte döner | `RememberTokenRepository.php` |
| **Parola değişiminde artık oturum** | Parola değişince kullanıcının tüm cihazlarındaki kalıcı anahtarlar iptal edilir | `ProfileController.php` |
| **Session fixation** | Girişte ve periyodik olarak oturum kimliği yenilenir | `Session.php` |
| **Session hijacking** | `httponly` + `samesite` + `secure` çerez, tarayıcı parmak izi | `Session.php` |
| **Yetki yükseltme** | Rol/durum alanları yetki olmadan okunmaz; güncellemede beyaz liste | `UserApiController.php` |
| **IDOR** | Profil güncellemede ID formdan değil **oturumdan** alınır | `ProfileController.php` |
| **Mass assignment** | `UserRepository::update()` yalnızca izinli sütunları yazar | `UserRepository.php` |
| **Kötü amaçlı dosya** | İçerik doğrulaması (`getimagesize`), uzantıyı sunucu belirler, görsel yeniden üretilir | `Uploader.php` |
| **Path traversal** | `basename()` + `realpath()` ile klasör dışına çıkış engeli | `Uploader.php` |
| **Parola saklama** | `password_hash()` / bcrypt, girişte otomatik `needs_rehash` | `User.php` |
| **Bilgi sızıntısı** | Canlıda hata detayı gizlenir, `X-Powered-By` kaldırılır | `index.php` |
| **Clickjacking** | `X-Frame-Options: DENY`, `frame-ancestors 'none'` | `Response.php` |
| **Kod dosyasına erişim** | `app/`, `config/`, `views/`, `routes/` için `.htaccess` engeli | `.htaccess` |

**Sistem** sayfası (yalnızca yönetici) bu önlemlerin kurulumunuzda aktif olup
olmadığını canlı olarak denetler.

---

### Yeni sayfa nasıl eklenir?

Üç adım:

**1 — Denetleyiciyi yazın** · `app/Http/Controllers/ReportController.php`

```php
namespace App\Http\Controllers;

use App\Core\Request;
use App\Http\Controller;

final class ReportController extends Controller
{
    public function index(Request $request): void
    {
        $this->view('reports/index', [
            'title' => 'Raporlar',
            'total' => $this->users()->countAll(),
        ]);
    }
}
```

**2 — Rotayı tanımlayın** · `routes/web.php`

```php
$router->get('reports', ReportController::class, 'index', ['auth', 'can:reports.view']);
```

**3 — Görünümü oluşturun** · `views/reports/index.php`

```php

    <div class="cy-card__body">Toplam kullanıcı: <?= (int) $total ?></div>

```

Menüde görünmesi için `views/partials/sidebar.php` içindeki `$menu` dizisine
bir satır, yetkinin tanınması için `app/Models/Role.php` içine
`'reports.view'` eklemeniz yeterlidir.

---

### Canlıya alma kontrol listesi

- [ ] `.env` içinde **`APP_DEBUG=false`**
- [ ] Demo hesapları silin; kendi yönetici hesabınızı oluşturun
- [ ] `views/auth/login.php` içindeki demo kartı bloğunu kaldırın
- [ ] Güçlü bir veritabanı kullanıcısı tanımlayın (root kullanmayın)
- [ ] HTTPS sertifikası kurun *(oturum çerezi otomatik `secure` olur)*
- [ ] `.env`, `app/`, `config/`, `views/`, `routes/` tarayıcıdan erişilemez olsun
- [ ] `upload/` klasöründe PHP çalıştırma kapalı olsun
- [ ] PHP hata günlüğünü (`error_log`) bir dosyaya yönlendirin
- [ ] Veritabanı yedeklemesini planlayın
- [ ] **Sistem** sayfasındaki denetim listesini kontrol edin — hepsi yeşil olmalı

#### Nginx kullanıyorsanız

`.htaccess` dosyaları yok sayılır. Sunucu bloğunuza ekleyin:

```nginx
location ~ ^/(app|config|views|routes)/ { deny all; return 404; }
location ~ /\.               { deny all; return 404; }
location ~ \.(sql|md|log|ini|bak)$ { deny all; return 404; }
location ^~ /upload/         { location ~ \.php$ { deny all; } }
```

---

### Sık karşılaşılan sorunlar

<details>
<summary><b>"Veritabanına bağlanılamadı" hatası</b></summary>

`.env` dosyanızdaki `DB_USER` / `DB_PASS` değerlerini kontrol edin.
XAMPP varsayılanı `root` ve boş paroladır. MySQL servisinin çalıştığından
emin olun.
</details>

<details>
<summary><b>Giriş yapıyorum ama sürekli giriş ekranına dönüyorum</b></summary>

Oturum klasörü yazılabilir değildir. `php.ini` içindeki `session.save_path`
değerini kontrol edin. Ayrıca tarayıcı çerezlerinin açık olduğundan emin olun.
</details>

<details>
<summary><b>"Çok fazla hatalı deneme" — kendimi kilitledim</b></summary>

```sql
DELETE FROM login_attempts;
```
Süre dolduğunda (varsayılan 15 dakika) kilit kendiliğinden kalkar.
</details>

<details>
<summary><b>Görsel yükleniyor ama görünmüyor</b></summary>

`upload/` klasörünün yazma izni olmalıdır (755). Ayrıca `php.ini` içindeki
`upload_max_filesize` ve `post_max_size` değerleri en az 2 MB olmalıdır.
</details>

<details>
<summary><b>Türkçe karakterler bozuk görünüyor</b></summary>

Veritabanı ve tablolar `utf8mb4_unicode_ci` olmalıdır. `cy_login.sql` bunu
zaten ayarlar; eski bir tabloyu dönüştürmek için:

```sql
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
</details>

<details>
<summary><b>Sayfa stilsiz görünüyor / JS çalışmıyor</b></summary>

Tarayıcı konsolunu açın. Content-Security-Policy hatası görüyorsanız
kendi eklediğiniz satır içi bir `<script>` veya dış CDN bağlantısı olabilir.
Tüm JavaScript ayrı dosyada olmalıdır. Geçici olarak `config/config.php`
içinden `security.csp_enabled` değerini `false` yapabilirsiniz.
</details>

<details>
<summary><b>Görseller küçültülmüyor</b></summary>

PHP'nin `gd` eklentisi kurulu değildir. Zorunlu değildir; eklenti yoksa
görsel olduğu gibi kaydedilir, diğer güvenlik kontrolleri yine çalışır.
XAMPP'ta `php.ini` içinde `extension=gd` satırını açın.
</details>

---

### Katkı ve lisans

Katkılara açığız. Depoyu çatallayın (fork), bir dal açın, değişikliğinizi
gönderin. Kod yazarken mevcut üslubu koruyun: **Türkçe açıklamalar,
`declare(strict_types=1)`, katman ayrımına saygı.**

MIT Lisansı — dilediğiniz gibi indirip kullanabilir, değiştirebilir,
ticari projelerinizde kullanabilirsiniz.

**[cilginyazilim.com](https://cilginyazilim.com)** ile geliştirildi

Faydalı bulduysanız ⭐ vermeyi unutmayın.

## Sık Sorulan Sorular

### Rol tabanlı yetkilendirme (RBAC) nedir?

Yetkinin kullanıcıya değil role bağlandığı modeldir: kullanıcı bir role atanır, rol bir yetki kümesine sahiptir. Yeni bir kullanıcı eklendiğinde tek tek yetki vermek yerine rol seçilir; bir yetki değiştiğinde o roldeki herkes için aynı anda değişir. Bu panelde roller ve yetkiler tek bir tabloda tutulur ve hangi rolün neyi yapabildiği tek ekrandan görülür.

### Menüyü gizlemek yetkilendirme için yeterli mi?

Hayır. Arayüzde bir bağlantıyı gizlemek yalnızca görsel bir kolaylıktır; adresi bilen biri doğrudan istek gönderebilir. Bu yüzden yetki kontrolü her istekte sunucu tarafında yapılır ve yetkisiz istek arayüzden bağımsız olarak reddedilir. Menü gizleme, o kontrolün yerine değil üstüne konur.

### Composer veya bir framework gerekiyor mu?

Hayır. Proje sıfır bağımlılıkla çalışır: Composer, framework ve CDN kullanılmaz, tüm kütüphaneler proje içindedir. İndirip cy_login.sql dosyasını içe aktarmanız ve veritabanı bilgilerini yazmanız yeterlidir. Paylaşımlı hostinglerde de ek kurulum gerektirmez.

### Parolalar nasıl saklanıyor?

Parolalar hiçbir zaman düz metin olarak tutulmaz; PHP'nin password_hash fonksiyonuyla hashlenir ve doğrulama password_verify ile yapılır. Ayrıca ard arda başarısız giriş denemelerine karşı kaba kuvvet koruması ve sertleştirilmiş oturum ayarları vardır.

### Yeni bir sayfa nasıl eklenir?

README'deki "Yeni sayfa nasıl eklenir?" bölümü adım adım anlatır: rota tanımlanır, denetleyici metodu yazılır, görünüm dosyası eklenir ve sayfanın gerektirdiği yetki rol tablosuna işlenir. Yetki tanımlanmadığı sürece sayfa hiçbir rolde görünmez — varsayılan davranış reddetmektir.

