WordPress’te Özel Widget Oluşturma: WP_Widget Sınıfı Kullanımı
WordPress ile ciddi anlamda uğraşmaya başladığınızda, hazır widget’ların yetersiz kaldığı anları kaçınılmaz olarak yaşarsınız. Müşteri “şu köşeye son ürünlerimiz gelsin ama sadece belirli bir kategoriden” ya da “sidebar’a özel duyuru kutusu ekleyelim, admin panelinden kolayca değiştirebilelim” gibi istekler sıralamaya başlar. İşte tam bu noktada WP_Widget sınıfı devreye giriyor ve hayatınızı kurtarıyor.
WP_Widget Sınıfı Nedir?
WordPress, widget sistemini nesne yönelimli programlama (OOP) mantığıyla yönetir. WP_Widget sınıfı, kendi özel widget’larınızı oluşturmak için extend (genişletme) yapmanız gereken temel sınıftır. Bu sınıfı extend ettiğinizde, dört temel metodu override etmeniz beklenir:
__construct(): Widget’ın kimliğini, adını ve varsayılan ayarlarını tanımlarwidget(): Widget’ın frontend’de nasıl görüneceğini belirlerform(): Admin panelinde widget ayarları formunu oluştururupdate(): Form verilerini temizleyip kaydeder
Bu yapı sayesinde her widget, hem görsel hem de yönetimsel açıdan tamamen izole bir bileşen olarak çalışır. functions.php dosyasına ya da ayrı bir plugin dosyasına ekleyebilirsiniz, ikisi de geçerli yaklaşım.
Temel Widget Yapısı
Önce iskelet yapıyı görelim, sonra üzerine inşa edelim:
class My_Custom_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'my_custom_widget', // Widget ID (benzersiz olmalı)
'Özel Widget', // Widget adı (admin panelinde görünür)
array(
'description' => 'Bu benim özel widget'ım.',
'classname' => 'my-custom-widget-class',
)
);
}
// Frontend görünümü
public function widget( $args, $instance ) {
echo $args['before_widget'];
echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title'];
echo '<p>Merhaba Dünya!</p>';
echo $args['after_widget'];
}
// Admin formu
public function form( $instance ) {
$title = ! empty( $instance['title'] ) ? $instance['title'] : 'Varsayılan Başlık';
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>">Başlık:</label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
name="<?php echo $this->get_field_name('title'); ?>"
type="text" value="<?php echo esc_attr($title); ?>">
</p>
<?php
}
// Veri güncelleme
public function update( $new_instance, $old_instance ) {
$instance = array();
$instance['title'] = ( ! empty( $new_instance['title'] ) )
? strip_tags( $new_instance['title'] )
: '';
return $instance;
}
}
// Widget'ı kaydet
function register_my_custom_widget() {
register_widget( 'My_Custom_Widget' );
}
add_action( 'widgets_init', 'register_my_custom_widget' );
Bu yapı, her özel widget’ın başlangıç noktasıdır. Şimdi gerçek dünya senaryolarına geçelim.
Senaryo 1: Son Yazılar Widget’ı (Özelleştirilebilir)
WordPress’in varsayılan “Son Yazılar” widget’ı oldukça kısıtlıdır. Müşteriler genellikle “kategori filtresi olsun”, “küçük resim görünsün”, “özet de çıksın” gibi istekler sıralar. İşte tam ihtiyaca göre yazılmış bir örnek:
class Custom_Recent_Posts_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'custom_recent_posts',
'Gelişmiş Son Yazılar',
array( 'description' => 'Kategori filtreli, görsel destekli son yazılar widget'ı.' )
);
}
public function widget( $args, $instance ) {
$title = apply_filters( 'widget_title', $instance['title'] ?? 'Son Yazılar' );
$post_count = ! empty( $instance['post_count'] ) ? absint( $instance['post_count'] ) : 5;
$category = ! empty( $instance['category'] ) ? absint( $instance['category'] ) : 0;
$show_thumb = ! empty( $instance['show_thumb'] ) ? (bool) $instance['show_thumb'] : false;
$query_args = array(
'post_type' => 'post',
'posts_per_page' => $post_count,
'post_status' => 'publish',
);
if ( $category > 0 ) {
$query_args['cat'] = $category;
}
$recent_posts = new WP_Query( $query_args );
echo $args['before_widget'];
if ( $title ) {
echo $args['before_title'] . $title . $args['after_title'];
}
if ( $recent_posts->have_posts() ) {
echo '<ul class="custom-recent-posts">';
while ( $recent_posts->have_posts() ) {
$recent_posts->the_post();
echo '<li>';
if ( $show_thumb && has_post_thumbnail() ) {
echo '<a href="' . get_permalink() . '">';
the_post_thumbnail( 'thumbnail' );
echo '</a>';
}
echo '<a href="' . get_permalink() . '">' . get_the_title() . '</a>';
echo '<span class="post-date">' . get_the_date() . '</span>';
echo '</li>';
}
echo '</ul>';
wp_reset_postdata();
} else {
echo '<p>Henüz yazı bulunamadı.</p>';
}
echo $args['after_widget'];
}
public function form( $instance ) {
$title = $instance['title'] ?? 'Son Yazılar';
$post_count = $instance['post_count'] ?? 5;
$category = $instance['category'] ?? 0;
$show_thumb = $instance['show_thumb'] ?? false;
$categories = get_categories( array( 'hide_empty' => false ) );
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>">Başlık:</label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
name="<?php echo $this->get_field_name('title'); ?>"
type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('post_count'); ?>">Gösterilecek Yazı Sayısı:</label>
<input class="tiny-text" id="<?php echo $this->get_field_id('post_count'); ?>"
name="<?php echo $this->get_field_name('post_count'); ?>"
type="number" min="1" max="20" value="<?php echo absint($post_count); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('category'); ?>">Kategori:</label>
<select class="widefat" id="<?php echo $this->get_field_id('category'); ?>"
name="<?php echo $this->get_field_name('category'); ?>">
<option value="0">Tümü</option>
<?php foreach ( $categories as $cat ) : ?>
<option value="<?php echo $cat->term_id; ?>"
<?php selected( $category, $cat->term_id ); ?>>
<?php echo esc_html( $cat->name ); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<p>
<input type="checkbox" id="<?php echo $this->get_field_id('show_thumb'); ?>"
name="<?php echo $this->get_field_name('show_thumb'); ?>"
value="1" <?php checked( $show_thumb, true ); ?>>
<label for="<?php echo $this->get_field_id('show_thumb'); ?>">Küçük Resim Göster</label>
</p>
<?php
}
public function update( $new_instance, $old_instance ) {
$instance = array();
$instance['title'] = sanitize_text_field( $new_instance['title'] );
$instance['post_count'] = absint( $new_instance['post_count'] );
$instance['category'] = absint( $new_instance['category'] );
$instance['show_thumb'] = ! empty( $new_instance['show_thumb'] ) ? 1 : 0;
return $instance;
}
}
add_action( 'widgets_init', function() {
register_widget( 'Custom_Recent_Posts_Widget' );
});
Senaryo 2: İletişim Bilgileri Widget’ı
Müşterilerin en sık istediği şeylerden biri, sidebar’a telefon, adres ve e-posta bilgilerinin kolayca yönetilebildiği bir widget. Bunu her seferinde tema dosyalarına hard-code yazmak hem tehlikeli hem de müşteriyi bağımlı kılar:
class Contact_Info_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'contact_info_widget',
'İletişim Bilgileri',
array( 'description' => 'Adres, telefon ve e-posta bilgilerini gösterir.' )
);
}
public function widget( $args, $instance ) {
$title = apply_filters( 'widget_title', $instance['title'] ?? 'İletişim' );
$phone = $instance['phone'] ?? '';
$email = $instance['email'] ?? '';
$address = $instance['address'] ?? '';
echo $args['before_widget'];
if ( $title ) {
echo $args['before_title'] . esc_html( $title ) . $args['after_title'];
}
echo '<div class="contact-info-widget">';
if ( $phone ) {
echo '<p class="contact-phone">';
echo '<strong>Telefon:</strong> ';
echo '<a href="tel:' . esc_attr( preg_replace('/D/', '', $phone) ) . '">';
echo esc_html( $phone );
echo '</a></p>';
}
if ( $email ) {
echo '<p class="contact-email">';
echo '<strong>E-posta:</strong> ';
echo '<a href="mailto:' . sanitize_email( $email ) . '">';
echo esc_html( $email );
echo '</a></p>';
}
if ( $address ) {
echo '<p class="contact-address">';
echo '<strong>Adres:</strong> ';
echo nl2br( esc_html( $address ) );
echo '</p>';
}
echo '</div>';
echo $args['after_widget'];
}
public function form( $instance ) {
?>
<p>
<label>Başlık:</label>
<input class="widefat" name="<?php echo $this->get_field_name('title'); ?>"
type="text" value="<?php echo esc_attr( $instance['title'] ?? 'İletişim' ); ?>">
</p>
<p>
<label>Telefon:</label>
<input class="widefat" name="<?php echo $this->get_field_name('phone'); ?>"
type="text" value="<?php echo esc_attr( $instance['phone'] ?? '' ); ?>">
</p>
<p>
<label>E-posta:</label>
<input class="widefat" name="<?php echo $this->get_field_name('email'); ?>"
type="email" value="<?php echo esc_attr( $instance['email'] ?? '' ); ?>">
</p>
<p>
<label>Adres:</label>
<textarea class="widefat" name="<?php echo $this->get_field_name('address'); ?>"
rows="3"><?php echo esc_textarea( $instance['address'] ?? '' ); ?></textarea>
</p>
<?php
}
public function update( $new_instance, $old_instance ) {
return array(
'title' => sanitize_text_field( $new_instance['title'] ),
'phone' => sanitize_text_field( $new_instance['phone'] ),
'email' => sanitize_email( $new_instance['email'] ),
'address' => sanitize_textarea_field( $new_instance['address'] ),
);
}
}
Senaryo 3: WooCommerce Uyumlu Kampanya Banner Widget’ı
E-ticaret sitelerinde sidebar’a indirim banneri ya da çağrı butonu koymak çok yaygın bir ihtiyaç. Bu örnek, WooCommerce’in yüklü olup olmadığını kontrol ederek çalışır:
class Promo_Banner_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'promo_banner_widget',
'Kampanya Banner',
array( 'description' => 'Özel kampanya metni ve CTA butonu gösterir.' )
);
}
public function widget( $args, $instance ) {
$title = $instance['title'] ?? '';
$content = $instance['content'] ?? '';
$btn_text = $instance['btn_text'] ?? 'Hemen İncele';
$btn_url = $instance['btn_url'] ?? '#';
$bg_color = $instance['bg_color'] ?? '#ff6b35';
$text_color = $instance['text_color'] ?? '#ffffff';
echo $args['before_widget'];
$style = sprintf(
'background-color: %s; color: %s; padding: 20px; border-radius: 8px; text-align: center;',
esc_attr( $bg_color ),
esc_attr( $text_color )
);
echo '<div class="promo-banner-widget" style="' . $style . '">';
if ( $title ) {
echo '<h3 style="color: ' . esc_attr($text_color) . '; margin-top: 0;">';
echo esc_html( $title );
echo '</h3>';
}
if ( $content ) {
echo '<p>' . wp_kses_post( $content ) . '</p>';
}
if ( $btn_text && $btn_url ) {
echo '<a href="' . esc_url( $btn_url ) . '" class="promo-btn" ';
echo 'style="display:inline-block; padding: 10px 20px; background: rgba(0,0,0,0.2); ';
echo 'color: ' . esc_attr($text_color) . '; text-decoration: none; border-radius: 4px;">';
echo esc_html( $btn_text );
echo '</a>';
}
echo '</div>';
echo $args['after_widget'];
}
public function form( $instance ) {
?>
<p>
<label>Başlık:</label>
<input class="widefat" name="<?php echo $this->get_field_name('title'); ?>"
type="text" value="<?php echo esc_attr( $instance['title'] ?? '' ); ?>">
</p>
<p>
<label>İçerik:</label>
<textarea class="widefat" name="<?php echo $this->get_field_name('content'); ?>"
rows="4"><?php echo esc_textarea( $instance['content'] ?? '' ); ?></textarea>
</p>
<p>
<label>Buton Metni:</label>
<input class="widefat" name="<?php echo $this->get_field_name('btn_text'); ?>"
type="text" value="<?php echo esc_attr( $instance['btn_text'] ?? 'Hemen İncele' ); ?>">
</p>
<p>
<label>Buton URL:</label>
<input class="widefat" name="<?php echo $this->get_field_name('btn_url'); ?>"
type="url" value="<?php echo esc_url( $instance['btn_url'] ?? '' ); ?>">
</p>
<p>
<label>Arkaplan Rengi:</label>
<input type="color" name="<?php echo $this->get_field_name('bg_color'); ?>"
value="<?php echo esc_attr( $instance['bg_color'] ?? '#ff6b35' ); ?>">
</p>
<p>
<label>Yazı Rengi:</label>
<input type="color" name="<?php echo $this->get_field_name('text_color'); ?>"
value="<?php echo esc_attr( $instance['text_color'] ?? '#ffffff' ); ?>">
</p>
<?php
}
public function update( $new_instance, $old_instance ) {
return array(
'title' => sanitize_text_field( $new_instance['title'] ),
'content' => wp_kses_post( $new_instance['content'] ),
'btn_text' => sanitize_text_field( $new_instance['btn_text'] ),
'btn_url' => esc_url_raw( $new_instance['btn_url'] ),
'bg_color' => sanitize_hex_color( $new_instance['bg_color'] ),
'text_color' => sanitize_hex_color( $new_instance['text_color'] ),
);
}
}
add_action( 'widgets_init', function() {
register_widget( 'Promo_Banner_Widget' );
});
Birden Fazla Widget’ı Tek Seferde Kaydetmek
Projenizde birden fazla özel widget varsa, hepsini tek bir widgets_init action’ına bağlamak temiz bir yaklaşımdır:
function register_all_custom_widgets() {
$widgets = array(
'My_Custom_Widget',
'Custom_Recent_Posts_Widget',
'Contact_Info_Widget',
'Promo_Banner_Widget',
);
foreach ( $widgets as $widget_class ) {
if ( class_exists( $widget_class ) ) {
register_widget( $widget_class );
}
}
}
add_action( 'widgets_init', 'register_all_custom_widgets' );
Widget’lara CSS Eklemek
Widget’ınızın özel CSS’e ihtiyacı varsa, bunu doğrudan widget() metoduna inline yazmak yerine düzgün şekilde sıraya almak gerekir:
class Styled_Widget extends WP_Widget {
public function __construct() {
parent::__construct( 'styled_widget', 'Stillenmiş Widget' );
// Sadece bu widget frontend'de varsa CSS yükle
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_widget_styles' ) );
}
public function enqueue_widget_styles() {
// Widget aktif sidebar'da mı kontrol et
if ( is_active_widget( false, false, $this->id_base, true ) ) {
wp_enqueue_style(
'styled-widget-css',
get_template_directory_uri() . '/assets/css/styled-widget.css',
array(),
'1.0.0'
);
}
}
public function widget( $args, $instance ) {
echo $args['before_widget'];
echo '<div class="styled-widget-wrapper">';
echo '<p>' . esc_html( $instance['content'] ?? 'Widget içeriği buraya gelecek.' ) . '</p>';
echo '</div>';
echo $args['after_widget'];
}
public function form( $instance ) {
?>
<p>
<label>İçerik:</label>
<input class="widefat" name="<?php echo $this->get_field_name('content'); ?>"
type="text" value="<?php echo esc_attr( $instance['content'] ?? '' ); ?>">
</p>
<?php
}
public function update( $new_instance, $old_instance ) {
return array(
'content' => sanitize_text_field( $new_instance['content'] ),
);
}
}
Widget Önbellekleme (Caching)
Eğer widget’ınız ağır bir veritabanı sorgusu çalıştırıyorsa, transient API ile önbelleğe almak performansı ciddi ölçüde artırır:
public function widget( $args, $instance ) {
$cache_key = 'my_widget_cache_' . $this->id;
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
echo $cached;
return;
}
ob_start();
echo $args['before_widget'];
// ... ağır sorgu ve çıktı işlemleri ...
$heavy_query = new WP_Query( array(
'post_type' => 'product',
'posts_per_page' => 10,
'meta_key' => '_featured',
'meta_value' => 'yes',
));
if ( $heavy_query->have_posts() ) {
echo '<ul>';
while ( $heavy_query->have_posts() ) {
$heavy_query->the_post();
echo '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
}
echo '</ul>';
wp_reset_postdata();
}
echo $args['after_widget'];
$output = ob_get_clean();
// 1 saat önbellekte tut
set_transient( $cache_key, $output, HOUR_IN_SECONDS );
echo $output;
}
// Widget güncellendiğinde önbelleği temizle
public function update( $new_instance, $old_instance ) {
delete_transient( 'my_widget_cache_' . $this->id );
// ... normal update işlemleri
return $new_instance;
}
Dikkat Edilmesi Gereken Noktalar
Widget geliştirirken sık yapılan hatalar ve pratik uyarılar:
- Sanitizasyon asla ihmal edilmemeli:
form()metodunda kullanıcı verisini ekrana yazdırırkenesc_attr(),esc_html(),esc_url()fonksiyonlarını kullanmadan geçmeyin update()metodunda doğru sanitizasyon türünü seçin: Metin içinsanitize_text_field(), HTML içerikli alanlar içinwp_kses_post(), URL içinesc_url_raw(), renk kodları içinsanitize_hex_color()kullanınwp_reset_postdata()unutmayın:WP_Queryile sorgu yaptıktan sonra bunu çağırmazsanız, sayfanın geri kalanındaki the loop bozulabilir- Widget ID’leri benzersiz olsun:
__construct()içindeki ilk parametre sitenizde başka hiçbir widget ile çakışmamalı $argsdeğerlerine dokunmayın:before_widget,after_widget,before_title,after_titledeğerleri tema tarafından belirlenir, bunları kendi isteğinize göre değiştirmeyin- PHP 8 uyumluluğu: Null coalescing operator (
??) kullanımında dikkatli olun, hedef PHP sürümünüzü göz önünde bulundurun
Block Editor ve Widget’ların Geleceği
WordPress 5.8 ile birlikte Gutenberg block editörü sidebar widget’larını da kapsama almaya başladı. Bu durum WP_Widget tabanlı widget’ları geçersiz kılmıyor, ancak uzun vadeli projelerde Block Widget yazmayı da düşünmek gerekiyor.
Mevcut temalar için functions.php‘ye şu satırı ekleyerek klasik widget editörünü koruyabilirsiniz:
// Klasik widget editörünü zorla kullan (Gutenberg widget editörünü devre dışı bırak)
add_filter( 'gutenberg_use_widgets_block_editor', '__return_false' );
add_filter( 'use_widgets_block_editor', '__return_false' );
Bu, özellikle eski temalar ve çok sayıda aktif WP_Widget kullanan sitelerde geçiş döneminde hayat kurtarır.
Sonuç
WP_Widget sınıfı, WordPress’in en sağlam ve test edilmiş yapı taşlarından biri. Doğru kullanıldığında, müşterinize admin panelinden kolayca yönetebileceği, güvenli ve performanslı bileşenler sunar. Her proje için sıfırdan yazmak zorunda değilsiniz; bu yazıdaki iskelet yapıyı alıp ihtiyacınıza göre şekillendirebilirsiniz.
Önemli olan, sanitizasyonu ve escaping’i asla atlamak, sorgu yaptıktan sonra wp_reset_postdata() çağırmak ve widget ID’lerinin benzersizliğini sağlamak. Bunlara dikkat ettiğinizde, hazır plugin almak yerine tam istediğiniz işi yapan, sitenizin geri kalanıyla uyumlu özel widget’lar geliştirmek oldukça tatmin edici bir hal alıyor.
