Google Analytics Ekleyen WordPress Eklentisi Yapımı
Bir WordPress sitesine Google Analytics eklemek için eklenti kurmak, tema dosyalarını düzenlemek ya da functions.php‘ye bir şeyler yapıştırmak… Hepsi geçerli yollar ama hiçbiri “kendi eklentini yaz” kadar tatmin edici değil. Üstelik kendi yazdığın eklenti ile tema değişimlerinden etkilenmezsin, güncellemeler kodunu silmez ve ne yaptığını tam olarak bilirsin. Bu yazıda sıfırdan bir Google Analytics eklentisi yazacağız; temel gtag.js entegrasyonundan admin paneli ayar sayfasına kadar her şeyi ele alacağız.
Eklenti Geliştirmeye Başlamadan Önce
WordPress eklenti geliştirme dünyasına ilk adımı atıyorsan şunu bil: WordPress, eklentiler için oldukça net bir yapı sunuyor. Hook sistemi (actions ve filters) sayesinde WordPress’in çalışma döngüsüne istediğin noktada dahil olabiliyorsun. Google Analytics eklentimiz için ihtiyacımız olan şeyler şunlar:
- Admin panelinde bir ayar sayfası (Tracking ID girilecek)
- Bu ayarı veritabanında saklama mekanizması
- Her sayfanın
bölümünegtag.jskodunu enjekte etme - Yönetici kullanıcılar için takibi isteğe bağlı devre dışı bırakma
Geliştirme ortamı olarak yerel bir WordPress kurulumu şart. LocalWP, XAMPP ya da Docker tabanlı bir setup kullanabilirsin. Ben genellikle Docker tercih ediyorum çünkü production ortamına daha yakın bir deneyim sunuyor.
Dosya Yapısı
WordPress eklentileri /wp-content/plugins/ dizini altında yaşar. Eklentimiz için şu yapıyı kullanacağız:
wp-content/plugins/
└── basit-analytics/
├── basit-analytics.php # Ana eklenti dosyası
├── includes/
│ ├── class-frontend.php # Frontend işlemleri
│ └── class-admin.php # Admin paneli işlemleri
└── assets/
└── css/
└── admin-style.css # Admin paneli için stil
Bu yapıyı oluşturmak için terminalden şunu çalıştırabilirsin:
mkdir -p wp-content/plugins/basit-analytics/{includes,assets/css}
touch wp-content/plugins/basit-analytics/basit-analytics.php
touch wp-content/plugins/basit-analytics/includes/class-frontend.php
touch wp-content/plugins/basit-analytics/includes/class-admin.php
touch wp-content/plugins/basit-analytics/assets/css/admin-style.css
Ana Eklenti Dosyası
Her WordPress eklentisinin bir başlık yorumu olması gerekiyor. WordPress bu yorumu okuyarak eklentinin adını, sürümünü ve diğer bilgilerini öğreniyor. basit-analytics.php dosyasını şu şekilde başlatıyoruz:
<?php
/**
* Plugin Name: Basit Analytics
* Plugin URI: https://siteadresi.com/basit-analytics
* Description: Google Analytics 4 entegrasyonu için basit ve hafif bir eklenti.
* Version: 1.0.0
* Author: Adın Soyadın
* Author URI: https://siteadresi.com
* License: GPL v2 or later
* Text Domain: basit-analytics
*/
// Doğrudan erişimi engelle
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Sabitler
define( 'BASIT_ANALYTICS_VERSION', '1.0.0' );
define( 'BASIT_ANALYTICS_PATH', plugin_dir_path( __FILE__ ) );
define( 'BASIT_ANALYTICS_URL', plugin_dir_url( __FILE__ ) );
// Sınıf dosyalarını dahil et
require_once BASIT_ANALYTICS_PATH . 'includes/class-admin.php';
require_once BASIT_ANALYTICS_PATH . 'includes/class-frontend.php';
// Eklentiyi başlat
function basit_analytics_init() {
$admin = new Basit_Analytics_Admin();
$frontend = new Basit_Analytics_Frontend();
$admin->register_hooks();
$frontend->register_hooks();
}
add_action( 'plugins_loaded', 'basit_analytics_init' );
// Aktivasyon hook'u
register_activation_hook( __FILE__, 'basit_analytics_activate' );
function basit_analytics_activate() {
// Varsayılan seçenekleri kaydet
if ( ! get_option( 'basit_analytics_settings' ) ) {
$defaults = array(
'tracking_id' => '',
'exclude_admins' => '1',
'anonymize_ip' => '1',
);
add_option( 'basit_analytics_settings', $defaults );
}
}
// Deaktivasyon hook'u
register_deactivation_hook( __FILE__, 'basit_analytics_deactivate' );
function basit_analytics_deactivate() {
// Gerekirse temizlik işlemleri buraya
}
ABSPATH kontrolü kritik bir güvenlik önlemi. Bu satır olmadan biri doğrudan dosyaya erişerek PHP kodunu çalıştırabilir. Her zaman ekle.
Admin Sınıfı
Admin paneli için class-admin.php dosyasını yazıyoruz. Bu sınıf, ayarlar menüsünü oluşturacak, formu işleyecek ve seçenekleri veritabanına kaydedecek:
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Basit_Analytics_Admin {
public function register_hooks() {
add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
}
public function add_settings_page() {
add_options_page(
'Basit Analytics Ayarları',
'Basit Analytics',
'manage_options',
'basit-analytics',
array( $this, 'render_settings_page' )
);
}
public function register_settings() {
register_setting(
'basit_analytics_group',
'basit_analytics_settings',
array( $this, 'sanitize_settings' )
);
add_settings_section(
'basit_analytics_main',
'Genel Ayarlar',
null,
'basit-analytics'
);
add_settings_field(
'tracking_id',
'GA4 Ölçüm ID'si',
array( $this, 'render_tracking_id_field' ),
'basit-analytics',
'basit_analytics_main'
);
add_settings_field(
'exclude_admins',
'Yöneticileri Hariç Tut',
array( $this, 'render_exclude_admins_field' ),
'basit-analytics',
'basit_analytics_main'
);
add_settings_field(
'anonymize_ip',
'IP Anonimleştirme',
array( $this, 'render_anonymize_ip_field' ),
'basit-analytics',
'basit_analytics_main'
);
}
public function sanitize_settings( $input ) {
$sanitized = array();
// Tracking ID formatını doğrula (G-XXXXXXXXXX)
if ( isset( $input['tracking_id'] ) ) {
$tracking_id = sanitize_text_field( $input['tracking_id'] );
if ( preg_match( '/^G-[A-Z0-9]{10}$/', $tracking_id ) || empty( $tracking_id ) ) {
$sanitized['tracking_id'] = $tracking_id;
} else {
$sanitized['tracking_id'] = '';
add_settings_error(
'basit_analytics_settings',
'invalid_tracking_id',
'Geçersiz GA4 Ölçüm ID formatı. Örnek: G-XXXXXXXXXX',
'error'
);
}
}
$sanitized['exclude_admins'] = isset( $input['exclude_admins'] ) ? '1' : '0';
$sanitized['anonymize_ip'] = isset( $input['anonymize_ip'] ) ? '1' : '0';
return $sanitized;
}
public function render_tracking_id_field() {
$settings = get_option( 'basit_analytics_settings' );
$tracking_id = isset( $settings['tracking_id'] ) ? $settings['tracking_id'] : '';
echo '<input type="text" name="basit_analytics_settings[tracking_id]"
value="' . esc_attr( $tracking_id ) . '"
placeholder="G-XXXXXXXXXX" class="regular-text">';
echo '<p class="description">Google Analytics 4 mülkünüzden aldığınız Ölçüm ID'sini girin.</p>';
}
public function render_exclude_admins_field() {
$settings = get_option( 'basit_analytics_settings' );
$exclude_admins = isset( $settings['exclude_admins'] ) ? $settings['exclude_admins'] : '1';
echo '<input type="checkbox" name="basit_analytics_settings[exclude_admins]"
value="1" ' . checked( '1', $exclude_admins, false ) . '>';
echo '<label>Yönetici rolüne sahip kullanıcıların ziyaretlerini sayma</label>';
}
public function render_anonymize_ip_field() {
$settings = get_option( 'basit_analytics_settings' );
$anonymize_ip = isset( $settings['anonymize_ip'] ) ? $settings['anonymize_ip'] : '1';
echo '<input type="checkbox" name="basit_analytics_settings[anonymize_ip]"
value="1" ' . checked( '1', $anonymize_ip, false ) . '>';
echo '<label>IP adreslerini anonimleştir (KVKK uyumu için önerilir)</label>';
}
public function render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<?php settings_errors( 'basit_analytics_settings' ); ?>
<form action="options.php" method="post">
<?php
settings_fields( 'basit_analytics_group' );
do_settings_sections( 'basit-analytics' );
submit_button( 'Ayarları Kaydet' );
?>
</form>
</div>
<?php
}
public function enqueue_admin_assets( $hook ) {
if ( 'settings_page_basit-analytics' !== $hook ) {
return;
}
wp_enqueue_style(
'basit-analytics-admin',
BASIT_ANALYTICS_URL . 'assets/css/admin-style.css',
array(),
BASIT_ANALYTICS_VERSION
);
}
}
sanitize_settings metoduna dikkat et. Kullanıcıdan gelen veriyi hiçbir zaman doğrudan kaydetme. sanitize_text_field() ve regex doğrulaması ile hem XSS hem de veri bütünlüğü sorunlarını engelliyoruz.
Frontend Sınıfı
İşin asıl büyüsü burada oluyor. class-frontend.php dosyası, sayfanın bölümüne gtag.js kodunu ekliyor:
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Basit_Analytics_Frontend {
private $settings;
private $tracking_id;
public function __construct() {
$this->settings = get_option( 'basit_analytics_settings' );
$this->tracking_id = isset( $this->settings['tracking_id'] )
? $this->settings['tracking_id']
: '';
}
public function register_hooks() {
// Tracking ID yoksa hiçbir şey yapma
if ( empty( $this->tracking_id ) ) {
return;
}
add_action( 'wp_head', array( $this, 'inject_analytics_code' ), 1 );
}
public function inject_analytics_code() {
// Yönetici hariç tutma kontrolü
if ( $this->should_exclude_current_user() ) {
return;
}
$anonymize_ip = isset( $this->settings['anonymize_ip'] )
&& '1' === $this->settings['anonymize_ip'];
$tracking_id = esc_js( $this->tracking_id );
?>
<!-- Basit Analytics tarafından eklendi -->
<script async src="https://www.googletagmanager.com/gtag/js?id=<?php echo $tracking_id; ?>"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '<?php echo $tracking_id; ?>', {
<?php if ( $anonymize_ip ) : ?>
'anonymize_ip': true,
<?php endif; ?>
'send_page_view': true
});
</script>
<!-- /Basit Analytics -->
<?php
}
private function should_exclude_current_user() {
$exclude_admins = isset( $this->settings['exclude_admins'] )
&& '1' === $this->settings['exclude_admins'];
if ( $exclude_admins && current_user_can( 'manage_options' ) ) {
return true;
}
return false;
}
}
wp_head hook’una öncelik olarak 1 veriyoruz. Bu, analytics kodunun diğer scriptlerden önce yüklenmesini sağlıyor; özellikle özel event tracking yapacaksan bu önemli.
Admin Stil Dosyası
Küçük bir dokunuş ama settings sayfasını biraz daha kullanışlı hale getiriyor:
/* basit-analytics admin stili */
.basit-analytics-wrap .form-table th {
width: 220px;
font-weight: 600;
}
.basit-analytics-status {
display: inline-block;
padding: 4px 10px;
border-radius: 3px;
font-size: 13px;
font-weight: 500;
}
.basit-analytics-status.active {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.basit-analytics-status.inactive {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
Gerçek Dünya Senaryosu: WooCommerce Uyumu
Eğer site WooCommerce kullanıyorsa teşekkür sayfasını (order-received) takip etmen gerekebilir. Bunun için class-frontend.php içine şu metodu ekleyebilirsin:
public function inject_purchase_event() {
if ( ! function_exists( 'is_order_received_page' ) ) {
return;
}
if ( ! is_order_received_page() ) {
return;
}
global $wp;
$order_id = isset( $wp->query_vars['order-received'] )
? absint( $wp->query_vars['order-received'] )
: 0;
if ( ! $order_id ) {
return;
}
$order = wc_get_order( $order_id );
if ( ! $order || $order->get_meta( '_ga_tracked' ) ) {
return;
}
$items = array();
foreach ( $order->get_items() as $item ) {
$items[] = array(
'item_id' => $item->get_product_id(),
'item_name' => $item->get_name(),
'quantity' => $item->get_quantity(),
'price' => $item->get_subtotal() / $item->get_quantity(),
);
}
$order_data = array(
'transaction_id' => $order->get_id(),
'value' => $order->get_total(),
'currency' => $order->get_currency(),
'items' => $items,
);
?>
<script>
gtag('event', 'purchase', <?php echo wp_json_encode( $order_data ); ?>);
</script>
<?php
// Çift tracking'i önlemek için işaretle
$order->update_meta_data( '_ga_tracked', true );
$order->save();
}
Bu metodu register_hooks() içinde çağırman gerekiyor:
public function register_hooks() {
if ( empty( $this->tracking_id ) ) {
return;
}
add_action( 'wp_head', array( $this, 'inject_analytics_code' ), 1 );
add_action( 'wp_footer', array( $this, 'inject_purchase_event' ) );
}
_ga_tracked meta alanını kullanma sebebi şu: Kullanıcı sayfayı yenilerse aynı sipariş için iki kez purchase eventi göndermemek istiyoruz. Production ortamında bu tür çift kayıt sorunları raporları ciddi şekilde bozuyor.
Eklentiyi Test Etme
Eklentiyi etkinleştirdikten sonra Ayarlar > Basit Analytics sayfasına git ve GA4 ölçüm ID’ni gir. Ardından siteyi ziyaret et ve Chrome DevTools ile şunu kontrol et:
# Network sekmesinde şu isteği ara:
https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX
# Console'da çalıştır:
window.dataLayer
# Output: Array içinde gtag verilerini görmeli
GA4 gerçek zamanlı raporlarından da doğrulama yapabilirsin. Ancak yönetici hesabıyla giriş yaptıysan ve “Yöneticileri Hariç Tut” seçeneği aktifse kendi ziyaretlerin görünmez; bunu unutma.
Güvenlik Kontrol Listesi
Bu eklentiyi production’a almadan önce şunları teyit et:
- Nonce doğrulama:
settings_fields()bunu otomatik hallediyor, özel form işlemlerinde manuel ekle - Capability kontrolleri:
current_user_can('manage_options')her admin fonksiyonunda mevcut - Veri sanitizasyonu: Kullanıcıdan gelen tüm veriler
sanitize_text_field()veesc_attr()ile geçiyor - Output escaping: HTML çıktısında
esc_html(),esc_attr(),esc_js()kullanılıyor - Doğrudan erişim engeli: Her dosyanın başında
ABSPATHkontrolü var
Eklentiyi WordPress.org’a göndermeyecek olsan bile bu standartları korumak iyi bir alışkanlık. Özellikle birden fazla kişinin geliştirdiği projelerde bu kurallar hayat kurtarır.
Sonuç
Yaklaşık 200 satır PHP ile production’a hazır, güvenli bir Google Analytics eklentisi yazdık. Tema değişimlerinden etkilenmiyor, ayarlar veritabanında güvenli biçimde saklanıyor, WooCommerce sipariş takibi destekliyor ve KVKK için IP anonimleştirme seçeneği var.
Bu eklentiyi daha da geliştirmek istersen şu adımlar mantıklı olur: Consent Mode v2 entegrasyonu eklemek (çerez onay bannerlarıyla uyum için), özel event tracking için bir JavaScript API’si sunmak, ya da veritabanına performans odaklı basit bir isabet sayacı entegre etmek. Ama şu an için elimizde bulunan; temel ihtiyaçları karşılayan, kodunu okuyup anlayabileceğin ve güvenle deploy edebileceğin bir yapı.
Kendi eklentini yazmak sana ne kazandırıyor? Tam kontrol. Tracking ID değiştiğinde eklenti güncellemesi beklemeye gerek yok, gereksiz JavaScript yükü yok, ve bir şey bozulduğunda nereye bakacağını biliyorsun. Sysadmin dünyasında “kara kutu” olmayan çözümler her zaman kazanır.
