Aktivasyon ve Deaktivasyon Hook: WordPress Kurulum Yönetimi

Bir WordPress eklentisi geliştirirken en çok gözden kaçan ama en kritik parçalardan biri aktivasyon ve deaktivasyon hook’larıdır. Yıllarca eklenti geliştirdikten sonra şunu net olarak söyleyebilirim: Bu hook’ları doğru kullanmayan eklentiler ya kullanıcı veri tabanını kirletiyor, ya silme sonrası iz bırakıyor, ya da kurulum sırasında gerekli tabloları oluşturmayı unutuyor. Bu yazıda bu mekanizmayı baştan sona ele alacağız.

Aktivasyon ve Deaktivasyon Hook’ları Nedir?

WordPress, bir eklenti aktive edildiğinde veya deaktive edildiğinde belirli fonksiyonları tetikler. Bu fonksiyonlar sayesinde eklentinizin düzgün çalışması için gerekli altyapıyı kurabilir, eklenti devre dışı bırakıldığında ise geçici verileri temizleyebilirsiniz.

İki temel hook vardır:

  • register_activation_hook: Eklenti aktive edildiğinde çalışır
  • register_deactivation_hook: Eklenti deaktive edildiğinde çalışır

Bir de silme işlemi için register_uninstall_hook veya uninstall.php dosyası vardır, onu da ele alacağız.

Bu hook’ların kritik bir özelliği var: Normal WordPress hook sistemiyle (add_action, add_filter) çalışmıyorlar. Doğrudan ana eklenti dosyasına, yani eklentinin __FILE__ sabitine bağlanmaları gerekiyor. Bu detayı kaçırırsanız hook’larınız hiç çalışmaz.

Temel Kullanım Yapısı

Önce iskelet yapıya bakalım. Ana eklenti dosyanız (genellikle my-plugin/my-plugin.php) şu şekilde organize olmalı:

