WordPress Çoklu Site Ağında Özel Fonksiyon Tanımlama

WordPress Multisite kurulumunda her sitenin kendi functions.php dosyası olduğunu biliyorsunuzdur. Ama ya onlarca siteniz varsa? Her birine ayrı ayrı aynı fonksiyonu eklemek hem zaman kaybı hem de bakım kabusu. İşte tam bu noktada multisite ağında merkezi ve özel fonksiyon tanımlama sanatına giriyoruz.

Bu yazıda gerçek dünya senaryoları üzerinden, bir WordPress Multisite ağında nasıl etkili, sürdürülebilir ve güvenli özel fonksiyonlar tanımlayabileceğinizi adım adım göstereceğim.

WordPress Multisite Ağı Mimarisini Anlamak

Fonksiyon yazmaya geçmeden önce, multisite ağının nasıl çalıştığını netleştirmek gerekiyor. WordPress Multisite kurulumunda üç farklı seviye var:

  • Network Admin Seviyesi: Tüm ağı yöneten super admin
  • Site Admin Seviyesi: Tek bir alt siteyi yöneten site yöneticisi
  • Kullanıcı Seviyesi: Normal üyeler

Fonksiyonlarınızı nereye yazdığınız, bu hiyerarşide neyi etkileyeceğinizi doğrudan belirliyor. Bir fonksiyon mu-plugins klasörüne yazıldığında tüm ağı etkilerken, bir alt sitenin functions.php dosyasına yazılan fonksiyon sadece o siteyi etkiler.

Mu-Plugins Klasörü: Ağ Geneli Fonksiyonların Evi

wp-content/mu-plugins/ klasörü, “Must-Use Plugins” anlamına gelir ve bu klasördeki PHP dosyaları otomatik olarak yüklenir, devre dışı bırakılamaz. Multisite için ideal yer burasıdır.

# mu-plugins klasörü yoksa oluşturun
mkdir -p /var/www/html/wp-content/mu-plugins

# Ağ geneli fonksiyon dosyası oluşturun
touch /var/www/html/wp-content/mu-plugins/network-functions.php

# Doğru izinleri ayarlayın
chmod 644 /var/www/html/wp-content/mu-plugins/network-functions.php
chown www-data:www-data /var/www/html/wp-content/mu-plugins/network-functions.php

Temel Yapıyı Kurmak

Her şeyden önce, fonksiyonlarınızı organize edecek bir yapı kurmanız gerekiyor. Tek bir devasa dosya yerine, modüler bir yaklaşım benimseyin.

# Organize bir klasör yapısı oluşturun
mkdir -p /var/www/html/wp-content/mu-plugins/network-core
touch /var/www/html/wp-content/mu-plugins/network-core/user-functions.php
touch /var/www/html/wp-content/mu-plugins/network-core/content-functions.php
touch /var/www/html/wp-content/mu-plugins/network-core/security-functions.php
touch /var/www/html/wp-content/mu-plugins/network-loader.php

Ana yükleyici dosyası şöyle görünmeli:

# network-loader.php içeriğini oluşturun
cat > /var/www/html/wp-content/mu-plugins/network-loader.php << 'EOF'
<?php
/**
 * Network Functions Loader
 * Tüm ağ geneli fonksiyonları yükler
 */

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

$network_modules = array(
    'user-functions',
    'content-functions',
    'security-functions',
);

foreach ( $network_modules as $module ) {
    $module_path = __DIR__ . '/network-core/' . $module . '.php';
    if ( file_exists( $module_path ) ) {
        require_once $module_path;
    }
}
EOF

Site ID’ye Göre Koşullu Fonksiyon Çalıştırma

Gerçek hayatta en sık ihtiyaç duyulan şey: “Şu fonksiyon sadece 3. sitede çalışsın.” İşte burada get_current_blog_id() devreye giriyor.

Diyelim ki 5 siteniz var ve sadece e-ticaret sitenizde (site ID: 3) özel bir stok kontrolü fonksiyonu çalışsın:

cat > /var/www/html/wp-content/mu-plugins/network-core/content-functions.php << 'EOF'
<?php
/**
 * İçerik ve site özelinde fonksiyonlar
 */

/**
 * Belirli bir site ID'si için kontrol yardımcısı
 */
function is_network_site( $site_id ) {
    return (int) get_current_blog_id() === (int) $site_id;
}

