WordPress Çerez Bildirimi Ekleme: GDPR Uyumu İçin functions.php Kullanımı

Avrupa’dan kullanıcı alan her WordPress sitesi için GDPR uyumu artık bir tercih değil, zorunluluk. 2018’den bu yana yürürlükte olan bu yönetmelik, ziyaretçilerin verilerinin nasıl toplandığı konusunda açık bir bildirim yapılmasını şart koşuyor. Peki bu işi eklenti olmadan, sadece functions.php üzerinden nasıl hallederiz? İşte tam olarak bunu konuşacağız.

GDPR Çerez Bildirimi Nedir, Neden Önemli?

GDPR (General Data Protection Regulation), kısaca kullanıcıların verilerinin toplanması, işlenmesi ve saklanması konusunda şeffaflık talep eden bir AB yönetmeliği. Çerezler de bu kapsama giriyor çünkü oturum bilgisi, analitik verisi, reklam takibi gibi kullanıcıya ait verileri barındırıyorlar.

Hukuki boyutunu bir kenara bırakırsak, pratik açıdan şunu bilmemiz gerekiyor: Eğer sitenizde Google Analytics, Facebook Pixel, reklam ağları ya da herhangi bir üçüncü taraf script varsa, ziyaretçinizin bunu bilmesi ve onay vermesi gerekiyor. Aksi takdirde teorik olarak para cezasıyla karşılaşabilirsiniz. Türkiye’de KVKK da benzer gereklilikleri kapsıyor.

Büyük eklentiler bu işi çözüyor ama her eklenti bir yük demek. Sayfanıza ek HTTP isteği, ek veritabanı sorgusu ve bazen gereksiz bloat. Eğer ihtiyacınız temel bir çerez bildirimi bannerı ise, bunu tamamen functions.php ve birkaç satır CSS/JS ile kendiniz yapabilirsiniz.

Temel Yaklaşım: functions.php ile Çerez Bildirimi

WordPress’in functions.php dosyası, tema veya child tema içindeki en güçlü silahımız. Buraya eklediğimiz hooklar ve fonksiyonlar, eklenti olmadan site genelinde çalışır.

Çerez bildirimi sistemimiz şu bileşenlerden oluşacak:

  • Banner HTML: Sayfanın altında veya üstünde görünen bildirim alanı
  • CSS stilleri: Banner’ın görünümü
  • JavaScript: Kullanıcı onayını çerez olarak kaydetmek ve banner’ı gizlemek
  • PHP mantığı: Onay verildiyse banner’ı göstermemek
  • Admin paneli ayarı: Banner metnini dinamik tutmak

Adım 1: Temel Banner HTML ve PHP Fonksiyonu

İlk olarak banner’ı çıktılayacak fonksiyonu yazalım. Bu fonksiyonu wp_footer hookuna bağlayacağız ki her sayfada çalışsın.

// functions.php

function gdpr_cookie_notice_html() {
    // Zaten onay verildiyse hiçbir şey çıktılama
    if ( isset( $_COOKIE['gdpr_cookie_consent'] ) && $_COOKIE['gdpr_cookie_consent'] === 'accepted' ) {
        return;
    }

    $notice_text = get_option( 'gdpr_notice_text', 'Bu site, deneyiminizi iyileştirmek için çerezler kullanmaktadır. Siteyi kullanmaya devam ederek çerez politikamızı kabul etmiş olursunuz.' );
    $policy_url  = get_option( 'gdpr_policy_url', '/gizlilik-politikasi/' );
    $button_text = get_option( 'gdpr_button_text', 'Kabul Et' );
    $reject_text = get_option( 'gdpr_reject_text', 'Reddet' );

    ?>
    <div id="gdpr-cookie-notice" style="display:none;" role="dialog" aria-label="Çerez Bildirimi" aria-live="polite">
        <div class="gdpr-notice-inner">
            <p class="gdpr-notice-text">
                <?php echo wp_kses_post( $notice_text ); ?>
                <?php if ( ! empty( $policy_url ) ) : ?>
                    <a href="<?php echo esc_url( $policy_url ); ?>" target="_blank">Gizlilik Politikası</a>
                <?php endif; ?>
            </p>
            <div class="gdpr-notice-buttons">
                <button id="gdpr-accept-btn" class="gdpr-btn gdpr-btn-accept">
                    <?php echo esc_html( $button_text ); ?>
                </button>
                <button id="gdpr-reject-btn" class="gdpr-btn gdpr-btn-reject">
                    <?php echo esc_html( $reject_text ); ?>
                </button>
            </div>
        </div>
    </div>
    <?php
}
add_action( 'wp_footer', 'gdpr_cookie_notice_html', 100 );

