Bakım Modu Sunan WordPress Eklentisi Geliştirme
Bir WordPress sitesini bakıma almak kulağa basit gelir; birkaç satır PHP, belki bir .htaccess kuralı, iş biter. Ama gerçek dünyada bu işi doğru yapmak, hem ziyaretçi deneyimini hem SEO’yu hem de admin kullanıcılarının siteye erişimini dengelemeyi gerektirir. Ben bu konuyu teorik olarak değil, onlarca prodüksiyona alma ve acil bakım senaryosu yaşadıktan sonra öğrendim. Bu yazıda sıfırdan bir WordPress bakım modu eklentisi geliştireceğiz ve bunu yaparken de gerçekten kullanılabilir, sağlam bir yapı kuracağız.
Eklentinin Mimarisini Düşünmek
Çoğu insan “bakım modu eklentisi” deyince şunu düşünür: kullanıcı girerse bak, bakım modundaysa bir HTML sayfası göster, değilse normal devam et. Doğru ama eksik. İyi bir bakım modu eklentisi şunları yapmalı:
- Admin kullanıcıları bakım ekranını görmemeli, siteye normal girip çalışabilmeli
- Arama motorlarına
503 Service Unavailabledönmeli (bakım geçici olduğu için 503, kalıcı değişiklik anlamına gelen 301/302 değil) - Bakım süresi tahminini gösterebilmeli
- Özelleştirilebilir bir mesaj ve görsel sunsun
- WP-CLI ile aktif/pasif yapılabilmeli (bu özelliği çoğu eklenti atlar, biz atlamayacağız)
Dosya yapımız şöyle olacak:
wp-content/plugins/ozgun-bakim-modu/
├── ozgun-bakim-modu.php # Ana eklenti dosyası
├── includes/
│ ├── class-bakim-modu.php # Çekirdek sınıf
│ ├── class-admin.php # Admin panel mantığı
│ └── class-wpcli.php # WP-CLI komutu
├── templates/
│ └── bakim-sayfasi.php # Bakım ekranı şablonu
└── assets/
└── bakim.css # Basit stil dosyası
Bu yapıyı neden böyle kuruyoruz? Çünkü her şeyi tek dosyaya yazmak başlangıçta hızlı görünür, bir yıl sonra o dosyayı okumaya çalışınca küçük bir cehennem olduğunu anlarsınız.
Ana Eklenti Dosyası
<?php
/**
* Plugin Name: Özgün Bakım Modu
* Plugin URI: https://siteniz.com
* Description: Profesyonel bakım modu yönetimi. 503 desteği, WP-CLI entegrasyonu.
* Version: 1.0.0
* Author: Sizin Adınız
* Text Domain: ozgun-bakim-modu
* Domain Path: /languages
*/
defined( 'ABSPATH' ) || exit;
define( 'OBM_VERSION', '1.0.0' );
define( 'OBM_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'OBM_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
require_once OBM_PLUGIN_DIR . 'includes/class-bakim-modu.php';
require_once OBM_PLUGIN_DIR . 'includes/class-admin.php';
if ( defined( 'WP_CLI' ) && WP_CLI ) {
require_once OBM_PLUGIN_DIR . 'includes/class-wpcli.php';
WP_CLI::add_command( 'bakim', 'OBM_WPCLI' );
}
function obm_baslat() {
$bakim = new OBM_Bakim_Modu();
$bakim->init();
if ( is_admin() ) {
$admin = new OBM_Admin();
$admin->init();
}
}
add_action( 'plugins_loaded', 'obm_baslat' );
register_activation_hook( __FILE__, 'obm_aktiflesme' );
function obm_aktiflesme() {
add_option( 'obm_ayarlar', [
'aktif' => false,
'mesaj' => 'Sitemiz kısa süreliğine bakımdadır. En kısa sürede geri döneceğiz.',
'sure' => '',
'baslik' => 'Yakında Geri Döneceğiz',
] );
}
register_deactivation_hook( __FILE__, 'obm_deaktiflesme' );
function obm_deaktiflesme() {
$ayarlar = get_option( 'obm_ayarlar', [] );
$ayarlar['aktif'] = false;
update_option( 'obm_ayarlar', $ayarlar );
}
Eklenti deaktif edildiğinde bakım modunu otomatik kapattığımıza dikkat edin. Bu küçük ama kritik bir ayrıntı. Kaç kez gördüm: bakım bitti, eklenti deaktif edildi, bakım modu kapatılmadı, site saatlerce 503 döndü.
Çekirdek Sınıf: OBM_Bakim_Modu
<?php
defined( 'ABSPATH' ) || exit;
class OBM_Bakim_Modu {
private $ayarlar;
public function init() {
$this->ayarlar = get_option( 'obm_ayarlar', [] );
add_action( 'template_redirect', [ $this, 'bakim_kontrolu' ], 1 );
}
public function bakim_aktif_mi() {
return ! empty( $this->ayarlar['aktif'] );
}
public function bakim_kontrolu() {
if ( ! $this->bakim_aktif_mi() ) {
return;
}
// Admin kullanıcıları geçsin
if ( current_user_can( 'manage_options' ) ) {
$this->admin_bildirim_goster();
return;
}
// Cron jobları geçsin
if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
return;
}
// REST API isteklerini de filtreleyelim ama izin ver
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
return;
}
$this->bakim_sayfasi_goster();
}
private function admin_bildirim_goster() {
add_action( 'wp_footer', function() {
echo '<div style="position:fixed;bottom:20px;right:20px;background:#d63638;
color:#fff;padding:12px 18px;border-radius:6px;z-index:99999;
font-family:sans-serif;font-size:14px;">
⚠️ Bakım modu aktif! Ziyaretçiler bu siteyi göremiyor.</div>';
});
}
private function bakim_sayfasi_goster() {
// 503 header - SEO için kritik
header( 'HTTP/1.1 503 Service Unavailable' );
header( 'Status: 503 Service Unavailable' );
// Retry-After: tarayıcıya ve botlara "1 saat sonra tekrar gel" der
header( 'Retry-After: 3600' );
header( 'Content-Type: text/html; charset=UTF-8' );
$ayarlar = $this->ayarlar;
include OBM_PLUGIN_DIR . 'templates/bakim-sayfasi.php';
exit;
}
public function ayar_guncelle( $yeni_ayarlar ) {
$this->ayarlar = array_merge( $this->ayarlar, $yeni_ayarlar );
update_option( 'obm_ayarlar', $this->ayarlar );
}
public function ayar_al( $anahtar = null ) {
if ( $anahtar ) {
return $this->ayarlar[ $anahtar ] ?? null;
}
return $this->ayarlar;
}
}
Retry-After başlığı genellikle göz ardı edilen ama SEO açısından önemli bir detay. Google bu header’ı gördüğünde “bu sayfa geçici olarak erişilemez, dizini bozmayacağım, belirtilen süre sonra tekrar kontrol edeceğim” der. Bunu koymazsanız Google bakım sayfanızı dizine almaya başlayabilir.
Admin Paneli Sınıfı
<?php
defined( 'ABSPATH' ) || exit;
class OBM_Admin {
public function init() {
add_action( 'admin_menu', [ $this, 'menu_ekle' ] );
add_action( 'admin_post_obm_kaydet', [ $this, 'ayarlari_kaydet' ] );
add_action( 'admin_notices', [ $this, 'admin_notice_goster' ] );
}
public function menu_ekle() {
add_options_page(
'Bakım Modu Ayarları',
'Bakım Modu',
'manage_options',
'ozgun-bakim-modu',
[ $this, 'ayar_sayfasi' ]
);
}
public function ayar_sayfasi() {
$ayarlar = get_option( 'obm_ayarlar', [] );
$aktif = ! empty( $ayarlar['aktif'] );
?>
<div class="wrap">
<h1>Bakım Modu Ayarları</h1>
<?php if ( $aktif ) : ?>
<div class="notice notice-warning">
<p><strong>Bakım modu şu an aktif.</strong> Ziyaretçiler sitenizi göremiyor.</p>
</div>
<?php endif; ?>
<form method="post" action="<?php echo admin_url( 'admin-post.php' ); ?>">
<?php wp_nonce_field( 'obm_kaydet_nonce', 'obm_nonce' ); ?>
<input type="hidden" name="action" value="obm_kaydet">
<table class="form-table">
<tr>
<th>Bakım Modu</th>
<td>
<label>
<input type="checkbox" name="aktif" value="1"
<?php checked( $aktif ); ?>>
Bakım modunu aktifleştir
</label>
</td>
</tr>
<tr>
<th>Sayfa Başlığı</th>
<td>
<input type="text" name="baslik" class="regular-text"
value="<?php echo esc_attr( $ayarlar['baslik'] ?? '' ); ?>">
</td>
</tr>
<tr>
<th>Bakım Mesajı</th>
<td>
<textarea name="mesaj" rows="4" class="large-text"><?php
echo esc_textarea( $ayarlar['mesaj'] ?? '' );
?></textarea>
</td>
</tr>
<tr>
<th>Tahmini Bitiş Süresi</th>
<td>
<input type="datetime-local" name="sure"
value="<?php echo esc_attr( $ayarlar['sure'] ?? '' ); ?>">
<p class="description">Boş bırakırsanız geri sayım gösterilmez.</p>
</td>
</tr>
</table>
<?php submit_button( 'Kaydet' ); ?>
</form>
</div>
<?php
}
public function ayarlari_kaydet() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'Yetkiniz yok.' );
}
check_admin_referer( 'obm_kaydet_nonce', 'obm_nonce' );
$yeni = [
'aktif' => isset( $_POST['aktif'] ) ? true : false,
'baslik' => sanitize_text_field( $_POST['baslik'] ?? '' ),
'mesaj' => wp_kses_post( $_POST['mesaj'] ?? '' ),
'sure' => sanitize_text_field( $_POST['sure'] ?? '' ),
];
update_option( 'obm_ayarlar', $yeni );
wp_redirect( admin_url( 'options-general.php?page=ozgun-bakim-modu&guncellendi=1' ) );
exit;
}
public function admin_notice_goster() {
if ( isset( $_GET['guncellendi'] ) && $_GET['page'] === 'ozgun-bakim-modu' ) {
echo '<div class="notice notice-success is-dismissible">
<p>Ayarlar kaydedildi.</p></div>';
}
}
}
Burada wp_kses_post kullandık mesaj için çünkü admin kullanıcının bakım mesajına link veya kalın yazı ekleyebilmesini isteyebiliriz. sanitize_text_field ise bu zenginliği kaldırır. Güvenlik ile kullanılabilirliği dengelemek önemli.
Bakım Sayfası Şablonu
<?php defined( 'ABSPATH' ) || exit; ?>
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<title><?php echo esc_html( $ayarlar['baslik'] ?? 'Bakımda' ); ?></title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #0f0f0f;
color: #f0f0f0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.kart {
max-width: 540px;
text-align: center;
padding: 3rem 2rem;
}
.kart h1 { font-size: 2rem; margin-bottom: 1rem; font-weight: 700; }
.kart p { font-size: 1.1rem; color: #aaa; line-height: 1.7; margin-bottom: 2rem; }
.geri-sayim {
display: flex;
justify-content: center;
gap: 1.5rem;
margin-top: 2rem;
}
.gs-kutu {
background: #1e1e1e;
border-radius: 8px;
padding: 1rem;
min-width: 70px;
}
.gs-sayi { font-size: 2rem; font-weight: 800; display: block; }
.gs-etiket { font-size: 0.75rem; color: #666; margin-top: 4px; display: block; }
</style>
</head>
<body>
<div class="kart">
<h1><?php echo esc_html( $ayarlar['baslik'] ?? 'Çok Yakında' ); ?></h1>
<p><?php echo wp_kses_post( $ayarlar['mesaj'] ?? '' ); ?></p>
<?php if ( ! empty( $ayarlar['sure'] ) ) : ?>
<div class="geri-sayim" id="geri-sayim">
<div class="gs-kutu"><span class="gs-sayi" id="gs-gun">00</span><span class="gs-etiket">Gün</span></div>
<div class="gs-kutu"><span class="gs-sayi" id="gs-saat">00</span><span class="gs-etiket">Saat</span></div>
<div class="gs-kutu"><span class="gs-sayi" id="gs-dakika">00</span><span class="gs-etiket">Dakika</span></div>
<div class="gs-kutu"><span class="gs-sayi" id="gs-saniye">00</span><span class="gs-etiket">Saniye</span></div>
</div>
<script>
(function() {
var hedef = new Date("<?php echo esc_js( $ayarlar['sure'] ); ?>").getTime();
function guncelle() {
var simdi = new Date().getTime();
var fark = hedef - simdi;
if (fark <= 0) {
document.getElementById('geri-sayim').innerHTML = '<p>Çok yakında...</p>';
return;
}
var gun = Math.floor(fark / 86400000);
var saat = Math.floor((fark % 86400000) / 3600000);
var dakika = Math.floor((fark % 3600000) / 60000);
var saniye = Math.floor((fark % 60000) / 1000);
document.getElementById('gs-gun').textContent = String(gun).padStart(2,'0');
document.getElementById('gs-saat').textContent = String(saat).padStart(2,'0');
document.getElementById('gs-dakika').textContent = String(dakika).padStart(2,'0');
document.getElementById('gs-saniye').textContent = String(saniye).padStart(2,'0');
}
guncelle();
setInterval(guncelle, 1000);
})();
</script>
<?php endif; ?>
</div>
</body>
</html>
Şablonda meta name="robots" content="noindex, nofollow" satırını görüyorsunuz. Bu 503 başlığıyla birlikte çift güvence sağlıyor. Botlar hem başlık seviyesinde hem meta tag seviyesinde “bu sayfayı dizinleme” sinyali alıyor.
WP-CLI Entegrasyonu
Bu kısmı çoğu bakım eklentisi atlar ama bir otomasyon senaryosunda hayat kurtarır. Ansible ile deployment yapıyorsanız, maintenance window açıp kapatmak için web arayüzüne girmenizi gerektirmez.
<?php
defined( 'ABSPATH' ) || exit;
class OBM_WPCLI extends WP_CLI_Command {
/**
* Bakım modunu açar veya kapatır.
*
* ## KULLANIM
*
* wp bakim ac
* wp bakim kapat
* wp bakim durum
*
* @subcommand ac
*/
public function ac( $args, $assoc_args ) {
$ayarlar = get_option( 'obm_ayarlar', [] );
$ayarlar['aktif'] = true;
update_option( 'obm_ayarlar', $ayarlar );
WP_CLI::success( 'Bakım modu aktifleştirildi.' );
}
/**
* @subcommand kapat
*/
public function kapat( $args, $assoc_args ) {
$ayarlar = get_option( 'obm_ayarlar', [] );
$ayarlar['aktif'] = false;
update_option( 'obm_ayarlar', $ayarlar );
WP_CLI::success( 'Bakım modu devre dışı bırakıldı.' );
}
/**
* @subcommand durum
*/
public function durum( $args, $assoc_args ) {
$ayarlar = get_option( 'obm_ayarlar', [] );
$aktif = ! empty( $ayarlar['aktif'] );
if ( $aktif ) {
WP_CLI::warning( 'Bakım modu: AKTİF' );
} else {
WP_CLI::line( 'Bakım modu: PASİF' );
}
}
}
Kullanımı şu şekilde:
# Bakım modunu aç
wp bakim ac --path=/var/www/html/siteniz
# Deployment işlemleri...
# composer install, cache temizleme vs.
# Bakım modunu kapat
wp bakim kapat --path=/var/www/html/siteniz
# Durumu kontrol et
wp bakim durum --path=/var/www/html/siteniz
Bu üç komutla bakım modunuzu CI/CD pipeline’ınıza, Ansible playbooklarınıza veya basit bir bash scriptine gömebilirsiniz. WordPress admin paneline girmeden, tarayıcı açmadan.
Güvenlik ve Edge Case’ler
Prodüksiyona almadan önce birkaç noktayı daha konuşalım.
Login sayfasına ne olacak? template_redirect hook’u login sayfasında çalışmaz çünkü login süreci farklı bir akışla ilerler. Bu iyi çünkü admin kullanıcılar bakım modundayken giriş yapabilmeli. Ama bunu test etmeyi unutmayın.
Çoklu yönetici rolü seçeneği: Şu an sadece manage_options yetkisine bakıyoruz. Eğer “editör” rolündeki kullanıcıların da geçmesini istiyorsanız şöyle bir filtre ekleyebilirsiniz:
// class-bakim-modu.php içinde bakim_kontrolu() metoduna eklenecek
$bakim_gecer_roller = apply_filters( 'obm_gecer_roller', [ 'administrator' ] );
$kullanici = wp_get_current_user();
if ( array_intersect( $bakim_gecer_roller, $kullanici->roles ) ) {
$this->admin_bildirim_goster();
return;
}
apply_filters eklemek, başka eklentilerin veya functions.php‘nin bu listeyi değiştirebilmesini sağlar. Bu WordPress felsefesine uygun bir yaklaşım.
IP bazlı izin verme bazı senaryolarda lazım olabilir. Örneğin müşteriye “siteyi bakım modundayken önizleme linki vereyim” demek yerine müşterinin IP’sini beyaz listeye alabilirsiniz. Ama bunu çok dikkatli ele alın; IP bazlı erişim paylaşımlı ofis ortamlarında, VPN’li kullanıcılarda güvenilmez olabilir.
Eklentiyi Test Etmek
Eklentiyi geliştirirken şu senaryoları mutlaka test edin:
# 503 başlığını curl ile doğrulayın
curl -I https://siteniz.com
# Beklenen çıktı:
# HTTP/2 503
# retry-after: 3600
# content-type: text/html; charset=UTF-8
# Admin kullanıcı olarak girişten sonra siteyi ziyaret edin
# Bakım ekranı değil, normal site görünmeli
# Footer'da sarı uyarı banner'ı görünmeli
# WP-CLI ile durumu kontrol edin
wp bakim durum
Googlebot’un bakım modunu nasıl gördüğünü test etmek için Google Search Console’daki “URL İnceleme” aracını kullanabilirsiniz. Sayfayı Googlebot olarak çektiğinde 503 görüp görmediğini kontrol edin.
Sonuç
Bu eklentiyi 200 satır PHP ile sıfırdan yazdık ve şunları elde ettik: doğru HTTP başlıkları, SEO dostu 503 dönüşü, admin kullanıcı bypass’ı, özelleştirilebilir bakım sayfası, geri sayım sayacı ve WP-CLI entegrasyonu. Piyasadaki pek çok bakım eklentisi ya 503 yerine 200 döndürür (SEO felaketi) ya da WP-CLI desteği sunmaz (otomasyon felaketi).
Kendi eklentinizi yazmak, bir soruna tam olarak ihtiyacınız olan çözümü üretmenizi sağlar. Bakım modu gibi kritik bir fonksiyon için bağımlılığı azaltmak, hangi kodun çalıştığını bilmek ve deployment sürecinize entegre edebilmek her şeyin üstündedir. Bir dahaki bakım penceresinde bu eklentiyi deneyin; farkı göreceksiniz.