/**
 * Birden fazla site için kontrol
 */
function is_any_network_site( array $site_ids ) {
    return in_array( (int) get_current_blog_id(), array_map( 'intval', $site_ids ) );
}

// Sadece site ID 3 (e-ticaret) için stok uyarısı
add_action( 'woocommerce_single_product_summary', 'network_low_stock_notice', 25 );
function network_low_stock_notice() {
    if ( ! is_network_site( 3 ) ) {
        return;
    }

    global $product;
    if ( $product && $product->get_stock_quantity() < 5 && $product->get_stock_quantity() > 0 ) {
        echo '<div class="low-stock-warning">Stokta sadece ' 
             . esc_html( $product->get_stock_quantity() ) 
             . ' adet kaldı!</div>';
    }
}

// Site ID 1 ve 2 için özel başlık meta
add_action( 'wp_head', 'network_custom_meta_tags' );
function network_custom_meta_tags() {
    if ( ! is_any_network_site( array( 1, 2 ) ) ) {
        return;
    }
    echo '<meta name="network-group" content="primary-sites">' . "n";
}
EOF

Ağ Geneli Kullanıcı Yönetimi Fonksiyonları

Multisite’ın en güçlü özelliklerinden biri, bir kullanıcının ağdaki birden fazla siteye üye olabilmesidir. Bunu yönetmek için özel fonksiyonlar şart.

cat > /var/www/html/wp-content/mu-plugins/network-core/user-functions.php << 'EOF'
<?php
/**
 * Ağ geneli kullanıcı yönetimi fonksiyonları
 */

/**
 * Kullanıcıyı ağdaki tüm sitelere otomatik ekle
 * Senaryo: Kurumsal intranet - yeni çalışan tüm sitelere erişmeli
 */
function network_add_user_to_all_sites( $user_id, $role = 'subscriber' ) {
    if ( ! is_multisite() ) {
        return false;
    }

    $sites = get_sites( array(
        'number' => 100,
        'fields' => 'ids',
    ) );

    foreach ( $sites as $site_id ) {
        switch_to_blog( $site_id );
        
        if ( ! is_user_member_of_blog( $user_id, $site_id ) ) {
            add_user_to_blog( $site_id, $user_id, $role );
        }
        
        restore_current_blog();
    }

    return true;
}

/**
 * Yeni kullanıcı kaydında otomatik ağ üyeliği
 * Kurumsal senaryo: HR sistemi entegrasyonu
 */
add_action( 'user_register', 'network_auto_assign_new_user', 10, 1 );
function network_auto_assign_new_user( $user_id ) {
    $user = get_userdata( $user_id );
    
    // Şirket e-posta domainiyle kayıt olan kullanıcıları otomatik ekle
    $company_domain = '@sirketim.com';
    
    if ( strpos( $user->user_email, $company_domain ) !== false ) {
        network_add_user_to_all_sites( $user_id, 'editor' );
        
        // Super admin'e bildirim gönder
        $admin_email = get_site_option( 'admin_email' );
        wp_mail(
            $admin_email,
            'Yeni Kurumsal Kullanıcı',
            sprintf(
                '%s (%s) tüm ağa editor olarak eklendi.',
                $user->display_name,
                $user->user_email
            )
        );
    }
}

/**
 * Kullanıcının ağdaki tüm sitelerden rollerini al
 */
function network_get_user_roles_across_sites( $user_id ) {
    $roles = array();
    $sites = get_sites( array( 'number' => 100 ) );
    
    foreach ( $sites as $site ) {
        switch_to_blog( $site->blog_id );
        $user = new WP_User( $user_id, '', $site->blog_id );
        
        if ( ! empty( $user->roles ) ) {
            $roles[ $site->blog_id ] = array(
                'site_name' => get_bloginfo( 'name' ),
                'roles'     => $user->roles,
            );
        }
        
        restore_current_blog();
    }
    
    return $roles;
}
EOF

Güvenlik Odaklı Ağ Fonksiyonları

Güvenlik fonksiyonlarını merkezi olarak yönetmek, multisite’ın en büyük avantajlarından biri. Bir güvenlik açığı kapandığında tüm ağı tek yerden koruyorsunuz.

cat > /var/www/html/wp-content/mu-plugins/network-core/security-functions.php << 'EOF'
<?php
/**
 * Ağ geneli güvenlik fonksiyonları
 */