Burada dikkat etmemiz gereken birkaç nokta var. wp_kses_post() kullanarak XSS açıklarının önüne geçiyoruz. esc_url() ile URL güvenliğini sağlıyoruz. esc_html() ile buton metinlerini temizliyoruz. Bu üç fonksiyon WordPress güvenlik pratiğinin temel taşları.

Adım 2: CSS Stillerini Enqueue Etmek

Satır içi CSS kullanmak yerine düzgün bir şekilde enqueue yapalım. Bu hem performans hem de override kolaylığı açısından daha doğru.

// functions.php - CSS enqueue fonksiyonu

function gdpr_enqueue_styles() {
    // Zaten onay verildiyse CSS'i de yükleme
    if ( isset( $_COOKIE['gdpr_cookie_consent'] ) && $_COOKIE['gdpr_cookie_consent'] === 'accepted' ) {
        return;
    }

    $custom_css = "
        #gdpr-cookie-notice {
            position: fixed;
            bottom: 0;
            left: 0;
            right: 0;
            background: #2c3e50;
            color: #ecf0f1;
            padding: 16px 24px;
            z-index: 999999;
            box-shadow: 0 -2px 10px rgba(0,0,0,0.3);
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            font-size: 14px;
            line-height: 1.5;
        }
        .gdpr-notice-inner {
            max-width: 1200px;
            margin: 0 auto;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 16px;
            flex-wrap: wrap;
        }
        .gdpr-notice-text {
            margin: 0;
            flex: 1;
            min-width: 200px;
        }
        .gdpr-notice-text a {
            color: #3498db;
            text-decoration: underline;
        }
        .gdpr-notice-buttons {
            display: flex;
            gap: 8px;
            flex-shrink: 0;
        }
        .gdpr-btn {
            padding: 8px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 600;
            transition: background 0.2s ease;
        }
        .gdpr-btn-accept {
            background: #27ae60;
            color: #fff;
        }
        .gdpr-btn-accept:hover {
            background: #229954;
        }
        .gdpr-btn-reject {
            background: transparent;
            color: #bdc3c7;
            border: 1px solid #7f8c8d;
        }
        .gdpr-btn-reject:hover {
            background: #34495e;
        }
        @media (max-width: 600px) {
            .gdpr-notice-inner {
                flex-direction: column;
            }
            .gdpr-notice-buttons {
                width: 100%;
            }
            .gdpr-btn {
                flex: 1;
            }
        }
    ";

    wp_register_style( 'gdpr-cookie-notice', false );
    wp_enqueue_style( 'gdpr-cookie-notice' );
    wp_add_inline_style( 'gdpr-cookie-notice', $custom_css );
}
add_action( 'wp_enqueue_scripts', 'gdpr_enqueue_styles' );

wp_add_inline_style() fonksiyonu, harici bir CSS dosyası oluşturmak zorunda kalmadan stilleri doğrudan head’e enjekte etmemizi sağlıyor. Küçük projeler için mükemmel bir yaklaşım.

Adım 3: JavaScript ile Kullanıcı Etkileşimi

Şimdi en kritik kısma geliyoruz. JavaScript, kullanıcının “Kabul Et” ya da “Reddet” butonuna tıkladığında çerezi kaydetmeli ve banner’ı gizlemeli.

// functions.php - JavaScript enqueue fonksiyonu

