Admin Menüsüne Sayfa Eklemek: WordPress Yönetim Paneli
WordPress eklenti geliştirirken en çok vakit harcadığım konulardan biri, admin menüsünü doğru şekilde yapılandırmaktır. Yanlış kurulmuş bir menü yapısı, kullanıcı deneyimini mahvediyor ve bakım süreçlerini zorlaştırıyor. Bu yazıda, WordPress yönetim paneline sayfa eklemenin tüm inceliklerini gerçek senaryolar üzerinden aktaracağım.
WordPress Admin Menüsünün Çalışma Mantığı
WordPress admin menüsü, wp-admin/menu.php dosyasında tanımlı bir dizi üzerine inşa edilmiştir. Her menü öğesi bu dizide bir yuvaya karşılık gelir ve bu yuvalar pozisyon numaraları ile belirlenir. Eklenti geliştirirken bu yapıyı anlamak, çakışmaları önlemek açısından kritik önem taşır.
WordPress’in varsayılan menü pozisyonları şöyle sıralanır:
- 2: Dashboard
- 4: Separator
- 5: Posts
- 10: Media
- 15: Links
- 20: Pages
- 25: Comments
- 59: Separator
- 60: Appearance
- 65: Plugins
- 70: Users
- 75: Tools
- 80: Settings
- 99: Separator
Bu pozisyonlar arasına kendi menülerinizi yerleştirirken çakışma yaratmamak için null pozisyon kullanma yöntemini tercih ediyorum. Bunu birazdan örneklerle göstereceğim.
add_menu_page Fonksiyonu ile Ana Menü Ekleme
Ana menüye yeni bir sayfa eklemek için add_menu_page() fonksiyonu kullanılır. Bu fonksiyonu admin_menu kancasına (hook) bağlamak zorundasınız, aksi halde çalışmaz.
<?php
/**
* Plugin Name: Stok Yönetim Paneli
* Description: Gelişmiş stok takip sistemi
* Version: 1.0.0
*/
function stok_panel_menu_ekle() {
add_menu_page(
'Stok Yönetim Paneli', // Sayfa başlığı (title tag)
'Stok Yönetimi', // Menü etiketi (görünen isim)
'manage_options', // Gerekli yetki
'stok-yonetim', // Menü slug (benzersiz olmalı)
'stok_panel_sayfa_icerik', // Callback fonksiyonu
'dashicons-chart-bar', // İkon
58 // Pozisyon
);
}
add_action( 'admin_menu', 'stok_panel_menu_ekle' );
function stok_panel_sayfa_icerik() {
echo '<div class="wrap">';
echo '<h1>Stok Yönetim Paneli</h1>';
echo '<p>Buraya içerik gelecek.</p>';
echo '</div>';
}
Burada dikkat edilmesi gereken birkaç nokta var. manage_options yetkisi yalnızca yöneticilere atanmıştır. Eğer editörlerin de erişebileceği bir menü oluşturmak istiyorsanız edit_posts veya özel bir yetki kullanmanız gerekir. Ayrıca dashicons-chart-bar yerine harici bir SVG ikon da kullanabilirsiniz; bunu yine örnekleyeceğim.
add_submenu_page ile Alt Menü Ekleme
Gerçek dünya projelerinde tek bir ana menü sayfası genellikle yeterli olmaz. Birden fazla işlevsellik için alt menüler oluşturmanız gerekir. WooCommerce’i düşünün: Ana “WooCommerce” menüsünün altında Siparişler, Raporlar, Ayarlar gibi onlarca alt menü var.
<?php
function stok_panel_alt_menu_ekle() {
// Ana menü
add_menu_page(
'Stok Yönetimi',
'Stok Yönetimi',
'manage_options',
'stok-yonetim',
'stok_genel_bakis',
'dashicons-archive',
58
);
// İlk alt menü - genellikle ana menüyle aynı slug kullanılır
add_submenu_page(
'stok-yonetim', // Üst menü slug
'Genel Bakış', // Sayfa başlığı
'Genel Bakış', // Menü etiketi
'manage_options',
'stok-yonetim', // Ana menüyle aynı slug
'stok_genel_bakis'
);
// İkinci alt menü
add_submenu_page(
'stok-yonetim',
'Ürün Stokları',
'Ürün Stokları',
'manage_options',
'stok-urunler',
'stok_urunler_sayfasi'
);
// Üçüncü alt menü - farklı yetki seviyesi
add_submenu_page(
'stok-yonetim',
'Stok Raporları',
'Raporlar',
'view_stok_reports', // Özel yetki
'stok-raporlar',
'stok_raporlar_sayfasi'
);
// Ayarlar alt menüsü
add_submenu_page(
'stok-yonetim',
'Stok Ayarları',
'Ayarlar',
'manage_options',
'stok-ayarlar',
'stok_ayarlar_sayfasi'
);
}
add_action( 'admin_menu', 'stok_panel_alt_menu_ekle' );
Burada dikkat çekmek istediğim bir husus var: İlk alt menü ile ana menünün slug’ını aynı yapmanız önerilir. Bu sayede WordPress, admin menüsünde “Genel Bakış” adlı bir alt menü oluşturur ve ana menü tıklandığında bu sayfaya yönlendirir. Eğer farklı bir slug verirseniz, ana menünün ilk alt menüsünde garip isimler çıkabilir.
Mevcut WordPress Menülerine Sayfa Eklemek
Kendi menünüzü oluşturmak yerine, mevcut WordPress menülerine alt sayfa ekleyebilirsiniz. Bu özellikle küçük eklentiler için daha temiz bir yaklaşım olabilir.
<?php
function ozel_ayarlar_ekle() {
// Settings menüsüne ekleme
add_options_page(
'Özel Eklenti Ayarları',
'Özel Eklenti',
'manage_options',
'ozel-eklenti-ayarlar',
'ozel_eklenti_ayarlar_sayfasi'
);
// Tools menüsüne ekleme
add_management_page(
'Veri Temizleme Aracı',
'Veri Temizle',
'manage_options',
'veri-temizle',
'veri_temizleme_sayfasi'
);
// Users menüsüne ekleme
add_users_page(
'Kullanıcı Profil Şablonları',
'Profil Şablonları',
'manage_options',
'profil-sablonlar',
'profil_sablonlar_sayfasi'
);
// Appearance menüsüne ekleme
add_theme_page(
'Tema Özelleştirme Eklentisi',
'Tema Özelleştir',
'edit_theme_options',
'tema-ozellestir',
'tema_ozellestirme_sayfasi'
);
}
add_action( 'admin_menu', 'ozel_ayarlar_ekle' );
Bu yardımcı fonksiyonlar aslında add_submenu_page()‘in sarmalayıcılarıdır. add_options_page() için üst menü slug’ı options-general.php, add_management_page() için tools.php olarak ayarlanmıştır.
Özel Yetki Kontrolü ve Güvenlik
Admin sayfaları için güvenlik, asla göz ardı edilemeyecek bir konudur. Yalnızca add_menu_page‘deki yetki parametresine güvenmek yeterli değildir. Sayfa içeriğini render eden callback fonksiyonunuzda da yetki kontrolü yapmalısınız.
<?php
function stok_ayarlar_sayfasi() {
// Yetki kontrolü - mutlaka yapılmalı
if ( ! current_user_can( 'manage_options' ) ) {
wp_die(
__( 'Bu sayfaya erişim yetkiniz bulunmamaktadır.' ),
403
);
}
// Nonce doğrulaması - form işlemlerinde zorunlu
if ( isset( $_POST['stok_ayarlar_guncelle'] ) ) {
if ( ! wp_verify_nonce( $_POST['stok_nonce'], 'stok_ayarlar_kaydet' ) ) {
wp_die( 'Güvenlik doğrulaması başarısız.' );
}
// Gelen veriyi temizle ve kaydet
$depo_adi = sanitize_text_field( $_POST['depo_adi'] );
$esik_deger = absint( $_POST['esik_deger'] );
update_option( 'stok_depo_adi', $depo_adi );
update_option( 'stok_esik_deger', $esik_deger );
echo '<div class="notice notice-success"><p>Ayarlar kaydedildi.</p></div>';
}
// Mevcut değerleri al
$depo_adi = get_option( 'stok_depo_adi', 'Ana Depo' );
$esik_deger = get_option( 'stok_esik_deger', 10 );
?>
<div class="wrap">
<h1>Stok Ayarları</h1>
<form method="post" action="">
<?php wp_nonce_field( 'stok_ayarlar_kaydet', 'stok_nonce' ); ?>
<table class="form-table">
<tr>
<th>Depo Adı</th>
<td>
<input type="text"
name="depo_adi"
value="<?php echo esc_attr( $depo_adi ); ?>"
class="regular-text">
</td>
</tr>
<tr>
<th>Stok Eşik Değeri</th>
<td>
<input type="number"
name="esik_deger"
value="<?php echo esc_attr( $esik_deger ); ?>"
min="1">
</td>
</tr>
</table>
<input type="hidden" name="stok_ayarlar_guncelle" value="1">
<?php submit_button( 'Ayarları Kaydet' ); ?>
</form>
</div>
<?php
}
Bunu üretim ortamında defalarca test ettim: WordPress’in menü mekanizması yetki kontrolünü bypass etmek için farklı yollar deneyebilecek biri tarafından zorlansa bile, callback içindeki current_user_can() kontrolü son savunma hattınız olur.
Menü Öğelerini Koşullu Gizlemek ve Kaldırmak
Bazen belirli kullanıcı rollerine göre menü öğelerini gizlemek gerekebilir. Bir müşteri projede editörlerin “Eklentiler” menüsünü görmemesi gerekiyordu. remove_menu_page() ve remove_submenu_page() fonksiyonları tam da bu iş için var.
<?php
function ozel_menu_duzenleme() {
// Sadece yönetici değilse menüleri kaldır
if ( ! current_user_can( 'manage_options' ) ) {
remove_menu_page( 'plugins.php' ); // Eklentiler
remove_menu_page( 'themes.php' ); // Görünüm
remove_menu_page( 'tools.php' ); // Araçlar
remove_menu_page( 'edit.php?post_type=acf-field-group' ); // ACF menüsü
}
// Belirli alt menüleri kaldır
remove_submenu_page( 'themes.php', 'nav-menus.php' ); // Menüler
remove_submenu_page( 'options-general.php', 'privacy.php' ); // Gizlilik
// WooCommerce alt menüsünü kaldır (editörler için)
if ( ! current_user_can( 'manage_woocommerce' ) ) {
remove_submenu_page( 'woocommerce', 'wc-settings' );
}
}
// Bu işlem için admin_menu yerine admin_init daha güvenilir çalışır
add_action( 'admin_menu', 'ozel_menu_duzenleme', 999 );
Burada 999 önceliğini fark ettiniz mi? Diğer eklentilerin menülerini kaldırmak için kendi menü oluşturma işlemlerinin tamamlanmış olması gerekir. Yüksek bir öncelik numarası vererek “en son ben çalışayım” diyoruz.
SVG İkon Kullanımı ve Dashicons
Dashicons WordPress’in kendi ikon kütüphanesidir ama bazen marka kimliğinize uygun özel ikonlar kullanmak isteyebilirsiniz. SVG veri URI formatında direkt ikon tanımlayabilirsiniz.
<?php
function ozel_ikonlu_menu_ekle() {
// SVG ikon - Base64 encode edilmiş
$svg_ikon = 'data:image/svg+xml;base64,' . base64_encode(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill="#a7aaad" d="M10 2C5.58 2 2 5.58 2 10s3.58 8 8 8
8-3.58 8-8-3.58-8-8-8zm1 13H9v-2h2v2zm0-4H9V5h2v6z"/>
</svg>'
);
add_menu_page(
'Özel Yönetim',
'Özel Yönetim',
'manage_options',
'ozel-yonetim',
'ozel_yonetim_sayfasi',
$svg_ikon,
null // null pozisyon - çakışma olmaz ama yeri tahmin edilemez
);
}
add_action( 'admin_menu', 'ozel_ikonlu_menu_ekle' );
SVG içindeki fill rengini #a7aaad yapmanızı öneririm. Bu WordPress admin’in varsayılan ikon rengidir ve aktif/hover durumlarında WordPress CSS’i otomatik olarak rengi değiştirir.
Gerçek Dünya Senaryosu: WooCommerce Sipariş Takip Paneli
Birkaç ay önce bir e-ticaret müşterisi için özel bir sipariş takip paneli geliştirdim. Müşteri, kargo firması API’siyle entegre bir dashboard istiyordu. Bu yapı, birden fazla menü sayfasını bir arada nasıl yönetebileceğinizi göstermek açısından iyi bir örnek.
<?php
/**
* Plugin Name: Kargo Takip Dashboard
* Description: WooCommerce siparişleri için özel kargo takip paneli
* Version: 2.1.0
* Author: Geliştirici Adı
*/
class KargoTakipPanel {
private static $instance = null;
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
add_action( 'admin_menu', array( $this, 'menu_olustur' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'assets_yukle' ) );
}
public function menu_olustur() {
$ana_menu_slug = 'kargo-takip';
// Ana menü - WooCommerce'in hemen altına
add_menu_page(
'Kargo Takip Sistemi',
'Kargo Takip',
'edit_shop_orders',
$ana_menu_slug,
array( $this, 'dashboard_sayfasi' ),
'dashicons-car',
56
);
// Alt menüler
$alt_menuler = array(
array(
'baslik' => 'Sipariş Dashboard',
'etiket' => 'Dashboard',
'yetki' => 'edit_shop_orders',
'slug' => $ana_menu_slug,
'callback' => array( $this, 'dashboard_sayfasi' ),
),
array(
'baslik' => 'Bekleyen Kargolar',
'etiket' => 'Bekleyenler',
'yetki' => 'edit_shop_orders',
'slug' => 'kargo-bekleyenler',
'callback' => array( $this, 'bekleyenler_sayfasi' ),
),
array(
'baslik' => 'Kargo Raporları',
'etiket' => 'Raporlar',
'yetki' => 'view_woocommerce_reports',
'slug' => 'kargo-raporlar',
'callback' => array( $this, 'raporlar_sayfasi' ),
),
array(
'baslik' => 'API Ayarları',
'etiket' => 'API Ayarları',
'yetki' => 'manage_woocommerce',
'slug' => 'kargo-api-ayarlar',
'callback' => array( $this, 'api_ayarlar_sayfasi' ),
),
);
foreach ( $alt_menuler as $menu ) {
add_submenu_page(
$ana_menu_slug,
$menu['baslik'],
$menu['etiket'],
$menu['yetki'],
$menu['slug'],
$menu['callback']
);
}
}
public function assets_yukle( $hook ) {
// Sadece kendi sayfalarımızda CSS/JS yükle
if ( strpos( $hook, 'kargo-takip' ) === false &&
strpos( $hook, 'kargo-' ) === false ) {
return;
}
wp_enqueue_style(
'kargo-takip-css',
plugin_dir_url( __FILE__ ) . 'assets/css/panel.css',
array(),
'2.1.0'
);
wp_enqueue_script(
'kargo-takip-js',
plugin_dir_url( __FILE__ ) . 'assets/js/panel.js',
array( 'jquery' ),
'2.1.0',
true
);
// JavaScript'e PHP verisi aktar
wp_localize_script( 'kargo-takip-js', 'kargoAjax', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'kargo_takip_nonce' ),
));
}
public function dashboard_sayfasi() {
if ( ! current_user_can( 'edit_shop_orders' ) ) {
wp_die( 'Yetkiniz yok.', 403 );
}
include plugin_dir_path( __FILE__ ) . 'templates/dashboard.php';
}
public function bekleyenler_sayfasi() {
if ( ! current_user_can( 'edit_shop_orders' ) ) {
wp_die( 'Yetkiniz yok.', 403 );
}
include plugin_dir_path( __FILE__ ) . 'templates/bekleyenler.php';
}
public function raporlar_sayfasi() {
if ( ! current_user_can( 'view_woocommerce_reports' ) ) {
wp_die( 'Yetkiniz yok.', 403 );
}
include plugin_dir_path( __FILE__ ) . 'templates/raporlar.php';
}
public function api_ayarlar_sayfasi() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( 'Yetkiniz yok.', 403 );
}
include plugin_dir_path( __FILE__ ) . 'templates/api-ayarlar.php';
}
}
// Eklenti yüklendiğinde sınıfı başlat
add_action( 'plugins_loaded', function() {
KargoTakipPanel::get_instance();
});
Bu yapıda Singleton pattern kullandım. WordPress eklentilerinde global fonksiyon tanımlamak yerine OOP kullanmak, hem isim çakışmalarını önler hem de kodu daha yönetilebilir kılar. admin_enqueue_scripts kancasındaki $hook kontrolü de önemli: CSS ve JS dosyalarını tüm admin sayfalarında değil, yalnızca kendi sayfalarınızda yüklerseniz performans sorunlarının önüne geçmiş olursunuz.
Menü Sıralamasını Yönetmek
Birden fazla eklenti kurulu olduğunda menü sıralaması kaotik hale gelebilir. Bunun önüne geçmek için $menu global değişkenini kullanabilirsiniz.
<?php
function menu_sirasi_duzenle() {
global $menu;
// Mevcut menü sıralamasını görüntülemek için (geliştirme aşamasında)
if ( defined('WP_DEBUG') && WP_DEBUG ) {
// error_log( print_r( $menu, true ) );
}
// Belirli bir menüyü farklı pozisyona taşı
foreach ( $menu as $pozisyon => $menu_item ) {
if ( isset( $menu_item[2] ) && $menu_item[2] === 'kargo-takip' ) {
$menu[57] = $menu_item;
unset( $menu[$pozisyon] );
break;
}
}
}
add_action( 'admin_menu', 'menu_sirasi_duzenle', 9999 );
Bu yöntemi dikkatli kullanın. $menu global değişkenini doğrudan manipüle etmek WordPress’in resmi API’si dışına çıkmak anlamına gelir. Gelecek güncellemelerde sorun yaratabilir. Mümkün olduğunda add_menu_page‘deki pozisyon parametresini kullanmayı tercih edin.
Sık Karşılaşılan Hatalar ve Çözümleri
Geliştirme sürecinde tekrar eden bazı problemler var. Bunları bilmek zaman kazandırır.
Beyaz ekran veya 404 hatası: Sayfa slug’ı benzersiz olmadığında ya da callback fonksiyonu bulunamadığında ortaya çıkar. Slug’ların çakışmadığından emin olun.
Menü görünmüyor: admin_menu kancasına bağlamayı unutmuş olabilirsiniz veya eklentiniz henüz aktif değildir. plugins_loaded ile admin_menu arasındaki farkı anlayın; menü ekleme işlemi her zaman admin_menu kancasında yapılmalıdır.
Alt menü iki kez görünüyor: İlk add_submenu_page çağrısında ana menüyle aynı slug kullanmazsanız, WordPress varsayılan olarak “Dashboard” benzeri bir alt menü ekler ve sizin eklediğiniz de ayrıca görünür.
Assets tüm sayfalarda yükleniyor: admin_enqueue_scripts‘teki $hook parametresini kontrol etmediğinizde bu sorun çıkar. Her sayfanın hook adı farklıdır; kendi sayfalarınızın hook adını get_current_screen() veya add_menu_page‘in döndürdüğü değer üzerinden kontrol edebilirsiniz.
Yetki hatası beklenmedik kullanıcılar için: Özel yetkiler (custom capabilities) tanımladıysanız, bu yetkiyi ilgili rollere atadığınızdan emin olun. Atama yapılmamış özel bir yetki, hiçbir kullanıcının sayfaya erişememesi anlamına gelir.
Sonuç
WordPress admin menüsü yönetimi ilk bakışta basit görünse de üretim ortamında çok sayıda detay içeriyor. Doğru hook sıralaması, yetki yönetimi, güvenlik kontrolleri ve performans optimizasyonu bunların başında geliyor.
Özellikle şu üç noktanın altını çizmek istiyorum: Birincisi, callback fonksiyonlarınızda her zaman current_user_can() kontrolü yapın; menü parametresindeki yetki kontrolü tek başına yeterli değil. İkincisi, OOP yapısı kullanın; global fonksiyon tanımlamak küçük projelerde bile isim çakışması riskini beraberinde getiriyor. Üçüncüsü, CSS ve JavaScript dosyalarınızı yalnızca kendi sayfalarınızda yükleyin; bu hem performans hem de olası çakışmalar açısından kritik.
WooCommerce entegrasyonu olan projelerde edit_shop_orders ve manage_woocommerce gibi WooCommerce’e özgü yetkileri kullanmak, standart WordPress rol sistemine ek bir güvenlik ve esneklik katmanı sağlıyor. Bu mimariyi bir kere oturttuğunuzda, üzerine inşa edilen her yeni özellik çok daha düzenli ve sürdürülebilir hale geliyor.