/**
 * Belirli IP bloklarını tüm ağda engelle
 * Senaryo: Sürekli saldırı yapan IP aralıklarını blokla
 */
add_action( 'init', 'network_block_malicious_ips', 1 );
function network_block_malicious_ips() {
    // Sadece admin ajax ve login sayfasında uygula
    if ( ! ( defined( 'DOING_AJAX' ) && DOING_AJAX ) && 
         ! strpos( $_SERVER['REQUEST_URI'], 'wp-login.php' ) ) {
        return;
    }

    $blocked_ranges = get_site_option( 'network_blocked_ip_ranges', array() );
    
    if ( empty( $blocked_ranges ) ) {
        return;
    }

    $visitor_ip = $_SERVER['REMOTE_ADDR'];
    
    foreach ( $blocked_ranges as $range ) {
        if ( network_ip_in_range( $visitor_ip, $range ) ) {
            wp_die( 
                'Erişim reddedildi.', 
                'Engellendi', 
                array( 'response' => 403 )
            );
        }
    }
}

/**
 * IP aralığı kontrolü (CIDR notasyonu destekli)
 */
function network_ip_in_range( $ip, $range ) {
    if ( strpos( $range, '/' ) === false ) {
        return $ip === $range;
    }
    
    list( $subnet, $bits ) = explode( '/', $range );
    $ip_long     = ip2long( $ip );
    $subnet_long = ip2long( $subnet );
    $mask        = -1 << ( 32 - $bits );
    $subnet_long &= $mask;
    
    return ( $ip_long & $mask ) === $subnet_long;
}

/**
 * Başarısız giriş denemelerini ağ genelinde logla
 */
add_action( 'wp_login_failed', 'network_log_failed_logins' );
function network_log_failed_logins( $username ) {
    $log_entry = array(
        'time'     => current_time( 'mysql' ),
        'username' => sanitize_text_field( $username ),
        'ip'       => $_SERVER['REMOTE_ADDR'],
        'site_id'  => get_current_blog_id(),
        'ua'       => substr( $_SERVER['HTTP_USER_AGENT'], 0, 200 ),
    );
    
    $logs = get_site_option( 'network_failed_logins', array() );
    array_unshift( $logs, $log_entry );
    
    // Son 1000 kaydı tut
    $logs = array_slice( $logs, 0, 1000 );
    update_site_option( 'network_failed_logins', $logs );
}

/**
 * Ağ genelinde dosya düzenlemeyi devre dışı bırak
 * Production ortamında kritik güvenlik önlemi
 */
add_action( 'init', 'network_disable_file_editing' );
function network_disable_file_editing() {
    if ( ! defined( 'DISALLOW_FILE_EDIT' ) ) {
        define( 'DISALLOW_FILE_EDIT', true );
    }
}
EOF

WooCommerce Multisite Entegrasyonu

Multisite üzerinde WooCommerce çalıştırıyorsanız, özellikle şu senaryo çok yaygın: Ana sitede ürün kataloğu var, alt siteler bu ürünleri kendi ağırlıklarıyla satıyor. Merkezi stok yönetimi için özel fonksiyonlar yazalım.

cat > /var/www/html/wp-content/mu-plugins/network-core/woocommerce-network.php << 'EOF'
<?php
/**
 * WooCommerce Ağ Entegrasyonu
 * Merkezi ürün kataloğu - Ana site ID: 1
 */

define( 'NETWORK_MAIN_STORE_ID', 1 );

/**
 * Ana sitedeki ürün bilgisini al
 * Senaryo: Franchise modeli - merkezi fiyatlandırma
 */
function network_get_master_product_data( $product_sku ) {
    switch_to_blog( NETWORK_MAIN_STORE_ID );
    
    $product_id = wc_get_product_id_by_sku( $product_sku );
    $data       = null;
    
    if ( $product_id ) {
        $product = wc_get_product( $product_id );
        $data    = array(
            'id'          => $product_id,
            'name'        => $product->get_name(),
            'price'       => $product->get_price(),
            'stock'       => $product->get_stock_quantity(),
            'description' => $product->get_short_description(),
            'sku'         => $product->get_sku(),
        );
    }
    
    restore_current_blog();
    
    return $data;
}