function gdpr_enqueue_scripts() {
    if ( isset( $_COOKIE['gdpr_cookie_consent'] ) ) {
        return;
    }

    $cookie_expiry_days = intval( get_option( 'gdpr_cookie_expiry', 365 ) );

    $inline_script = "
        (function() {
            function setCookie(name, value, days) {
                var expires = '';
                if (days) {
                    var date = new Date();
                    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
                    expires = '; expires=' + date.toUTCString();
                }
                document.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=/; SameSite=Lax; Secure';
            }

            function hideBanner() {
                var banner = document.getElementById('gdpr-cookie-notice');
                if (banner) {
                    banner.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
                    banner.style.opacity = '0';
                    banner.style.transform = 'translateY(100%)';
                    setTimeout(function() {
                        banner.style.display = 'none';
                    }, 400);
                }
            }

            document.addEventListener('DOMContentLoaded', function() {
                var banner = document.getElementById('gdpr-cookie-notice');
                if (banner) {
                    // Kısa gecikmeyle göster (sayfa yüklenmesini bekle)
                    setTimeout(function() {
                        banner.style.display = 'block';
                        banner.style.opacity = '0';
                        banner.style.transform = 'translateY(100%)';
                        banner.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
                        setTimeout(function() {
                            banner.style.opacity = '1';
                            banner.style.transform = 'translateY(0)';
                        }, 50);
                    }, 800);
                }

                var acceptBtn = document.getElementById('gdpr-accept-btn');
                if (acceptBtn) {
                    acceptBtn.addEventListener('click', function() {
                        setCookie('gdpr_cookie_consent', 'accepted', " . $cookie_expiry_days . ");
                        hideBanner();
                        // Opsiyonel: Analitik scriptleri burada yükleyebilirsiniz
                        if (typeof window.gdprAcceptCallback === 'function') {
                            window.gdprAcceptCallback();
                        }
                    });
                }

                var rejectBtn = document.getElementById('gdpr-reject-btn');
                if (rejectBtn) {
                    rejectBtn.addEventListener('click', function() {
                        setCookie('gdpr_cookie_consent', 'rejected', " . $cookie_expiry_days . ");
                        hideBanner();
                    });
                }
            });
        })();
    ";

    wp_register_script( 'gdpr-cookie-notice', false, array(), null, true );
    wp_enqueue_script( 'gdpr-cookie-notice' );
    wp_add_inline_script( 'gdpr-cookie-notice', $inline_script );
}
add_action( 'wp_enqueue_scripts', 'gdpr_enqueue_scripts' );

SameSite=Lax ve Secure direktiflerini çerezimize ekliyoruz. Bu modern tarayıcı güvenlik gerekliliklerine uyum sağlıyor. window.gdprAcceptCallback hook’u ise ileride genişletme yapmak isteyenler için bir kapı aralıyor.

Adım 4: Analitik Scriptleri Koşullu Yükleme

GDPR’ın özü şu: kullanıcı onay vermeden tracking scriptleri yüklenmemeli. Google Analytics veya benzer bir şey kullanıyorsanız, bunu onaya bağlamak gerekiyor.

// functions.php - Koşullu Analytics yükleme

function gdpr_load_analytics() {
    // Onay verilmemişse veya reddedildiyse yükleme
    if ( ! isset( $_COOKIE['gdpr_cookie_consent'] ) || $_COOKIE['gdpr_cookie_consent'] !== 'accepted' ) {
        return;
    }

    $ga_id = get_option( 'gdpr_ga_tracking_id', '' );
    if ( empty( $ga_id ) ) {
        return;
    }

    $ga_id = sanitize_text_field( $ga_id );
    ?>
    <!-- Google Analytics - Sadece onay sonrası yüklenir -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=<?php echo esc_attr( $ga_id ); ?>"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        gtag('config', '<?php echo esc_js( $ga_id ); ?>', {
            'anonymize_ip': true
        });
    </script>
    <?php
}
add_action( 'wp_head', 'gdpr_load_analytics', 5 );

anonymize_ip: true ayarı, IP adresini anonim hale getiriyor. Bu GDPR uyumu için ekstra bir katman sağlıyor. Google Analytics 4 varsayılan olarak IP’yi anonimleştirse de eski GA kurulumları için bu satır kritik.

Adım 5: Admin Paneli Ayarları

Müşteriye site teslim ediyorsanız veya metni sık güncelleyecekseniz, bir ayar sayfası hayat kurtarır. Basit bir Settings API entegrasyonu yapalım.

// functions.php - Admin ayarları

function gdpr_admin_settings_init() {
    add_settings_section(
        'gdpr_settings_section',
        'Çerez Bildirimi Ayarları',
        null,
        'gdpr-settings'
    );

    $fields = array(
        'gdpr_notice_text'    => 'Bildirim Metni',
        'gdpr_policy_url'     => 'Gizlilik Politikası URL',
        'gdpr_button_text'    => 'Kabul Butonu Metni',
        'gdpr_reject_text'    => 'Reddet Butonu Metni',
        'gdpr_ga_tracking_id' => 'Google Analytics ID',
        'gdpr_cookie_expiry'  => 'Çerez Süresi (Gün)',
    );

    foreach ( $fields as $id => $label ) {
        register_setting( 'gdpr-settings', $id, array(
            'sanitize_callback' => 'sanitize_text_field',
        ) );
        add_settings_field(
            $id,
            $label,
            'gdpr_settings_field_callback',
            'gdpr-settings',
            'gdpr_settings_section',
            array( 'id' => $id )
        );
    }
}
add_action( 'admin_init', 'gdpr_admin_settings_init' );

