İçerik Kopyalamayı Engelleyen WordPress Eklentisi Nasıl Yapılır

Bir müşteri geçen ay beni aradı, sesi sinirli: “Hocam, rakiplerimiz ürün açıklamalarımızı kopyalıyor, ne yapabiliriz?” dedi. WordPress sitelerinde içerik hırsızlığı gerçekten ciddi bir sorun ve çoğu hazır eklenti ya çok şişirilmiş ya da tam istediğinizi yapmıyor. O gün ona “Bak, bunu kendimiz yazalım” dedim ve birlikte sıfırdan bir içerik koruma eklentisi geliştirdik. Bugün o süreci adım adım aktaracağım.

Eklentinin Yapısı ve Temel Felsefesi

Özel bir WordPress eklentisi yazmadan önce ne yapmak istediğimizi netleştirmek gerekiyor. Bu eklentide şunları hedefliyoruz:

  • Sağ tıklamayı devre dışı bırakmak
  • Metin seçimini engellemek
  • Klavye kısayollarını (Ctrl+C, Ctrl+A, Ctrl+U, F12) bloke etmek
  • Sadece belirli post type’larda veya sayfalarda aktif olmasını sağlamak
  • Yönetici kullanıcıları bu kısıtlamalardan muaf tutmak
  • Yönetim panelinden kolayca yapılandırılabilmesini sağlamak

Önemli bir not: Bu önlemler %100 koruma sağlamaz. Kararlı biri DevTools’u başka yollarla açabilir veya sayfa kaynağını görüntüleyebilir. Ama bu eklenti %80 oranındaki “hızlı kopyalayıp yapıştır” vakasını engeller; rakibinizin hazır bir scripti yoksa işe yarar.

Eklenti Dosya Yapısını Oluşturmak

WordPress eklentileri /wp-content/plugins/ dizini altında yaşar. Kendi klasörümüzü oluşturalım:

mkdir /var/www/html/wp-content/plugins/icerik-koruma
cd /var/www/html/wp-content/plugins/icerik-koruma
touch icerik-koruma.php
mkdir assets
touch assets/koruma.js
touch assets/koruma.css

Eklentinin ana PHP dosyasını açıp header bilgilerini ekleyelim. WordPress bu yorumları okuyarak eklentiyi tanır:

cat > icerik-koruma.php << 'EOF'
<?php
/**
 * Plugin Name: İçerik Koruma
 * Plugin URI: https://siteniz.com
 * Description: Ziyaretçilerin içerik kopyalamasını engelleyen hafif bir koruma katmanı.
 * Version: 1.0.0
 * Author: Siteniz
 * Author URI: https://siteniz.com
 * License: GPL v2 or later
 * Text Domain: icerik-koruma
 */

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

ABSPATH kontrolü önemli. Bu satır olmadan dosyayı doğrudan URL ile çağıran birisi PHP kodunuzu tetikleyebilir. Her eklentide bu kontrol olmalı.

Ana PHP Sınıfını Yazmak

Eklentiyi nesne yönelimli yazacağız. Global fonksiyonlar yerine bir sınıf kullanmak, isim çakışmalarını önler ve kodu daha yönetilebilir kılar. icerik-koruma.php dosyasını düzenlemeye devam ediyoruz:

cat >> icerik-koruma.php << 'EOF'

class Icerik_Koruma {

    private static $instance = null;
    private $options;

    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        $this->options = get_option( 'icerik_koruma_ayarlar', array(
            'aktif'           => '1',
            'sag_tikla'       => '1',
            'metin_secim'     => '1',
            'klavye'          => '1',
            'admin_muaf'      => '1',
            'post_tipleri'    => array( 'post', 'page', 'product' ),
            'uyari_mesaji'    => 'Bu içerik koruma altındadır.',
        ) );

        add_action( 'wp_enqueue_scripts', array( $this, 'scriptleri_yukle' ) );
        add_action( 'admin_menu', array( $this, 'admin_menu_ekle' ) );
        add_action( 'admin_init', array( $this, 'ayarlari_kaydet' ) );
    }

    public function koruma_aktif_mi() {
        if ( $this->options['aktif'] !== '1' ) {
            return false;
        }
        if ( $this->options['admin_muaf'] === '1' && current_user_can( 'administrator' ) ) {
            return false;
        }
        if ( ! is_singular() ) {
            return false;
        }
        $mevcut_tip = get_post_type();
        $hedef_tipler = (array) $this->options['post_tipleri'];
        if ( ! in_array( $mevcut_tip, $hedef_tipler, true ) ) {
            return false;
        }
        return true;
    }
}