/**
 * Alt sitelerde fiyatı ana siteyle senkronize et
 * Cronjob ile tetiklenir
 */
function network_sync_prices_from_master() {
    $sites = get_sites( array(
        'number'   => 100,
        'site__not_in' => array( NETWORK_MAIN_STORE_ID ),
    ) );
    
    $synced_count = 0;
    
    foreach ( $sites as $site ) {
        switch_to_blog( $site->blog_id );
        
        if ( ! class_exists( 'WooCommerce' ) ) {
            restore_current_blog();
            continue;
        }
        
        $products = wc_get_products( array(
            'limit'  => -1,
            'status' => 'publish',
        ) );
        
        foreach ( $products as $product ) {
            $sku         = $product->get_sku();
            $master_data = network_get_master_product_data( $sku );
            
            if ( $master_data ) {
                $product->set_price( $master_data['price'] );
                $product->set_regular_price( $master_data['price'] );
                $product->save();
                $synced_count++;
            }
        }
        
        restore_current_blog();
    }
    
    // Senkronizasyon logunu güncelle
    update_site_option( 'last_price_sync', array(
        'time'  => current_time( 'mysql' ),
        'count' => $synced_count,
    ) );
    
    return $synced_count;
}

// Günlük fiyat senkronizasyonu için cron
add_action( 'network_daily_price_sync', 'network_sync_prices_from_master' );

if ( ! wp_next_scheduled( 'network_daily_price_sync' ) ) {
    wp_schedule_event( strtotime( 'midnight' ), 'daily', 'network_daily_price_sync' );
}
EOF

Network Admin Paneline Özel Sayfa Ekleme

Tüm bu fonksiyonları yönetmek için Network Admin paneline özel bir yönetim sayfası eklemek hayat kurtarır.

cat >> /var/www/html/wp-content/mu-plugins/network-loader.php << 'EOF'

/**
 * Network Admin yönetim sayfası
 */
add_action( 'network_admin_menu', 'network_functions_admin_menu' );
function network_functions_admin_menu() {
    add_menu_page(
        'Ağ Yönetimi',
        'Ağ Fonksiyonları',
        'manage_network',
        'network-functions-manager',
        'network_functions_admin_page',
        'dashicons-networking',
        30
    );
}

function network_functions_admin_page() {
    if ( ! current_user_can( 'manage_network' ) ) {
        wp_die( 'Bu sayfaya erişim yetkiniz yok.' );
    }
    
    // Form işleme
    if ( isset( $_POST['network_blocked_ips_nonce'] ) && 
         wp_verify_nonce( $_POST['network_blocked_ips_nonce'], 'update_blocked_ips' ) ) {
        
        $raw_ips = sanitize_textarea_field( $_POST['blocked_ip_ranges'] );
        $ip_list = array_filter( array_map( 'trim', explode( "n", $raw_ips ) ) );
        update_site_option( 'network_blocked_ip_ranges', $ip_list );
        echo '<div class="updated"><p>Engelli IP listesi güncellendi.</p></div>';
    }
    
    $blocked_ips  = get_site_option( 'network_blocked_ip_ranges', array() );
    $failed_logins = get_site_option( 'network_failed_logins', array() );
    $last_sync    = get_site_option( 'last_price_sync', null );
    
    ?>
    <div class="wrap">
        <h1>Ağ Fonksiyonları Yönetimi</h1>
        
        <h2>Engelli IP Aralıkları</h2>
        <form method="post">
            <?php wp_nonce_field( 'update_blocked_ips', 'network_blocked_ips_nonce' ); ?>
            <textarea name="blocked_ip_ranges" rows="10" cols="50"
                placeholder="Her satıra bir IP veya CIDR aralığı (örn: 192.168.1.0/24)"
            ><?php echo esc_textarea( implode( "n", $blocked_ips ) ); ?></textarea>
            <br>
            <input type="submit" class="button button-primary" value="Kaydet">
        </form>
        
        <h2>Son Başarısız Giriş Denemeleri (Son 10)</h2>
        <ul>
        <?php foreach ( array_slice( $failed_logins, 0, 10 ) as $log ) : ?>
            <li>
                <strong><?php echo esc_html( $log['time'] ); ?></strong> - 
                Kullanıcı: <?php echo esc_html( $log['username'] ); ?> | 
                IP: <?php echo esc_html( $log['ip'] ); ?> | 
                Site: <?php echo esc_html( $log['site_id'] ); ?>
            </li>
        <?php endforeach; ?>
        </ul>
        
        <?php if ( $last_sync ) : ?>
        <h2>Son Fiyat Senkronizasyonu</h2>
        <p>
            Zaman: <?php echo esc_html( $last_sync['time'] ); ?> | 
            Güncellenen ürün: <?php echo esc_html( $last_sync['count'] ); ?>
        </p>
        <?php endif; ?>
    </div>
    <?php
}
EOF