function gdpr_settings_field_callback( $args ) {
    $value = get_option( $args['id'], '' );
    echo '<input type="text" name="' . esc_attr( $args['id'] ) . '" value="' . esc_attr( $value ) . '" class="regular-text">';
}

function gdpr_admin_menu() {
    add_options_page(
        'Çerez Bildirimi',
        'Çerez Bildirimi',
        'manage_options',
        'gdpr-settings',
        'gdpr_settings_page_html'
    );
}
add_action( 'admin_menu', 'gdpr_admin_menu' );

function gdpr_settings_page_html() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    ?>
    <div class="wrap">
        <h1>Çerez Bildirimi Ayarları</h1>
        <form method="post" action="options.php">
            <?php
            settings_fields( 'gdpr-settings' );
            do_settings_sections( 'gdpr-settings' );
            submit_button( 'Ayarları Kaydet' );
            ?>
        </form>
    </div>
    <?php
}

Artık Ayarlar > Çerez Bildirimi menüsünden tüm metinleri yönetebilirsiniz. Müşteriye “functions.php’e gir ve şu satırı değiştir” demek yerine temiz bir arayüz sunmuş oluyorsunuz.

Adım 6: Onay Durumuna Göre İçerik Kontrolü

Bazı senaryolarda belirli embed’leri (YouTube, Google Maps) onay olmadan yüklemek istemeyebilirsiniz. Bunun için bir yardımcı fonksiyon hazırlayalım.

// functions.php - Çerez onay kontrol yardımcı fonksiyonları

function gdpr_is_consent_given() {
    return isset( $_COOKIE['gdpr_cookie_consent'] ) && $_COOKIE['gdpr_cookie_consent'] === 'accepted';
}

function gdpr_is_consent_rejected() {
    return isset( $_COOKIE['gdpr_cookie_consent'] ) && $_COOKIE['gdpr_cookie_consent'] === 'rejected';
}

function gdpr_is_consent_pending() {
    return ! isset( $_COOKIE['gdpr_cookie_consent'] );
}

// Shortcode: İçeriği sadece onay sonrası göster
function gdpr_consent_required_shortcode( $atts, $content = null ) {
    $atts = shortcode_atts( array(
        'message' => 'Bu içeriği görüntülemek için çerez iznini kabul etmelisiniz.',
    ), $atts, 'gdpr_content' );

    if ( gdpr_is_consent_given() ) {
        return do_shortcode( $content );
    }

    return '<div class="gdpr-blocked-content" style="background:#f8f9fa;border:1px solid #dee2e6;padding:20px;text-align:center;border-radius:4px;">'
        . '<p>' . esc_html( $atts['message'] ) . '</p>'
        . '</div>';
}
add_shortcode( 'gdpr_content', 'gdpr_consent_required_shortcode' );

Bu shortcode’u template’lerinizde veya Gutenberg’de şu şekilde kullanabilirsiniz:

[gdpr_content message="Haritayı görüntülemek için çerezleri kabul edin."]
    <iframe src="https://maps.google.com/..."></iframe>
[/gdpr_content]

Adım 7: WooCommerce Entegrasyonu

WooCommerce kullanan siteler için ekstra bir durum var. Sepet ve ödeme sayfalarında oturum çerezleri zaten zorunlu teknik çerezlerdir ve bunlar için onay gerekmez. Ancak reklam pixel’leri için onay gereklidir. Bu ayrımı kodumuzda da yansıtalım.

// functions.php - WooCommerce ile GDPR entegrasyonu

function gdpr_woo_disable_tracking_pixels() {
    // Reklam pixel'lerini sadece onay verildiyse yükle
    if ( ! gdpr_is_consent_given() ) {
        // WooCommerce tracking'i devre dışı bırak
        add_filter( 'woocommerce_google_analytics_active', '__return_false' );
        // Facebook Pixel varsa devre dışı bırak
        add_filter( 'facebook_for_woocommerce_pixel_enabled', '__return_false' );
        return;
    }
}
add_action( 'init', 'gdpr_woo_disable_tracking_pixels' );

// Ödeme sayfasında banner'ı gizle (UX iyileştirmesi)
function gdpr_hide_notice_on_checkout() {
    if ( function_exists( 'is_checkout' ) && is_checkout() ) {
        // Ödeme akışını bozmamak için banner'ı otomatik kabul et
        // NOT: Bu karar hukuki danışmanınızla teyit edilmeli
        add_filter( 'gdpr_force_hide_banner', '__return_true' );
    }
}
add_action( 'wp', 'gdpr_hide_notice_on_checkout' );

Checkout sayfasında banner’ı gizlemek tartışmalı bir karar, hukuki boyutuna dikkat edin. Bazı uygulamalar checkout akışına müdahale etmeden banner’ı göstermeye devam eder.

Gerçek Dünya Senaryoları

Senaryo 1: Ajans Teslimi Bir müşteri sitesi teslim ediyorsunuz. Eklenti kurmak istemiyorlar çünkü “plugin sayısını azalt” diyorlar. Yukarıdaki kodu bir child tema functions.php dosyasına koyun. Ana tema güncellenince kodunuz kaybolmaz. Admin menüsü sayesinde müşteri kendi metinlerini güncelleyebilir.

Senaryo 2: Cache ile Sorun Nginx FastCGI cache veya WP Super Cache kullanıyorsanız, PHP tarafındaki $_COOKIE kontrolü anlamsız hale gelebilir çünkü sayfa cache’den gelir. Bu durumda PHP kontrolünü kaldırın ve banner gösterimi tamamen JavaScript’e bırakın. JS tarafında document.cookie kontrolü cache’den etkilenmez.

Senaryo 3: Çok Dilli Site WPML veya Polylang kullanıyorsanız, get_option() yerine çeviri fonksiyonu eklemeniz gerekir. Notice metnini direkt çeviri dosyasında (__() veya _e()) tanımlarsanız WPML ile otomatik çevirilebilir hale gelir.

Performans İpuçları

  • Cache bypass: Banner JS’ini footer‘a koyun, header‘a değil. Render-blocking olmaktan çıkar.
  • Conditional loading: is_admin() kontrolü ile admin panelinde gereksiz yüklemeyi önleyin.
  • Cookie boyutu: Çerez değerini kısa tutun (“accepted”/”rejected”), gereksiz veri saklamayın.
  • Preload: Banner’ı sayfa yüklenir yüklenmez göstermek yerine 800ms gecikme kullandık. Bu sayfa içeriğinin önce yüklenmesini sağlar, kullanıcı deneyimini iyileştirir.

Olası Sorunlar ve Çözümleri

Banner tekrar çıkıyor: SameSite=Lax; Secure direktifleri localhost’ta çalışmayabilir. Geliştirme ortamında bu direktifleri kaldırın.

PHP çerezi okuyamıyor: wp_head hookunda henüz çerez set edilmemişse $_COOKIE boş gelir. WordPress yüklenirken $_COOKIE superglobal’i PHP tarafından doldurulur, bu genellikle doğru çalışır.

IE11 uyumluluğu: Eğer hala desteklemeniz gereken kullanıcılar varsa, CSS flexbox yerine inline-block kullanmayı düşünün. Ama 2024 itibarıyla bu ihtimal giderek azalıyor.

Güvenlik Kontrol Listesi

  • wp_kses_post(): Bildirim metni için, HTML içerebilir ama güvenli filtrelenmiş olmalı
  • esc_url(): Policy URL için mutlaka kullanın
  • esc_attr(): HTML attribute içine yazılan her değer için
  • esc_js(): JavaScript içine yazılan PHP değerleri için
  • sanitize_text_field(): Settings API’de kaydettiğimiz her alanda
  • current_user_can('manage_options'): Admin sayfası erişimi için

Sonuç

Eklenti kullanmadan, sadece functions.php ile tam işlevsel, güvenli ve GDPR uyumlu bir çerez bildirimi sistemi kurabilirsiniz. Yazdığımız kod yaklaşık 200 satır ama size şunları sağladı:

  • Kullanıcı onayını çerezde saklayan banner sistemi
  • Onaya bağlı analitik yükleme
  • WooCommerce pixel entegrasyonu
  • Admin paneli üzerinden yönetilebilir ayarlar
  • Onay durumuna göre içerik kontrolü için shortcode
  • Accessibility uyumlu (ARIA attribute’ları)

Tabii ki ciddi bir e-ticaret sitesi veya çok karmaşık çerez yapısı olan bir platform için profesyonel bir çerez yönetim çözümü (Cookiebot, OneTrust gibi) düşünebilirsiniz. Ama büyük çoğunluğu temsil eden kurumsal tanıtım siteleri ve küçük WooCommerce mağazaları için bu çözüm hem performanslı hem de yeterince sağlam bir yaklaşım. “Daha az eklenti, daha fazla kontrol” prensibini seven sysadminler için biçilmiş kaftan.

Bir yanıt yazın

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