Icerik_Koruma::get_instance();
EOF

Singleton pattern burada mantıklı çünkü eklentinin birden fazla kez örneklenmesine gerek yok. koruma_aktif_mi() metodu ise her script yükleme kararından önce çağrılacak; gereksiz yere her sayfaya JS yüklemiyoruz.

JavaScript Koruma Scriptini Yazmak

Asıl iş JavaScript tarafında oluyor. assets/koruma.js dosyasını oluşturalım:

cat > assets/koruma.js << 'EOF'
(function($) {
    'use strict';

    var IcerikKoruma = {

        ayarlar: typeof icerikKorumaAyarlar !== 'undefined' ? icerikKorumaAyarlar : {},

        init: function() {
            if (this.ayarlar.sagTikla === '1') {
                this.sagTiklaEngelleA();
            }
            if (this.ayarlar.metinSecim === '1') {
                this.metinSecimEngelleA();
            }
            if (this.ayarlar.klavye === '1') {
                this.klavyeEngelleA();
            }
        },

        sagTiklaEngelleA: function() {
            $(document).on('contextmenu', function(e) {
                e.preventDefault();
                IcerikKoruma.uyariGoster();
                return false;
            });
        },

        metinSecimEngelleA: function() {
            $(document).on('selectstart dragstart', function(e) {
                e.preventDefault();
                return false;
            });
        },

        klavyeEngelleA: function() {
            $(document).on('keydown', function(e) {
                var engelliKombinler = [
                    { ctrl: true, key: 'c' },
                    { ctrl: true, key: 'a' },
                    { ctrl: true, key: 'u' },
                    { ctrl: true, key: 's' },
                    { ctrl: true, key: 'p' },
                    { key: 'F12' },
                    { key: 'F5', ctrl: true }
                ];

                for (var i = 0; i < engelliKombinler.length; i++) {
                    var kombin = engelliKombinler[i];
                    var eslesme = true;

                    if (kombin.ctrl !== undefined && kombin.ctrl !== e.ctrlKey) {
                        eslesme = false;
                    }
                    if (kombin.key && e.key !== kombin.key &&
                        e.key.toLowerCase() !== kombin.key.toLowerCase()) {
                        eslesme = false;
                    }

                    if (eslesme) {
                        e.preventDefault();
                        if (kombin.key !== 'F12') {
                            IcerikKoruma.uyariGoster();
                        }
                        return false;
                    }
                }
            });
        },

        uyariGoster: function() {
            var mesaj = this.ayarlar.uyariMesaji || 'Bu içerik koruma altındadır.';
            if ($('#ik-uyari-kutusu').length === 0) {
                $('body').append(
                    '<div id="ik-uyari-kutusu">' + mesaj + '</div>'
                );
                setTimeout(function() {
                    $('#ik-uyari-kutusu').fadeOut(400, function() {
                        $(this).remove();
                    });
                }, 2000);
            }
        }
    };

    $(document).ready(function() {
        IcerikKoruma.init();
    });

})(jQuery);
EOF

F12 için uyarı göstermiyoruz çünkü kullanıcı yanlışlıkla basabilir ve bir uyarı kutusuyla karşılaşmak sinir bozucu olur. Klavye engellemesi hâlâ çalışır ama sessizce.

CSS ile Uyarı Kutusunu Şekillendirmek

cat > assets/koruma.css << 'EOF'
#ik-uyari-kutusu {
    position: fixed;
    bottom: 30px;
    right: 30px;
    background-color: rgba(30, 30, 30, 0.92);
    color: #ffffff;
    padding: 14px 22px;
    border-radius: 6px;
    font-size: 14px;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    z-index: 999999;
    box-shadow: 0 4px 16px rgba(0,0,0,0.3);
    max-width: 320px;
    line-height: 1.5;
    display: block;
}

/* Metin seçimini CSS ile de engelle - JS kapalıysa bile kısmen çalışır */
.ik-korumali {
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
}
EOF