<?php
/**
 * Plugin Name: Benim Eklentim
 * Description: Örnek eklenti
 * Version: 1.0.0
 * Author: Ahmet Yılmaz
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// Aktivasyon hook'u
register_activation_hook( __FILE__, 'benim_eklentim_activate' );

// Deaktivasyon hook'u
register_deactivation_hook( __FILE__, 'benim_eklentim_deactivate' );

function benim_eklentim_activate() {
    // Aktivasyon işlemleri
}

function benim_eklentim_deactivate() {
    // Deaktivasyon işlemleri
}

Basit görünüyor, değil mi? Ama şeytan ayrıntıda gizli. Özellikle büyük eklentilerde bu fonksiyonların içi çok karmaşık bir hale gelebiliyor.

Aktivasyon Hook’u: Ne Yapmalı?

Aktivasyon hook’u genellikle şu işler için kullanılır:

  • Özel veritabanı tabloları oluşturma
  • Varsayılan seçenekleri kaydetme
  • Gerekli dosya/dizin yapısını oluşturma
  • Zamanlanmış görevleri (cron) planlama
  • Kapasiteleri (capabilities) ve rolleri tanımlama

Özel Veritabanı Tablosu Oluşturma

Bu en yaygın kullanım senaryosu. $wpdb->get_charset_collate() ve dbDelta() fonksiyonlarını mutlaka kullanın:

function benim_eklentim_activate() {
    global $wpdb;

    $tablo_adi = $wpdb->prefix . 'eklenti_loglar';
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE $tablo_adi (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        kullanici_id bigint(20) NOT NULL,
        eylem varchar(100) NOT NULL,
        tarih datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        PRIMARY KEY (id),
        KEY kullanici_id (kullanici_id)
    ) $charset_collate;";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( $sql );

    // Tablo versiyonunu kaydet
    add_option( 'benim_eklentim_db_version', '1.0' );
}

dbDelta() fonksiyonu hakkında önemli bir not: Bu fonksiyon tablo yoksa oluşturur, varsa günceller. Ama SQL yazım kurallarına çok duyarlıdır. Sütun tanımlarından sonra virgül kullanımı, büyük-küçük harf duyarlılığı gibi konulara dikkat edin. Yanlış formatta yazılmış SQL, dbDelta() tarafından sessizce görmezden gelinebilir.

Varsayılan Seçenekleri Kaydetme

add_option kullanın, update_option değil. Neden? Çünkü add_option sadece değer yoksa ekler, zaten varsa değiştirmez. Bu sayede kullanıcının önceki ayarlarını ezmemiş olursunuz:

function benim_eklentim_activate() {
    // Varsayılan seçenekler
    $varsayilan_ayarlar = array(
        'aktif'           => true,
        'log_seviyesi'    => 'warning',
        'max_kayit'       => 1000,
        'email_bildirimi' => get_option( 'admin_email' ),
    );

    add_option( 'benim_eklentim_ayarlar', $varsayilan_ayarlar );
    add_option( 'benim_eklentim_kurulum_tarihi', current_time( 'mysql' ) );

    // Cron görevi planla
    if ( ! wp_next_scheduled( 'benim_eklentim_gunluk_temizlik' ) ) {
        wp_schedule_event( time(), 'daily', 'benim_eklentim_gunluk_temizlik' );
    }
}

Flush Rewrite Rules

Özel post type veya taxonomy kaydeden eklentiler için aktivasyon sonunda rewrite kurallarını yenilemeniz şart. Yoksa özel URL’leriniz 404 verecek:

function benim_eklentim_activate() {
    // Önce custom post type'ı kaydet
    benim_eklentim_post_type_kaydet();

    // Sonra rewrite kurallarını yenile
    flush_rewrite_rules();
}

Deaktivasyon Hook’u: Ne Yapmalı?

Deaktivasyon, silme değildir. Kullanıcı eklentiyi geçici olarak kapatmış olabilir. Bu yüzden deaktivasyon hook’unda veritabanı tablolarını veya ayarları silmemelisiniz. Bunun için uninstall mekanizması var.

Deaktivasyon için uygun işlemler:

  • Zamanlanmış cron görevlerini iptal etme
  • Geçici verileri (transients) temizleme
  • Aktif oturumları veya kilitli kaynakları serbest bırakma
function benim_eklentim_deactivate() {
    // Cron görevini iptal et
    $timestamp = wp_next_scheduled( 'benim_eklentim_gunluk_temizlik' );
    if ( $timestamp ) {
        wp_unschedule_event( $timestamp, 'benim_eklentim_gunluk_temizlik' );
    }

    // Tüm zamanlanmış görevleri temizle (toplu)
    wp_clear_scheduled_hook( 'benim_eklentim_gunluk_temizlik' );
    wp_clear_scheduled_hook( 'benim_eklentim_haftalik_rapor' );

    // Geçici verileri temizle
    delete_transient( 'benim_eklentim_cache' );
    delete_transient( 'benim_eklentim_api_yanit' );

    // Rewrite kurallarını yenile
    flush_rewrite_rules();
}

Uninstall: Kalıcı Temizlik

Şimdi gelelim çoğu eklentinin ihmal ettiği kısma. Kullanıcı eklentiyi sildiğinde tüm verilerin temizlenmesi için iki yöntem var.

Birinci yöntem: register_uninstall_hook

register_uninstall_hook( __FILE__, 'benim_eklentim_uninstall' );

function benim_eklentim_uninstall() {
    global $wpdb;

    // Seçenekleri sil
    delete_option( 'benim_eklentim_ayarlar' );
    delete_option( 'benim_eklentim_db_version' );
    delete_option( 'benim_eklentim_kurulum_tarihi' );

    // Özel tabloyu sil
    $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}eklenti_loglar" );

    // Tüm kullanıcı meta verilerini temizle
    $wpdb->delete(
        $wpdb->usermeta,
        array( 'meta_key' => 'benim_eklentim_tercihler' )
    );
}

İkinci yöntem: uninstall.php dosyası (önerilen)

Ana eklenti dizinine uninstall.php adında bir dosya koyun. WordPress bu dosyayı otomatik olarak bulur ve eklenti silindiğinde çalıştırır. Bu yöntem daha güvenli çünkü eklenti kodu yüklenmeden önce çalışıyor:

<?php
// uninstall.php

// WordPress'in bu dosyayı doğrudan çağırdığından emin ol
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
    exit;
}

global $wpdb;

// Çoklu site desteği
if ( is_multisite() ) {
    $bloglar = get_sites( array( 'fields' => 'ids' ) );
    foreach ( $bloglar as $blog_id ) {
        switch_to_blog( $blog_id );
        benim_eklentim_verileri_temizle();
        restore_current_blog();
    }
} else {
    benim_eklentim_verileri_temizle();
}

function benim_eklentim_verileri_temizle() {
    global $wpdb;

    delete_option( 'benim_eklentim_ayarlar' );
    delete_option( 'benim_eklentim_db_version' );

    $wpdb->query(
        "DROP TABLE IF EXISTS {$wpdb->prefix}eklenti_loglar"
    );
}

Nesne Yönelimli Yaklaşım

Modern eklenti geliştirmede hook’ları sınıf içinde kullanmak çok daha düzenli bir yapı sağlar. Ama burada kritik bir tuzak var:

<?php
// YANLIŞ KULLANIM - Bu çalışmaz!
class BenimEklentim {
    public function __construct() {
        register_activation_hook( __FILE__, array( $this, 'activate' ) );
    }

    public function activate() {
        // Bu fonksiyon ÇALIŞMAZ çünkü __FILE__ yanlış dosyayı gösteriyor
    }
}

Doğru kullanım şöyle olmalı. Hook kaydını ana eklenti dosyasından yapın, sınıf içinden değil:

<?php
// my-plugin.php (ana dosya)

require_once plugin_dir_path( __FILE__ ) . 'includes/class-benim-eklentim.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-activator.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-deactivator.php';

register_activation_hook( __FILE__, array( 'Benim_Eklentim_Activator', 'activate' ) );
register_deactivation_hook( __FILE__, array( 'Benim_Eklentim_Deactivator', 'deactivate' ) );

// includes/class-activator.php
class Benim_Eklentim_Activator {
    public static function activate() {
        global $wpdb;

        $tablo_adi = $wpdb->prefix . 'eklenti_loglar';
        $charset_collate = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE $tablo_adi (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            mesaj text NOT NULL,
            seviye varchar(20) NOT NULL DEFAULT 'info',
            olusturma_tarihi datetime NOT NULL,
            PRIMARY KEY (id)
        ) $charset_collate;";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta( $sql );

        update_option( 'benim_eklentim_version', BENIM_EKLENTIM_VERSION );
    }
}

// includes/class-deactivator.php
class Benim_Eklentim_Deactivator {
    public static function deactivate() {
        wp_clear_scheduled_hook( 'benim_eklentim_cron' );
        flush_rewrite_rules();
    }
}

Veritabanı Güncelleme Senaryosu

Eklentinizin yeni sürümünde veritabanı şemasını değiştirmeniz gerektiğinde ne yapacaksınız? Bu gerçek hayatta çok sık karşılaşılan bir senaryo. Bunun için versiyon kontrolü yapın:

function benim_eklentim_veritabani_guncelle() {
    $mevcut_versiyon = get_option( 'benim_eklentim_db_version', '0' );
    $hedef_versiyon = '2.0';

    if ( version_compare( $mevcut_versiyon, $hedef_versiyon, '>=' ) ) {
        return; // Güncelleme gerekmiyor
    }

    global $wpdb;
    $tablo_adi = $wpdb->prefix . 'eklenti_loglar';

    // 1.0'dan 2.0'a geçiş: yeni sütun ekle
    if ( version_compare( $mevcut_versiyon, '2.0', '<' ) ) {
        $wpdb->query(
            "ALTER TABLE $tablo_adi 
             ADD COLUMN kullanici_adi varchar(60) NOT NULL DEFAULT '' 
             AFTER kullanici_id"
        );
    }

    update_option( 'benim_eklentim_db_version', $hedef_versiyon );
}

// Bu fonksiyonu plugins_loaded hook'una bağla, aktivasyon hook'una değil
add_action( 'plugins_loaded', 'benim_eklentim_veritabani_guncelle' );

Bu pattern çok önemli. Veritabanı güncellemelerini aktivasyon hook’una değil, plugins_loaded hook’una bağlayın. Çünkü aktivasyon hook’u sadece eklenti aktive edildiğinde çalışır, sürüm güncellemelerinde çalışmaz.

Güvenlik Kontrolleri

Hook’lar içinde de güvenlik kontrolü yapmayı unutmayın. Özellikle doğrudan çağrılma ihtimaline karşı:

function benim_eklentim_activate() {
    // Sadece admin kullanıcılar aktive edebilir
    if ( ! current_user_can( 'activate_plugins' ) ) {
        wp_die(
            __( 'Bu işlem için yetkiniz yok.', 'benim-eklentim' ),
            __( 'Yetki Hatası', 'benim-eklentim' ),
            array( 'back_link' => true )
        );
    }

    // PHP versiyon kontrolü
    if ( version_compare( PHP_VERSION, '7.4', '<' ) ) {
        wp_die(
            sprintf(
                __( 'Bu eklenti PHP 7.4 veya üstü gerektirir. Mevcut PHP versiyonunuz: %s', 'benim-eklentim' ),
                PHP_VERSION
            )
        );
    }

    // WordPress versiyon kontrolü
    global $wp_version;
    if ( version_compare( $wp_version, '5.8', '<' ) ) {
        wp_die(
            __( 'Bu eklenti WordPress 5.8 veya üstü gerektirir.', 'benim-eklentim' )
        );
    }

    // Bağımlı eklenti kontrolü
    if ( ! is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
        wp_die(
            __( 'Bu eklenti WooCommerce gerektirir. Lütfen önce WooCommerce'i aktive edin.', 'benim-eklentim' )
        );
    }

    // Asıl aktivasyon işlemleri...
    benim_eklentim_tablolari_olustur();
    benim_eklentim_varsayilanlari_kaydet();
    flush_rewrite_rules();
}

Multisite Desteği

WordPress Multisite kurulumlarında aktivasyon hook’u ağ genelinde mi yoksa sadece aktif sitede mi çalışacak? Bunu kontrol etmeniz lazım:

function benim_eklentim_activate( $network_wide ) {
    if ( is_multisite() && $network_wide ) {
        // Ağdaki tüm sitelerde aktivasyon yap
        $siteler = get_sites( array(
            'fields' => 'ids',
            'number' => 0, // Tümünü al
        ) );

        foreach ( $siteler as $site_id ) {
            switch_to_blog( $site_id );
            benim_eklentim_tek_site_aktivasyon();
            restore_current_blog();
        }
    } else {
        // Sadece mevcut sitede aktivasyon yap
        benim_eklentim_tek_site_aktivasyon();
    }
}

function benim_eklentim_tek_site_aktivasyon() {
    global $wpdb;

    $charset_collate = $wpdb->get_charset_collate();
    $tablo_adi = $wpdb->prefix . 'eklenti_loglar';

    $sql = "CREATE TABLE $tablo_adi (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        mesaj text NOT NULL,
        PRIMARY KEY (id)
    ) $charset_collate;";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( $sql );

    add_option( 'benim_eklentim_aktif', true );
}

register_activation_hook( __FILE__, 'benim_eklentim_activate' );

$network_wide parametresinin register_activation_hook tarafından otomatik olarak geçirildiğine dikkat edin. Bunu fonksiyon imzasına eklemeniz yeterli.

Sık Yapılan Hatalar

Yıllarca kod incelemesi yaparak gördüğüm en yaygın hatalar şunlar:

  • Yanlış dosya referansı: Hook’u alt dizindeki bir dosyadan kaydetmek. __FILE__ o dosyayı gösterir, ana eklenti dosyasını değil.
  • dbDelta yerine direkt SQL: CREATE TABLE IF NOT EXISTS kullanmak güncelleme senaryolarında işe yaramaz.
  • Deaktivasyon’da veri silmek: Kullanıcı eklentiyi yeniden aktive ettiğinde tüm ayarları sıfırdan girmek zorunda kalır.
  • Flush rewrite rules eksikliği: Custom post type’lar 404 verir.
  • Cron temizliği yapmamak: Deaktive edilen eklentinin cron’ları çalışmaya devam eder, hata logları dolar.
  • Multisite desteğini atlamak: Ağ genelinde aktive edilen eklenti sadece ana sitede çalışır.

Sonuç

Aktivasyon ve deaktivasyon hook’ları, bir eklentinin “iyi vatandaş” olup olmadığını belirleyen en önemli mekanizmalar. Bunları doğru uygulamak kullanıcı deneyimini doğrudan etkiliyor. Eklentiniz aktive edildiğinde her şey yerli yerinde olmalı, deaktive edildiğinde sistem temiz kalmalı, silindiğinde ise hiçbir iz bırakmamalı.

Bu yazıda ele aldığımız konuları özetleyecek olursak:

  • register_activation_hook her zaman ana eklenti dosyasından kayıt edilmeli
  • Aktivasyonda tablo oluşturma, varsayılan ayarlar ve cron planlaması yapılmalı
  • Deaktivasyonda sadece cron ve geçici veriler temizlenmeli, kalıcı veriler dokunulmamalı
  • Kalıcı temizlik için uninstall.php dosyası kullanılmalı
  • Veritabanı şema güncellemeleri için versiyon kontrolü ve plugins_loaded hook’u tercih edilmeli
  • OOP yapıda hook’ları ana dosyadan kaydetmek şart
  • Multisite kurulumları için $network_wide parametresi kontrol edilmeli

Bu temeli sağlam attığınızda eklentiniz hem daha güvenilir çalışır hem de WordPress Eklenti Dizini gereksinimlerini karşılar. Geri kalanı zaten sizin iş mantığınız.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir