# Çekirdeğe Dokunmadan Çalışma

https://dev.wisecp.com/tr/cekirdege-dokunmadan-calisma

Platformun yayımladığı genişleme noktalarının işlenmiş bir kataloğu: her birinin neye ulaşıp neye ulaşamadığı ve önünüzdeki değişiklik için doğrusunun nasıl seçileceği.

## Genel Bakış

Aşağıdaki her noktanın adı belli bir giriş dosyası, bir sözleşmesi ve bir sınırı var.

Noktalar birbirinin dengi değil. Bir kanca yalnız yayımlanmış olduğu yere uzanır. Bir modül kendi dizinine, kendi tablolarına ve kendi ayarlarına sahiptir ama on altı tip sözleşmesinden birine sığmak zorundadır. Bir tema website'i kapsar, admin panelinde karşılığı yoktur.

## Ön Koşullar

- Arkasında bir nokta olmayan dosyaya yükseltme koşusunun ne yaptığı için [Güncellemeye Dayanıklı Çalışma İlkeleri](https://dev.wisecp.com/tr/guncellemeye-dayanikli-calisma-ilkeleri).
- Sistem dizinine yazma erişimi ve görebildiğiniz bir sayfayı yenileyebilme imkânı.
- Adı konmuş bir hedef: değiştirmek istediğiniz ekran, akış ya da değer. "Ödeme akışını değiştirmek", "sepet toplamı filtresinin verdiği değeri değiştirmek" hâline gelir.

## Yapı

### Genişleme Noktası Haritası

Değiştirdiğiniz şeye uyan satırı bulun, sözleşmesini aşağıdan okuyun.

| Neyi değiştirmeniz gerekiyor | Nokta | Sınırı |
| --- | --- | --- |
| Çekirdeğin hesapladığı bir değer (toplam, liste, veri yükü) | Bir `filter:` kancası | Yalnız yayımlanmış olan yerde. Jenerik bir "her metottan önce" araya girme yoktur |
| Olan bir şeye tepki verme (eşitleme, bildirim, denetim kaydı) | Bir `action:` kancası | Dönüş değeriniz atılır, istisnanız yutulur; loglamazsanız hatalarınız görünmez |
| Kendi kuralınıza göre bir işlemi reddetme | Bir `gate:` kancası | Reddet ya da izin ver, arası yok. İşlemi değiştiremezsiniz |
| Mevcut bir sayfaya işaretleme ekleme | Bir `ui:` kancası | Yalnız var olan konumlarda. Admin paneli için şablon geçersiz kılma katmanı yoktur |
| Bir şey sağlama, ödeme alma, ad tescili, mesaj gönderme | Uygun tipte bir modül | On altı tip, her biri sabit bir metot sözleşmesiyle. Hiçbirine sığmayan ihtiyacın modül tipi yoktur |
| Tümüyle size ait bir admin sayfası | Modül admin alanı | On altı tipin on birinde tanınır; kalanlar sessizce hiçbir şey olarak kaydedilir |
| Size ait bir HTTP ucu | API rota filtresi | İşleyiciniz modül örneğinde gerçek bir metot olmalıdır. Toplayıcı bir dağıtım yoktur |
| Mevcut bir sayfada yeni bir admin AJAX işlemi | Admin operation yedek kancası | Sarmalayıcı korumalarının hiçbiri geçerli değildir: yetki kontrolü, demo kapısı ve istek başlığı kontrolü yok |
| Zamanlanmış iş | Modülden kaydedilen bir cron işleyicisi | Yeniden deneme ve zamanlama kuyruğundadır. Diliminizin ne zaman koşacağını siz seçmezsiniz |
| Ziyaretçiye dönük görünüm | Bir tema dizini | Yalnız website. Admin paneli, tema katmanı olmayan düz PHP şablonlarıdır |
| Bir bileşen metninin ifadesi | Çeviri filtresi | Yalnız bileşen okumaları. Paket seviyesindeki metinlerin okuma anı filtresi yoktur |
| Şablona ulaşan değişkenler | Şablon değişkeni filtresi | Değerle çalışır ve dizi döndüren son dinleyici tüm kümenin yerine geçer |
| Müşteri, talep ya da ürün kaydındaki veri | Operatörün tanımladığı özel alanlar | Kod değil yapılandırma. Depolama ve gösterim alırsınız, davranış değil |

### Dosyalar Nereye Gider

Hepsini tek bir dizin taşır; yukarıdakilerin hiçbiri bu ağacın dışına uzanmaz.

```bash
Acme.php            # çekirdeğin örneklediği modül sınıfı
config.php          # ayarlar; modül yazar, bir sürüm asla yazmaz
hooks.php           # otomatik dahil edilir: her Hook::add burada yaşar
router.php          # admin yönlendirmesi için otomatik dahil edilir: admin alanını kaydeder
AdminArea.php       # size ait admin sayfaları
lang/en.php         # metinleriniz, istediğiniz gibi anahtarlanmış
cronjobs/Sync.php   # zamanlanmış işleyiciniz, hooks.php'den kaydedilir
src/AcmeClient.php  # size ait düz sınıflar, `new` ile kurulur
```

> **İki dosya sizin için yüklenir, geri kalanını siz dahil edersiniz**
> 
> Kanca yükleyicisi her modül dizinindeki `hooks.php` dosyasını tarar, admin yönlendirmesi de `router.php` dosyasını okur. Diğer her şeye bu ikisinden ulaşılır: açık bir `include_once` ile ya da çekirdeğin sizin için kurduğu modül sınıfı üzerinden.

## Adım Adım

### Gerçekte Neyi Değiştirdiğinizin Adını Koyun

1. Ekranı açın ve değiştirmek istediğiniz tam değeri, kontrolü ya da anı bulun.
2. Onu üreten kodu bulun. Ekrandaki metni, formdaki alan adını ya da adresteki rota anahtarını arayın, değerin sahibi helper'a ya da operation'a kadar geri izleyin.
3. Beş fiilden hangisinin geçerli olduğuna karar verin. Bir değeri dönüştürmek, bir olaya tepki vermek, bir eylemi reddetmek, işaretleme basmak, listeye kayıt eklemek. Kanca kategorisi o fiildir.

### Noktayı Seçin

1. Değişikliğinizin yaşadığı alanı `hooks/INDEX.md` kataloğunda arayın. Dizin her kancayı alanıyla ve tetiklendiği tam dosya ve satırla verir.
2. Kanca varsa önce parametre tablosunu okuyun. Bir argümanın referansla gelip gelmediği o sayfadadır ve dinleyicinizin nasıl yazılacağına o karar verir.
3. Kanca yoksa ama iş bütün bir yetenekse (bir panel, bir geçit, bir sağlayıcı) bu bir modüldür. Seçtiğiniz tip, çekirdeğin çağıracağı metotları sabitler.
4. İkisi de uymuyorsa çekirdeği yamalamak yerine jenerik bir genişleme noktası isteyin. Modülünüzün, bayrağınızın ya da tablonuzun adını taşıyan kanca jenerik değildir; kararın adını taşıyan kanca jeneriktir.

### Kaydedin

1. Modül dizininizde `hooks.php` yoksa oluşturun. İstek başına bir kez, ilk kanca etkinliğinde otomatik olarak dahil edilir.
2. Dosyanın tamamını modülünüzün etkin olmasına bağlayın. Yapılandırmayı hafif kipte yükleyip durumu okuyun, kaydı ondan sonra yapın.
3. Bir öncelikle kaydedin. Düşük olan önce çalışır, alınmış bir numara boş bulunana kadar artırılır; hiçbir dinleyici sessizce düşmez.
4. Modül örneğini dosyanın başında değil dinleyici gövdesinin içinde, kanonik fabrikayla kurun.

### Sağ Çıkacağını Kanıtlayın

1. Sayfayı yenileyin ve davranışın değiştiğini doğrulayın. Kaydı düzgün ama gövdesi hata fırlatan bir dinleyici, hiç ateşlenmemiş bir kancayla birebir aynı görünür. Hata yakalanır ve loglanır.
2. Kendi ağacınızda çekirdek yollarını arayın. Düzenlediğiniz, `coremio/classes`, `coremio/controllers`, `coremio/helpers` ya da `templates/admin` altındaki her şey bir gelecek arızadır.
3. Kendi dizininizin dışından çağırdığınız her kancayı, sınıfı ve metodu listeleyin. O liste, bir yükseltmeden sonra yeniden kontrol edeceğiniz uyumluluk sözleşmesidir.
4. Modülü kapatın ve sayfayı yenileyin. Ekran hatasız biçimde kendi varsayılan davranışına dönmelidir. Dönmüyorsa size ait bir şey durum kapısının dışında çalışıyordur.

## Referans

### Kayıt İmzaları

```php
// coremio/classes/Hook.php
public static function add($name, $priority, $properties = []): void;

// coremio/classes/Modules.php - sınıfı dahil etmeden yapılandırmayı yükler ($nominc = true),
// sonra okur. hooks.php içinde ucuz bir "modülüm açık mı?" kapısı için ikisi de gerekir.
public static function Load($type = '', $name = '', $nominc = false, $status = '');
public static function Config($type, $module);
public static function getInstance(string $type, string $name, array $params = []): ?object;

// coremio/classes/ModuleAdminArea.php - modülün router.php dosyasından çağrılır.
public static function register(string $areaClass): void;
public static function get(string $type, string $name): ?array;

// coremio/helpers/CronJobQueue.php - bir register:cronjobs dinleyicisinden çağrılır.
public static function register(string $type, string $handlerClass): void;
public static function dispatch(string $type, array $payload = [], array $opts = []): int;

// coremio/cronjobs/CronJobHandler.php - bir ARAYÜZDÜR, yani genişletmeyin, uygulayın.
// Başarı için true, başarısız olup yeniden denenmek için false, ya da admin Sonuçlar
// sekmesine bir yük de vermek için ['success' => bool, 'result' => array] döndürün.
public function handle(array $payload, array $job): bool|array;
```

- **Modules::Load()**: Üçüncü argüman `$nominc = true`, modül sınıfını dahil etmeden `config.php` ve dil dosyasını yükler. Kapı biçimi budur: `hooks.php` içinde her istekte koşacak kadar ucuzdur.
- **Modules::Config()**: Yalnız yükleme önbelleğinden okur. `Load()` çağrılmadan çağrılırsa hiçbir şey dönmez; bu "modül kapalı" diye okunur ve dosyanızın tamamını kapatır. Sıra isteğe bağlı değildir.
- **ModuleAdminArea::register()**: Sınıf adını alır, tip ve adı ad alanından çözer, üç rota ekler (`slug`, `slug/(?)`, `slug/(?)/(?)`) ve menü kaydını yapar. Sınıfı reddettiğinde de dahil olmak üzere hiçbir şey döndürmez.
- **CronJobQueue::register()**: İlk argüman işleyicinin `TYPE` sabiti, ikincisi sınıf adıdır. Keşif işleyicisi (`.discover` ile biten bir tip) ayrıca bir `FREQUENCY` sabiti ister; zamanlayıcının okuduğu değer odur.

### Her Nokta Karşılığında Ne Bekler

| Kanca | Ateşlenmesi | Sözleşme |
| --- | --- | --- |
| `register:admin.operations` | `run()`, argümanlar: controller adı, operation adı | Reddetmek için `null` döndürün. Dizi döndürürseniz JSON'a çevrilip yanıtın tamamı olarak gönderilir |
| `filter:api.routes` | `runRefs()`, argümanlar: referansla rota listesi, referansla hedef kitle | Listeye demet ekleyin. Dönüş değeri kullanılmaz. İlk eşleşme kazanır, gölgelemeye öncelik karar verir |
| `register:cronjobs` | `run()`, argümansız | İşleyici dosyanızı dahil edin ve kuyruğun kayıt metodunu çağırın. Dönüş değeri kullanılmaz |
| `filter:i18n.translation` | `runRefs()`, argümanlar: referansla metin, anahtar, dil kodu | Metni yerinde değiştirin. Yalnız bileşen okumalarında ve yalnız çözülen değer bir dize olduğunda ateşlenir |
| `filter:template.variables` | `run()`, argümanlar: şablon yolu, veri dizisi | Değiştirilmiş veri dizisinin **tamamını** döndürün. Dizi döndüren son dinleyici tek başına kazanır |
| `register:admin.menu` | `run()`, argümansız | Menü ağacına yazıp `true` döndürün ya da hiçbir katkı yapmamak için `false` döndürün |

```php
// [0] METHOD    [1] desen (/api/v1 sonrasındaki tam yol, {x} yakalar)
// [2] Group     [3] Action   [4] public?  [5] authOnly?  [6] audience
$routes[] = ['GET', 'acme/status', 'Module:Addons/Acme', 'status', false, true, 'admin'];

// "Module:{Type}/{Name}" grubu, modül örneğinin şu metoduna dağıtır
//   api_{Action}(WISECP\Api\Core\Request $request, array $match)
// ve kararı method_exists verir: __call yedeği yoktur. [3] içindeki bir yazım hatası
// görebileceğiniz bir hata değil, 404'tür.
//
// [4] public = true   → hiç kimlik bilgisi yok; modül kendi erişimini kendi denetler
// [5] authOnly = true → geçerli bir kimlik bilgisi yeter, kapsam kontrol edilmez (serbest
//                       yüzeydeki varsayılan, çünkü modül kapsamları katalogda değildir)
// [6] audience        → 'admin' | 'client' | 'any', yalnız public false iken bakılır
```

### Genişleme Noktasının Olmadığı Yerler

Dört boşluk. Hiçbirinin desteklenen bir kaçamağı yok.

- **Admin şablonları geçersiz kılınamaz**: Website'in teması vardır, admin panelinin yoktur. Şablonları düz PHP'dir ve tek bir sabit dizinden yüklenir. Tek giriş yolu, zaten var olan bir konumdaki `ui:` kancasıdır. Olmayan bir konum yeni bir kanca talebidir.
- **Beş modül tipi admin sayfası açamaz**: Admin alanı çözücüsü on bir tipi kabul eder: Servers, Payment, Registrars, Product, Addons, SMS, Mail, Authentication, Pipe, Imports ve Fraud. Bir SocialAuth, Captcha, Currency, IP ya da Storage modülü kimlik kontrolünde reddedilir ve `register()` hiçbir şey yapmadan, hiçbir yerde hata bırakmadan döner. Yanına bir Addons modülü koyun ya da tipin eklenmesini isteyin.
- **Paket metinlerinin okuma anı filtresi yoktur**: Çeviri filtresi yalnız bileşen okumasının içinde ateşlenir. Paket okumasıyla alınan metinler hiçbir kancadan geçmez. Birini değiştirmenin tek yolları operatör dil düzenleyicisi ve kendi dosyanızdaki kendi anahtarınızdır.
- **Bir çekirdek tablosuna eklediğiniz kolonun sahibi yoktur**: Bunu hiçbir şey yasaklamaz ve migration içe aktarıcısı da takılmaz: yinelenen kolon hatası "zaten uygulanmış" sayılır. Ama hiçbir çekirdek kodu onu okumaz ya da yazmaz. Sonraki bir sürüm aynı kolon adını eklerse atlanan onun tanımı olur; sizinki kalır, çekirdeğin beklentisi tutmaz. Adın önüne modülünüzü ekleyin, tercihen kendi tablonuzu kullanın.

## Örnek

Dört noktayı kullanan, kendi dizininin dışına dokunmayan küçük bir eklenti.

```php
<?php

// Ucuz kapı: yalnız yapılandırma, sınıf dahil edilmez. Aşağıdaki her şey if'in içinde,
// yani kapalı bir modül hiçbir şey kaydetmez ve yalnız bir dosya okuması kadar tutar.
Modules::Load('Addons', 'Acme', true);
$acme = Modules::Config('Addons', 'Acme') ?: [];

if ($acme['status'] ?? false) {

    // 1. Bu modülün sahibi olmadığı bir sayfadaki AJAX eylemi. services controller'ında
    //    operation=acme_resync olarak ulaşılır. Bu çağrıyı hiçbir şey sarmalamaz,
    //    yani yetki kontrolünü yapmak bize düşer.
    Hook::add('register:admin.operations', 1, function ($cname, $operation) {
        if ($operation !== 'acme_resync')       return null;
        if ($cname !== 'services')              return null;
        if (!Admin::isPrivilege(['SERVICES_OPERATION']))
            return ['status' => 'error', 'message' => Language::gc('acme/no-privilege')];

        return Modules::getInstance('Addons', 'Acme')->resync_request();
    });

    // 2. Kimlik bilgisi isteyen admin yüzeyinde bize ait bir uç.
    Hook::add('filter:api.routes', 1, function (&$routes, &$audience) {
        if ($audience !== 'admin') return;
        $routes[] = ['GET', 'acme/status', 'Module:Addons/Acme', 'status', false, true, 'admin'];
    });

    // 3. Zamanlanmış bir görev. Dosya açılışta değil burada dahil edilir: cron kaydı
    //    yalnız çekirdek dizinini tarar.
    Hook::add('register:cronjobs', 1, function () {
        include_once __DIR__ . DS . 'cronjobs' . DS . 'Sync.php';
        CronJobQueue::register(\WISECP\Modules\Addons\Acme\CronJobs\Sync::TYPE,
                               \WISECP\Modules\Addons\Acme\CronJobs\Sync::class);
    });

    // 4. Bir dil dosyasını düzenlemeden tek bir bileşen metnini yeniden ifade edin.
    //    Referansla ve yalnızca ifadesinin sahibi olduğumuz tam anahtar için.
    Hook::add('filter:i18n.translation', 1, function (&$text, $key, $lang) {
        if ($key !== 'admin/services/status-active') return;
        $text = Language::gc('acme/status-live');
    });
}
```

İlk noktanın diğer tarafı. Metot, çekirdeğin kodladığı diziyi döndürür; yanıtın şeklinin sahibi odur.

```php
public function resync_request(): array
{
    $id = (int) Filter::init('POST/id', 'rnumbers');
    if (!$id) return ['status' => 'error', 'message' => Language::gc('acme/id-required')];

    // Fırlatmak, modülün başarısızlığı bildirme yoludur; burada bir AJAX çağrısını
    // doğrudan yanıtlıyoruz, dolayısıyla hatayı bunun yerine elle şekillendiriyoruz.
    try {
        $rows = $this->client()->resync($id);
    }
    catch (\Throwable $e) {
        return ['status' => 'error', 'message' => $e->getMessage()];
    }

    return ['status' => 'successful', 'message' => Language::gc('acme/resynced'), 'count' => $rows];
}

// Yukarıda kaydedilen API ucu buraya iner. Ad, api_ artı demetteki Action'dır
// ve kararı veren tek şey method_exists'tir.
public function api_status(\WISECP\Api\Core\Request $request, array $match): array
{
    return ['data' => ['enabled' => true, 'last_sync' => Config::getd('acme_last_sync')]];
}
```

Admin sayfası: iki dosya ve tek satırlık bir kayıt.

```php
// coremio/modules/Addons/Acme/router.php
namespace WISECP\Modules\Addons\Acme;

include_once __DIR__ . DS . 'AdminArea.php';

\ModuleAdminArea::register(AdminArea::class);

// coremio/modules/Addons/Acme/AdminArea.php
namespace WISECP\Modules\Addons\Acme;

class AdminArea extends \ModuleAdminArea
{
    public static function manifest(): array
    {
        return [
            'title'      => 'Acme',
            'slug'       => 'acme',
            'privileges' => ['TOOLS_ADDONS'],
            'menu'       => ['path' => ['TOOLS'], 'name' => 'Acme'],
        ];
    }

    // /{admin}/acme          → page_home()
    // /{admin}/acme/report   → page_report()
    public function page_home(array $params): string|array
    {
        return ['content' => '<p>Acme</p>', 'page_title' => 'Acme'];
    }

    // Aynı adrese operation=refresh ile POST
    public function op_refresh(\Operation $operation): bool
    {
        $operation->demo();

        return $operation->output(['status' => 'successful']);
    }
}
```

## Tuzaklar

> **Operation yedek kancası her sarmalayıcının dışında çalışır**
> 
> Normal bir operation bir sarmalayıcıdan geçer. O sarmalayıcı yetki listesini kontrol eder, panelin AJAX katmanından gelmeyen isteği reddeder ve demo kipinde yazmaları engelleyen nesneyi kurar. Yedek kancasına ancak sarmalayıcı reddettikten sonra ulaşılır, yani bunların hiçbiri geçerli değildir. Yetkiyi kendiniz, dinleyicinin içinde, ilk iş olarak kontrol edin.

> **Şablon değişkeni filtresinde kazanan her şeyi alır**
> 
> Değerle ateşlenir ve çağıran, dönen her diziyi bir öncekinin üzerine atar. Dizi döndüren son dinleyici kümenin tamamının yerine geçer; aynı şablondaki iki dinleyici birleşmez. Size verilen diziyi okuyun, değiştirin ve tamamını döndürün.

> **Reddedilen bir admin alanı kaydı sessizdir**
> 
> Kayıt üç durumda hiçbir şey yapmadan döner. Sınıf bir alt sınıf değilse, ad alanı beklenen biçimde değilse ya da modül tipi tanınan on bir tipten biri değilse. İstisna yok, log satırı yok, menü kaydı yok. Sayfanız bulunamadı verdiğinde önce tipe bakın.

> **hooks.php içinde dosya seviyesinde yapılan iş her istekte çalışır**
> 
> Kanca yükleyicisi, kancalarınızdan biri ateşlenecek olsun ya da olmasın dosyayı dahil eder. Bir dinleyici gövdesinin dışına yazılmış veritabanı sorgusu, uzak çağrı ya da modül örneği oluşturma her sayfa yüklemesinde ödenir. Yapılandırmayı yükleyin, durumu kontrol edin, geri kalanı bir closure'ın içine koyun.

## İlgili Makaleler

- [Güncellemeye Dayanıklı Çalışma İlkeleri](https://dev.wisecp.com/tr/guncellemeye-dayanikli-calisma-ilkeleri)
- [Çekirdek Yükseltmesini Atlatma](https://dev.wisecp.com/tr/cekirdek-yukseltmesini-atlatma)
- [Kanca Dinleyicisi Yazma](https://dev.wisecp.com/tr/kanca-dinleyicisi-yazma)
- [Modülden Kanca Kaydetme](https://dev.wisecp.com/tr/modulden-kanca-kaydetme)
- [Admin Sayfası Ekleme](https://dev.wisecp.com/tr/admin-sayfasi-ekleme)
- [API Ucu Açma](https://dev.wisecp.com/tr/api-ucu-acma)