CSS ile user-select: none eklemek ayrıca önemli. JavaScript devre dışı bırakılmış tarayıcılarda bile metin seçimi güçleşir. İki katmanlı koruma.

Script Yükleme ve PHP Tarafına Veri Aktarma

PHP sınıfına geri dönelim ve scriptleri_yukle metodunu ekleyelim. Bu metod, WordPress’in wp_enqueue_scripts hook’una bağlı:

cat >> icerik-koruma.php << 'EOF'

// Icerik_Koruma sınıfının içine eklenecek metod:
// NOT: Bu kodu sınıf içindeki uygun yere manuel olarak eklemeniz gerekiyor.
// Aşağıdaki metodları sınıf kapanış parantezinden önce ekleyin.

/*
public function scriptleri_yukle() {
    if ( ! $this->koruma_aktif_mi() ) {
        return;
    }

    wp_enqueue_style(
        'icerik-koruma-css',
        plugin_dir_url( __FILE__ ) . 'assets/koruma.css',
        array(),
        '1.0.0'
    );

    wp_enqueue_script(
        'icerik-koruma-js',
        plugin_dir_url( __FILE__ ) . 'assets/koruma.js',
        array( 'jquery' ),
        '1.0.0',
        true
    );

    wp_localize_script( 'icerik-koruma-js', 'icerikKorumaAyarlar', array(
        'sagTikla'    => $this->options['sag_tikla'],
        'metinSecim'  => $this->options['metin_secim'],
        'klavye'      => $this->options['klavye'],
        'uyariMesaji' => esc_js( $this->options['uyari_mesaji'] ),
    ) );

    add_filter( 'body_class', array( $this, 'body_class_ekle' ) );
}

public function body_class_ekle( $classes ) {
    $classes[] = 'ik-korumali';
    return $classes;
}
*/
EOF

wp_localize_script kullanımı kritik. PHP verilerini doğrudan JavaScript’e aktarmanın WordPress’çe doğru yolu bu. Bazı geliştiricilerin yaptığı gibi inline script içine PHP echo etmek yerine bu yöntemi kullanın; hem güvenli hem de daha temiz.

Admin Paneli Ayar Sayfasını Oluşturmak

Eklentiyi güçlü yapan şey yönetim arayüzü. Ayar sayfası için gerekli metodları ekleyelim:

cat > /var/www/html/wp-content/plugins/icerik-koruma/admin-sayfa.php << 'EOF'
<?php
// Bu dosya sınıf metodları olarak ana dosyaya entegre edilmeli

function admin_menu_ekle() {
    add_options_page(
        'İçerik Koruma Ayarları',
        'İçerik Koruma',
        'manage_options',
        'icerik-koruma',
        array( $this, 'ayar_sayfasi_ciz' )
    );
}

function ayarlari_kaydet() {
    register_setting(
        'icerik_koruma_grubu',
        'icerik_koruma_ayarlar',
        array( $this, 'ayarlari_dogrula' )
    );
}

function ayarlari_dogrula( $input ) {
    $temiz = array();
    $temiz['aktif']        = isset( $input['aktif'] ) ? '1' : '0';
    $temiz['sag_tikla']    = isset( $input['sag_tikla'] ) ? '1' : '0';
    $temiz['metin_secim']  = isset( $input['metin_secim'] ) ? '1' : '0';
    $temiz['klavye']       = isset( $input['klavye'] ) ? '1' : '0';
    $temiz['admin_muaf']   = isset( $input['admin_muaf'] ) ? '1' : '0';
    $temiz['uyari_mesaji'] = sanitize_text_field( $input['uyari_mesaji'] );

    $izinli_tipler = array( 'post', 'page', 'product', 'portfolio' );
    $temiz['post_tipleri'] = array();
    if ( isset( $input['post_tipleri'] ) && is_array( $input['post_tipleri'] ) ) {
        foreach ( $input['post_tipleri'] as $tip ) {
            if ( in_array( $tip, $izinli_tipler, true ) ) {
                $temiz['post_tipleri'][] = $tip;
            }
        }
    }
    return $temiz;
}