Dosya İzinleri ve Güvenlik Kontrolleri

Tüm bu dosyaları yerleştirdikten sonra izinleri doğru ayarlamak kritik:

# Tüm mu-plugins dosyaları için doğru izinler
find /var/www/html/wp-content/mu-plugins -type f -name "*.php" -exec chmod 644 {} ;
find /var/www/html/wp-content/mu-plugins -type d -exec chmod 755 {} ;

# Sahipliği web sunucusu kullanıcısına ver
chown -R www-data:www-data /var/www/html/wp-content/mu-plugins/

# Yazma izinlerini sadece gerekli dizinlere ver
# mu-plugins klasörünün root'una yazma izni verme!
chmod 755 /var/www/html/wp-content/mu-plugins/

# Syntax hatası kontrolü yap
php -l /var/www/html/wp-content/mu-plugins/network-loader.php
php -l /var/www/html/wp-content/mu-plugins/network-core/user-functions.php
php -l /var/www/html/wp-content/mu-plugins/network-core/security-functions.php
php -l /var/www/html/wp-content/mu-plugins/network-core/content-functions.php

# WP-CLI ile fonksiyonların yüklenip yüklenmediğini test et
wp --url=https://siteniz.com eval 'echo function_exists("network_add_user_to_all_sites") ? "OK" : "FAIL";'

Yaygın Hatalar ve Çözümleri

Multisite fonksiyon geliştirirken sıkça karşılaşılan sorunlar:

  • switch_to_blog() sonrası restore_current_blog() unutmak: Her switch_to_blog() çağrısından sonra mutlaka restore_current_blog() kullanın. Aksi halde sonraki database sorguları yanlış blog context’inde çalışır.
  • get_option() yerine get_site_option() kullanmamak: Ağ geneli ayarlar için get_site_option() ve update_site_option() kullanın. get_option() sadece aktif sitenin ayarlarını okur.
  • current_user_can() kontrolleri eksik bırakmak: Network admin fonksiyonlarında mutlaka manage_network yetki kontrolü yapın.
  • WP_User nesnesini yanlış blog context’inde oluşturmak: WP_User($user_id, '', $blog_id) şeklinde blog ID’yi açıkça belirtin.
  • Cron olaylarını her sayfa yüklemesinde tekrar kaydetmeye çalışmak: Her zaman wp_next_scheduled() ile kontrol edin, zaten kayıtlıysa tekrar eklemeyin.
  • Büyük ağlarda get_sites() limit parametresini unutmak: Varsayılan limit 100’dür, daha büyük ağlarda 'number' => -1 veya sayfalama kullanın.

Sonuç

WordPress Multisite ağında merkezi fonksiyon yönetimi, başlangıçta karmaşık görünse de doğru yapılandırıldığında inanılmaz zaman ve emek tasarrufu sağlıyor. mu-plugins klasörünü modüler bir yapıyla kullanmak, switch_to_blog() ve restore_current_blog() ikilisine dikkat etmek, ağ geneli ayarlar için site_option fonksiyonlarını tercih etmek, temel başarı kriterleriniz bunlar.

Özellikle 10’un üzerinde siteye sahip bir ağ yönetiyorsanız, bu yazıdaki yaklaşımı benimsemek ilk haftadan itibaren fark yaratacaktır. Güvenlik güncellemelerini tek bir dosyadan yapabilmek, kullanıcı yönetimini merkezi hale getirmek ve WooCommerce fiyatlarını otomatik senkronize etmek, multisite’ın sunduğu en güçlü avantajlar.

Kendi ağınızda bu yapıyı kurarken önce test ortamında deneyin, php -l ile syntax kontrolü yapın ve her önemli değişiklik öncesinde veritabanı yedeği almayı ihmal etmeyin.

Bir yanıt yazın

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