# IP Modülü Yazma

https://dev.wisecp.com/tr/ip-modulu-yazma

Bir konum ve proxy tespiti sağlayıcısını, ürünün ziyaretçinin yerini ve adresin riskini sorduğu tek noktaya bağlayın.

## Genel Bakış

Bir IP modülü bir adres hakkında iki soruyu yanıtlar. Nerede, ve proxy mi? Aynı anda tek modül etkindir; seçim `modules/ip` altında saklanır. Diğer her şey bunu `UserManager::ip_info()` ve `UserManager::is_proxy()` üzerinden okur.

Bu cevaplar uzağa uzanır. Para birimi ülke kodundan seçilir, giriş akışı ülkesi değişen oturumu doğrulatır, formlar proxy'yi reddedebilir.

İki modül gelir: WAtlas ve ip_api. Taban sınıf yoktur; sözleşme çağrı noktasının kendisidir.

## Ön Koşullar

- IPv4 ya da IPv6 adresini en azından bir ISO ülke koduna çözen bir sağlayıcı. O alanın yedeği yoktur.
- Sağlayıcıya dışa HTTP erişimi ve hız sınırı planı.
- [Modül Anatomisi](https://dev.wisecp.com/tr/modul-anatomisi) ve [Modül Yapılandırması](https://dev.wisecp.com/tr/modul-yapilandirmasi).

## Yapı

```bash
coremio/modules/IP/AcmeGeo/
├── AcmeGeo.php       AcmeGeo sınıfı   (taban sınıf yok, ad alanı gerekmez)
├── config.php        ['website' => 'AcmeGeo', 'key' => '']
└── pages/
    └── settings.php  kimlik bilgisi alanları, Ayarlar içinde render edilir
```

- **UserManager::ip_info()**: Konum giriş noktası. `info()` çağırıp önbelleğe alır.
- **UserManager::is_proxy()**: Risk giriş noktası. `proxy()` bildirilmişse çağırır, sonra beyaz listeyi uygular.
- **Modules::getInstance()**: Nesnenizi kurar; `new` kullanılmaz.
- **Modules::getPage()**: `pages/settings.php` parçasını Ayarlar ekranına yerleştirir.
- **coremio/modules/IP**: Klasörünüz buraya girer.

## Adım Adım

### 1. Modül İskeletini Kurun

1. `coremio/modules/IP/AcmeGeo/` dizinini `AcmeGeo.php` ile açın. Sınıf adı klasörle aynı olmalı.
2. `config.php` yazın. Açılır liste `website` anahtarını kullanır.
3. Ayar alanlarınızı boş anahtarlar olarak ekleyin.

### 2. info() Metodunu Uygulayın

1. Size verilen adresle sağlayıcınızı çağırın; çekirdek adresi zaten çözdü.
2. Cevabı normalize edin. İki kural zorunlu: `countryCode` küçük harf, sağlayıcı yalnız bölge verse bile `city` dolu.
3. Başarısızlıkta `$this->error` yazın ve false döndürün.

### 3. proxy() Metodunu Uygulayın ya da Uygulamayın

1. Metot isteğe bağlıdır. Yalnız konum çözen bir modül onsuz da tamamdır; proxy engelleme o zaman karar üretmez.
2. İki anahtar benzer görünür: `proxy` "proxy'ye benziyor", `result` "bunu engelle" demektir. Yalnız `result` bir şeyi durdurur.
3. Otonom sistemi `as` anahtarında `AS15169 Example Org` biçiminde döndürün; beyaz liste ilk parçayla eşleşir.

### 4. Ayarlar Sayfasını Ekleyin

1. `pages/settings.php` oluşturun: form değil, Ayarlar formuna eklenen düz bir parça.
2. Her girdiyi `ip_api_config[anahtar]` olarak adlandırın; ad doğrudan config anahtarıdır.
3. Mevcut değerleri `$module->config` üzerinden okuyun.
4. Ayarlar'da modülünüzü seçin ve kaydedin. Seçim `modules/ip` anahtarına düşer.

## Referans

### İki Metot

Uygulanacak bir arayüz yoktur. Aşağıdakiler tam olarak çağrı noktalarıdır.

```php
public $error;          // false dönüşünden sonra çekirdek tarafından okunur
public $config = [];    // yapıcıda modülün config.php dosyasından doldurulur

public function __construct();

// Konum. ZORUNLU. Aşağıdaki diziyi döndürün, ya da $this->error dolu olarak false.
// classes/UserManager.php içindeki UserManager::ip_info() metodundan çağrılır.
public function info($ip = '');

// Risk puanlama. İSTEĞE BAĞLI - çekirdek çağırmadan önce method_exists() ile yoklar.
// classes/UserManager.php içindeki UserManager::is_proxy() metodundan çağrılır.
public function proxy($ip = '');
```

```php
// classes/UserManager.php, ip_info()
$ip_module = Config::get("modules/ip");
$obj       = Modules::getInstance("IP", $ip_module);
if (!$obj) return ['status' => "error", 'message' => "IP module '{$ip_module}' could not be loaded."];

$result = $obj->info($ip);
if (!$result) {
    // Zaman aşımı düz bir false olarak yutulur; başka her şey loglanır ve yüzeye çıkarılır.
    if (stristr($obj->error, 'timed out')) return false;
    Modules::save_log("IP", $ip_module, "check", $ip, $obj->error);

    return ['status' => "error", 'message' => $obj->error];
}

// classes/UserManager.php, is_proxy()
$proxy_obj = Modules::getInstance("IP", $ip_module);
if ($proxy_obj && method_exists($proxy_obj, 'proxy')) {
    $pdata = $proxy_obj->proxy($ip);
    if ($pdata === false) $error = $proxy_obj->error;
}
```

### info() Ne Döndürür

Tüketicilerin okuduğu anahtarlar, bekledikleri biçimle.

| Anahtar | Biçim | Kim okur |
| --- | --- | --- |
| `countryCode` | **küçük harf** ISO kodu, örn. `nl` | Para birimi ve konum denetimi. Yoksa sorgu başarısız. |
| `city` | şehir, yoksa bölge adı | Şehir hassasiyetinde konum denetimi. |
| `regionName` | bölge ya da eyalet adı | Gösterim; çoğu modül `city` içine kopyalar. |
| `country` | İngilizce ülke adı | Gösterim. |
| `as` | `AS15169 Example Org` | Proxy beyaz listesi, ilk parça. |
| `query` | sorgulanan adres | Girdinin yankısı. |
| `zip`, `lat`, `lon`, `timezone`, `isp` | dize ya da sayı | Opsiyonel; saklanır, zorunlu değildir. |

> **Ülke kodu yoksa false döndürün**
> 
> Çağıranlar boş `countryCode` değerini "bilinmiyor" sayar; ama dolu dizi yine başarılı sorgu sayılıp önbelleğe alınır. Arıza, dosya silinene kadar o adrese yapışır.

### proxy() Ne Döndürür

- **result**: Boolean. Engelleme kararı; bir şeyi durduran tek anahtar budur.
- **proxy**: Boolean. "Proxy ya da VPN'e benziyor." Bilgilendiricidir.
- **hosting**: Boolean. Veri merkezi aralığı. O da bilgilendiricidir.
- **as**: Otonom sistem. Beyaz listedeki bir AS numarası riskli kararı temizler.
- **score, verdict**: Serbest ekler. Çekirdek okumaz ama önbelleğe yazılır.

Ayrıntılı biçim yalnız üç boolean döndürür:

```php
// Üçüncü argüman true, çıplak karar yerine ayrıntılı dökümü ister.
$verdict = UserManager::is_proxy($ip, false, true);
// ['proxy' => bool, 'hosting' => bool, 'risky' => bool]
// 'risky' sizin 'result' değerinizdir, operatörün beyaz listesi uygulandıktan sonra.

// Giriş ve kayıt kapılarının kullandığı yaygın biçim:
if (Config::get('options/proxy-block') && UserManager::is_proxy() === true)
    throw new Exception(Language::g('errors/error9'));
```

### Önbellek ve Kota

Çekirdek sağlayıcınızı siz kod çalıştırmadan önce korur. Bir düzeltmenin etkisiz görünmesinin sebebi de bu dosyalardır.

- **temp/ip-log-{ip}.json**: Başarılı bir `info()` sonucu, adres başına, süresiz.
- **temp/{ip}-proxy.json**: `proxy()` için aynısı. Bir adresi yeniden denerken ikisini de silin.
- **coremio/storage/ip-overload-limit.php**: Tarih ve sayaç. Sınır aşılınca `ip_info()` hata dizisi döner ve sizi çağırmaz. `options/ip-overload-limit`, varsayılan 2000; 0 sınırı kaldırır.
- **coremio/storage/proxy-overload-limit.php**: Risk sorguları için aynısı: `options/proxy-overload-limit`, varsayılan 100.
- **istek içi bellek**: Adres başına statik harita; tek istekteki tekrar sorgular bedavadır.

### Ayarlar Sözleşmesi

- **config.php: website**: Açılır liste etiketi, `$v["config"]["website"]` değerinden. Anahtar yoksa seçenek boş görünür.
- **girdi adı: ip_api_config[key]**: Köşeli parantez içindeki ad birebir config anahtarı olur.
- **$module**: Parçanın aldığı tek değişken: canlı örneğiniz.
- **get_ip_api_configs()**: Parçanızı getiren operation (`operations/AdminGeneralSettings.php`).
- **özyinelemeli birleştirme**: Gönderilen dizi saklı config üzerine birleştirilir; yazmadığınız anahtar değerini korur.

## Örnek

Eksiksiz bir modül, ayarlar parçası ve tüketici tarafı.

```php
<?php

class AcmeGeo
{
    public $error;
    public $config = [];

    public function __construct()
    {
        $this->config = Modules::Config("IP", __CLASS__);
    }

    public function info($ip = '')
    {
        $this->error = null;

        $key = (string) ($this->config["key"] ?? '');
        $url = "https://api.acmegeo.example/v1/lookup/" . rawurlencode($ip);

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: " . $key]);

        $response = curl_exec($ch);
        if (curl_errno($ch)) {
            $this->error = curl_error($ch);
            $response    = false;
        }
        curl_close($ch);

        if ($response === false) return false;

        $data = Utility::jdecode((string) $response, true);
        if (!is_array($data)) {
            $this->error = "Invalid response from AcmeGeo.";

            return false;
        }

        // Ülke yoksa kullanılabilir cevap da yok. Burada dolu bir dizi döndürmek başarı
        // sayılıp önbelleğe alınır ve dosya silinene dek o adres bozuk kalır.
        $iso = (string) ($data["country_code"] ?? '');
        if (!$iso) {
            $this->error = "Country code not found : " . $ip;

            return false;
        }

        $region = (string) ($data["region"] ?? '');
        $city   = (string) ($data["city"] ?? '');

        return [
            'status'      => "success",
            'query'       => (string) ($data["ip"] ?? $ip),
            'countryCode' => strtolower($iso),          // küçük harf zorunludur
            'country'     => (string) ($data["country_name"] ?? ''),
            'regionName'  => $region ?: $city,
            'city'        => $city ?: $region,          // city alanını asla boş bırakmayın
            'zip'         => (string) ($data["postal"] ?? ''),
            'lat'         => $data["latitude"] ?? '',
            'lon'         => $data["longitude"] ?? '',
            'timezone'    => (string) ($data["time_zone"] ?? ''),
            'as'          => isset($data["asn"]) ? trim("AS" . $data["asn"] . " " . ($data["asn_org"] ?? '')) : '',
        ];
    }

    public function proxy($ip = '')
    {
        $this->error = null;

        $ch = curl_init("https://api.acmegeo.example/v1/risk/" . rawurlencode($ip));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: " . (string) ($this->config["key"] ?? '')]);

        $response = curl_exec($ch);
        if (curl_errno($ch)) {
            $this->error = curl_error($ch);
            $response    = false;
        }
        curl_close($ch);

        if ($response === false) return false;

        $data = Utility::jdecode((string) $response, true);
        if (!is_array($data)) {
            $this->error = "AcmeGeo risk lookup failed.";

            return false;
        }

        $score = (int) ($data["score"] ?? 0);

        return [
            // 'result' engeller, 'proxy' yalnız tanımlar. İkisini birleştirmeyin.
            'result'  => $score >= 60,
            'proxy'   => $score >= 30,
            'hosting' => (bool) ($data["datacenter"] ?? false),
            'score'   => $score,
            'as'      => isset($data["asn"]) ? trim("AS" . $data["asn"] . " " . ($data["asn_org"] ?? '')) : '',
        ];
    }
}
```

```php
<?php
return [
    'website' => "AcmeGeo",   // Ayarlar açılır listesinin gösterdiği etiket
    'key'     => '',          // kayıt yolu tarafından geri yazılır, asla elle değil
];
```

```html
<div class="row mb-3 pb-3 pt-3 border-bottom">
    <label for="acmegeo_key" class="col-sm-1 col-form-label text-sm-end">
        <span class="d-block fw-semibold">Api Key</span>
    </label>
    <div class="col-sm-11">
        <!-- Ad, config anahtarıdır: ip_api_config[key] config.php dosyasına 'key' olarak düşer. -->
        <input type="text" class="form-control" id="acmegeo_key"
               name="ip_api_config[key]"
               value="<?php echo $module->config["key"] ?? ''; ?>">
    </div>
</div>
```

```php
// helpers/Money.php - ziyaretçinin para birimi ülke kodundan gelir.
$info = UserManager::ip_info();

// Günlük kota tükendiğinde ya da modül yüklenemediğinde ip_info() ayrıca
// ['status' => 'error', 'message' => ...] döndürür: dolu, ama countryCode yok. Null-güvenli okuyun.
$needle = strtoupper($info["countryCode"] ?? '');

// classes/Auth.php - giriş konum denetimi, ülke ya da şehir hassasiyetinde.
$country = is_array($info) ? (string) ($info['countryCode'] ?? '') : '';
$city    = is_array($info) ? (string) ($info['city'] ?? '') : '';
if ($country === '') return false;   // ulaşılamayan bir sorgu asla değişiklik sayılmaz
```

## Tuzaklar

> **Hata error özelliğiyle bildirilir, fırlatılmaz**
> 
> Sağlama, ödeme ve servis sağlayıcı modülleri hata fırlatır ve çağıranları yakalar. Buradaki iki giriş noktası yakalamaz: boş bir dönüş arar, sonra `$this->error` okur. `info()` içinde doğan bir hata küresel işleyiciye kaçar. Özelliği yazın, false döndürün.

> **Zaman aşımını kısa tutun: sayfa yüklenirken çalışırsınız**
> 
> Bu sorgular sıradan isteklerin içinde çalışır. Hazır modüller iki ile on saniye arası kullanır. İçinde "timed out" ya da "timeout" geçen mesaj bilerek false olarak yutulur.

> **Önbelleklenmiş bir sorgu değişikliğinizi gizler**
> 
> Adres başına önbellek dosyalarının süresi yoktur, yani ilk çağrıdan sonra yeni kodunuza o adres için hiç ulaşılmaz. Ölçmeden önce iki dosyayı da silin.

> **Yalnız tek modül etkindir**
> 
> Ödeme yöntemlerinin aksine bu, `modules/ip` altında saklanan tekil bir seçimdir. Modülünüzü kurmak hiçbir şeyi açmaz; önce operatörün onu seçmesi gerekir.

## İlgili Makaleler

- [Modül Anatomisi](https://dev.wisecp.com/tr/modul-anatomisi)
- [Modül Yapılandırması](https://dev.wisecp.com/tr/modul-yapilandirmasi)
- [Dolandırıcılık Modülü Yazma](https://dev.wisecp.com/tr/dolandiricilik-modulu-yazma)
- [Kur Modülü Yazma](https://dev.wisecp.com/tr/kur-modulu-yazma)
- [Hata Yönetimi](https://dev.wisecp.com/tr/hata-yonetimi)
- [Güvenlik Pratikleri](https://dev.wisecp.com/tr/guvenlik-pratikleri)