function ayar_sayfasi_ciz() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    ?>
    <div class="wrap">
        <h1>İçerik Koruma Ayarları</h1>
        <form method="post" action="options.php">
            <?php
            settings_fields( 'icerik_koruma_grubu' );
            do_settings_sections( 'icerik-koruma' );
            $opts = $this->options;
            ?>
            <table class="form-table">
                <tr>
                    <th>Korumayı Aktif Et</th>
                    <td>
                        <input type="checkbox" name="icerik_koruma_ayarlar[aktif]"
                               value="1" <?php checked( $opts['aktif'], '1' ); ?> />
                    </td>
                </tr>
                <tr>
                    <th>Sağ Tıklamayı Engelle</th>
                    <td>
                        <input type="checkbox" name="icerik_koruma_ayarlar[sag_tikla]"
                               value="1" <?php checked( $opts['sag_tikla'], '1' ); ?> />
                    </td>
                </tr>
                <tr>
                    <th>Metin Seçimini Engelle</th>
                    <td>
                        <input type="checkbox" name="icerik_koruma_ayarlar[metin_secim]"
                               value="1" <?php checked( $opts['metin_secim'], '1' ); ?> />
                    </td>
                </tr>
                <tr>
                    <th>Klavye Kısayollarını Engelle</th>
                    <td>
                        <input type="checkbox" name="icerik_koruma_ayarlar[klavye]"
                               value="1" <?php checked( $opts['klavye'], '1' ); ?> />
                    </td>
                </tr>
                <tr>
                    <th>Yöneticiyi Muaf Tut</th>
                    <td>
                        <input type="checkbox" name="icerik_koruma_ayarlar[admin_muaf]"
                               value="1" <?php checked( $opts['admin_muaf'], '1' ); ?> />
                        <p class="description">İşaretlenirse admin hesabıyla girerken koruma çalışmaz.</p>
                    </td>
                </tr>
                <tr>
                    <th>Uyarı Mesajı</th>
                    <td>
                        <input type="text" name="icerik_koruma_ayarlar[uyari_mesaji]"
                               value="<?php echo esc_attr( $opts['uyari_mesaji'] ); ?>"
                               class="regular-text" />
                    </td>
                </tr>
            </table>
            <?php submit_button(); ?>
        </form>
    </div>
    <?php
}
EOF

sanitize_text_field() ve whitelist ile post type doğrulaması yapıyoruz. Kullanıcı girdisine asla güvenme, hatta admin paneli girdisine bile. WordPress güvenlik standartları bunu gerektiriyor.

Eklentiyi Aktif Etmek ve Test Etmek

Dosyaları yerleştirdikten sonra WordPress yönetim panelinden eklentiyi aktif edebilirsiniz, ya da WP-CLI varsa komut satırından:

# WP-CLI ile eklentiyi aktif etmek
wp plugin activate icerik-koruma --path=/var/www/html

# Eklentinin doğru yüklenip yüklenmediğini kontrol et
wp plugin list --path=/var/www/html | grep icerik-koruma

# Eklenti ayarlarını komut satırından kontrol etmek
wp option get icerik_koruma_ayarlar --path=/var/www/html

# Ayarları WP-CLI ile güncellemek (test için)
wp option update icerik_koruma_ayarlar 
    '{"aktif":"1","sag_tikla":"1","metin_secim":"1","klavye":"1","admin_muaf":"1","uyari_mesaji":"Bu içerik koruma altındadır.","post_tipleri":["post","page","product"]}' 
    --format=json --path=/var/www/html

Test ederken farklı bir tarayıcıda veya gizli sekmede açın. Admin muafiyet özelliği aktifse yönetici hesabıyla test ettiğinizde eklentiyi çalışır göremezsiniz, bu normal.

Gerçek Dünya Notları: Sık Karşılaşılan Sorunlar

jQuery çakışması: Bazı temalar $ yerine jQuery kullanır veya jQuery’yi defer ile yükler. IIFE içinde $ parametresi kullanmak bunu çözer, zaten öyle yazdık.

WooCommerce product sayfaları: product post type’ını listeye eklemek genellikle yeterli ama bazı sayfa oluşturucular (Elementor, Divi) kendi özel sınıfları nedeniyle is_singular() kontrolünü atlayabilir. Bu durumda is_product() fonksiyonu eklenebilir:

# WooCommerce uyumluluğu için koruma_aktif_mi() metoduna ek kontrol
# Aşağıdaki satırı is_singular() kontrolünden sonra ekleyin:

# if ( function_exists('is_product') && is_product() ) {
#     return true;
# }

Cache eklentileri: W3 Total Cache veya WP Rocket kullanıyorsanız eklentiyi aktive ettikten sonra cache’i temizleyin. JS dosyası cache’lenmiş eski sürümle geliyorsa koruma çalışmaz görünür.

Mobil tarayıcılar: iOS Safari’de sağ tıklama yoktur ama uzun basma bağlam menüsü açar. contextmenu eventi bunu da yakalar, iOS 13 ve üzeri için genellikle çalışır.

Gelişmiş Özellik: Seçili Metni Engelleme Yerine Watermark Ekleme

Sadece engellemek yerine daha sofistike bir yaklaşım: Kullanıcı bir şeyi kopyaladığında kopyalanan metne site adresinizi ekleyin. Bu özellik bazı yayın sitelerinin kullandığı bir yöntem:

cat >> assets/koruma.js << 'EOF'

// Kopyalama olayına watermark ekleme
// Bu bloğu IcerikKoruma.init() içinde çağırın: this.watermarkEkle();
/*
watermarkEkle: function() {
    $(document).on('copy', function(e) {
        var seciliMetin = window.getSelection().toString();
        if (seciliMetin.length > 50) {
            var watermark = "nn--- Kaynak: " + window.location.href + " ---";
            var clipboardData = e.originalEvent.clipboardData;
            if (clipboardData) {
                clipboardData.setData('text/plain', seciliMetin + watermark);
                e.preventDefault();
            }
        }
    });
}
*/
EOF

Bu yöntemi metin seçim engeliyle beraber kullanmak yerine birini seçin. İkisi aynı anda aktifse çelişki yaratır.

Güvenlik Taraması ve Üretim Öncesi Kontrol

Eklentiyi yayına almadan önce yapılacaklar:

# PHP syntax kontrolü
php -l /var/www/html/wp-content/plugins/icerik-koruma/icerik-koruma.php

# WordPress kodlama standartları kontrolü (PHP_CodeSniffer kuruluysa)
phpcs --standard=WordPress /var/www/html/wp-content/plugins/icerik-koruma/

# Dosya izinlerini kontrol et
ls -la /var/www/html/wp-content/plugins/icerik-koruma/

# İzinler doğru olmalı: PHP dosyaları 644, dizinler 755
chmod 755 /var/www/html/wp-content/plugins/icerik-koruma/
chmod 644 /var/www/html/wp-content/plugins/icerik-koruma/icerik-koruma.php
chmod 644 /var/www/html/wp-content/plugins/icerik-koruma/assets/koruma.js
chmod 644 /var/www/html/wp-content/plugins/icerik-koruma/assets/koruma.css

Üretim ortamına alınacak bir eklentide debug logunu da kapatın ya da hata loglama için WordPress’in kendi error_log() fonksiyonunu kullanın, var_dump() veya print_r() bırakmayın.

Sonuç

Başta bahsettiğim müşteriyle bu eklentiyi iki saatte yazdık ve deploy ettik. Hazır çözümlerin aksine gereksiz bir özellik yok, veritabanına onlarca tablo açmıyor ve her sayfa yüklemesinde 200KB JavaScript indirmiyor. Toplam boyut: üç dosya, ~15KB.

Özel eklenti yazmak çok daha az korkutucu gelecek eğer temeli doğru kurarsanız: ABSPATH kontrolü, nonce ile form güvenliği, sanitize ve escape her yerde, hook’lara direkt fonksiyon yerine sınıf metodu bağlamak. Bu alışkanlıkları kazandığınızda sıfırdan bir eklenti yazmak artık standart bir sysadmin görevi haline gelir.

Bir hatırlatma: Bu koruma katmanı, telif hakkı ihlallerini hukuki olarak durdurmaz. Gerçek koruma için içeriklerinizi düzenli olarak DMCA bildirimiyle destekleyin ve özgün içerik üretmeye devam edin. Teknik engeller deterrent (caydırıcı) görevi görür, mutlak bariyer değil.

Bir yanıt yazın

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