Admin Sayfası Ekleme

1.8k görüntülenme Markdown

Bir modüle admin panelde kendi sayfasını iki dosyayla açarsınız. HTML'i bir alan sınıfı döndürür; router.php içindeki tek satır onu kaydeder.

Genel Bakış

Kendi kayıtlarını yöneten ya da toplu iş çalıştıran bir modülün gerçek bir sayfaya ihtiyacı vardır. O sayfa çekirdeğe ait değildir: sınıfı bildirir, kaydedersiniz; rota, menü öğesi, yetki kontrolü, künye çizgisi ve tema kabuğu kendiliğinden gelir. Yeniyseniz: İlk Modülünüz.

Dağıtıcı coremio/controllers/admin/module-page.php dosyasıdır; slug rotaları oraya çözülür ve sonuç eklenti tema sarmalayıcısına gider.

Yapı

classes/ModuleAdminArea.php Taban sınıf ve kayıt defteri: register(), rota kablolaması, menü kancası, örnek yardımcıları.
admin/module-page.php İç dağıtıcı: page_* ve op_* metotlarını çözer, URL'de görünmez.
tools/addons-area.php Tema sarmalayıcısı: başlık şeridi, butonlar, eklentiler, stiller, script'ler, modaller.
{modül}/AdminArea.php + router.php Yazdığınız iki dosya. Router::loadModuleRouters() router dosyasını rotalardan önce okur.

Adım Adım

Alan Sınıfını Bildirin

  1. Modül dizininizde AdminArea.php oluşturun, ad alanı WISECP\Modules\{Tip}\{Ad} olsun.
  2. \ModuleAdminArea sınıfını genişletin ve statik manifest() metodundan manifest'i döndürün.
  3. Sayfa yalnız bir koşulda anlamlıysa available() metodunu ezin; menü öğesini de kapılar.

Kaydedin

  1. Yanına router.php oluşturun: sınıf dosyasını include edip \ModuleAdminArea::register(AdminArea::class) çağırın.
  2. Oraya başka bir şey yazılmaz. Kayıt üç rota ekler, manifest'i saklar ve menü öğesini kancalar.

Sayfa Metodu Yazın

  1. page_home() alanın kökünü yanıtlar; her metot URL'in kalan parçalarını dizi olarak alır.
  2. HTML'i heredoc ile kurun; metinlerinizi $this->lang içinden okuyun.
  3. Kendinize verdiğiniz bağlantıları $this->link() ile kurun, elle yazılmış bir yolla değil.

Operation Yazın

  1. Metodu op_{ad} olarak adlandırın; yalnız bu önek çağrılabilir.
  2. Alan URL'ine bir operation parametresiyle POST edin; fırlatılan exception standart JSON hatasına dönüşür.
  3. $operation->demo() ile başlayın, girdiyi Filter::init() ile okuyun, $operation->output() ile bitirin.

Alan Ayarlarını Saklayın

  1. Kendi dosyanız değil, settings(), setting() ve save_settings().
  2. save_settings() mevcut değerlerle birleştirir, yani kısmi bir kayıt gerisini korur, sonra action:module.area_settings_saved kancasını çalıştırır.

Referans

Taban Sınıf API'si

tam imzalar
class ModuleAdminArea
{
    public string $module_type = '';   // ad alanından çözülür
    public string $module_name = '';   // ad alanından çözülür
    public array  $lang        = [];   // {module}/lang/{selected}.php, en.php'ye düşerek

    public function __construct();

    // Ezeceğiniz metot budur. Varsayılanı olan diğer her şey isteğe bağlıdır.
    public static function manifest(): array;

    public static function register(string $areaClass): void;
    public static function get(string $type, string $name): ?array;

    public function available(): bool;                                  // varsayılan true
    public function slug(): string;
    public function link(array $params = []): string;
    public function module_dir(): string;                               // dosya sistemi yolu, sonda ayraçla
    public function module_url(): string;                               // modül dizininin genel URL'i

    public function settings(): array;
    public function setting(string $key, mixed $default = null): mixed;
    public function save_settings(array $values): void;

    protected function license_state_badge(string $slug): string;
}
On altı değil, on bir modül tipi

Kabul edilen tipler: Servers, Payment, Registrars, Product, Addons, SMS, Mail, Authentication, Pipe, Imports ve Fraud. Diğerleri için register() sessizce hiçbir şey yapmaz.

Manifest Neyi Kabul Eder

title Sayfa ve tarayıcı başlığı; menu.name yoksa menü etiketi.
slug URL parçası, varsayılanı modül adının küçük harfli hâli. Filter::route() yalnız a-zA-Z0-9, tire, alt çizgi ve noktayı korur.
privileges Yetki anahtarları, alanın tamamı için bir kez denetlenir. Boş bırakmak kontrolü kaldırır.
menu ['path' => ['PRODUCTS', 'GROUP_HOSTING_SERVER'], 'name' => 'Hetzner Cloud']. Yol ağaçta aşağı yürür; yazmazsanız öğe oluşmaz.
type, name, class register() bunları ad alanından ve argümandan yazar. Siz atamayın.

URL Şeması ve Metot Çözümlemesi

üç rota
// Üç rota, en özgülü başta. $target değeri module-page/{Type}/{Name} olur.
$router->add($slug . '-2', $slug . '/(?)/(?)', $target . '/(1)/(2)');
$router->add($slug . '-1', $slug . '/(?)',     $target . '/(1)');
$router->add($slug,        $slug,              $target);

// link() metodunun rota anahtarını parametre sayısından türetmesinin sebebi budur:
//   $this->link()                     -> /{admin}/{slug}
//   $this->link(['configuration'])    -> /{admin}/{slug}/configuration
//   $this->link(['logs', 'archive'])  -> /{admin}/{slug}/logs/archive
İstekÇağrılan metotArgüman
GET /{admin}/{slug}page_home()boş dizi
GET /{admin}/{slug}/configurationpage_configuration()['configuration']
GET /{admin}/{slug}/logs/archivepage_logs_archive() / page_logs()['logs', 'archive']
POST + operation=sync_pricesop_sync_prices(Operation $operation)Operation
eşleşen metot yok404 sayfasıhiçbir şey görünmez

Bir parçadaki tire, nokta ve virgül alt çizgiye dönüşür, yani /{slug}/price-list adresi page_price_list() metoduna çözülür. Operation adı da aynı dönüşümden geçer.

Sayfa Metodu Ne Döndürebilir

Dize sayfa gövdesi olur. Dizi, aşağıdaki her anahtarı sarmalayıcıya geçirir; yazmadığınız anahtar gösterilen varsayılana düşer.

AnahtarTipEtkisi
contentstringSayfa gövdesi. Boşsa yerine No module area content is available. uyarısı görünür.
page_titlestringManifest başlığını ezer.
page_title_buttonsarrayButon tanımı listesi; düz HTML işe yaramaz.
page_title_afterstringBaşlık ve butonlardan sonra serbest HTML.
page_title_logostringBir görsel adresi, ya da < içeriyorsa ham HTML.
content_layoutstringVarsayılan panel; plain paneli kaldırır.
content_data_classstringİçerik sarmalayıcısına ek sınıf (plain yerleşimi).
breadcrumbsarrayPano + alan başlığı izinin sonuna eklenir.
pluginsarraySarmalayıcı varsayılanlarıyla birleşir.
page_styles, page_scripts, modalsstringHead, footer ve modal alanı.

Oturumdaki Yönetici

imza
public static function LoginData($type = 'member', $isRemembered = false, $recheck = false);
alan metodu içinde
$adminId = (int) (\UserManager::LoginData('admin')['id'] ?? 0);   // panel dışında 0 (CLI, cron)

// Bir atama alanı için operatör seçici.
$staff = \Admin::list();          // [id => ['id' => 1, 'full_name' => 'Jane Doe'], ...]

Örnek

Hayali bir Acme modülü için iki dosyalık alan.

Acme/AdminArea.php
<?php
namespace WISECP\Modules\Servers\Acme;

use AdminFormBuilder;
use Exception;
use Filter;
use Operation;
use User;
use UserManager;
use WDB;

class AdminArea extends \ModuleAdminArea
{
    public static function manifest(): array
    {
        return [
            'title'      => 'Acme',
            // 'slug'    => 'acme',                  // varsayılan: strtolower(modül adı)
            'privileges' => ['PRODUCTS_OPERATION'],
            'menu'       => ['path' => ['PRODUCTS', 'GROUP_HOSTING_SERVER'], 'name' => 'Acme'],
        ];
    }

    /** Acme sunucusu yokken menü öğesini gizler ve sayfayı 404 yapar. */
    public function available(): bool
    {
        static $available = null;

        if ($available === null)
            $available = (bool) WDB::select('id')->from('servers')
                ->where('type', '=', 'Acme', '&&')
                ->where('status', '=', 'active')
                ->build();

        return $available;
    }

    public function page_home(array $params): array
    {
        $L    = $this->lang;
        $conf = $this->settings();

        $form = new AdminFormBuilder('acmeAreaForm', $this->link(), ['disableStickySubmit' => true]);
        $form->addHidden('operation', 'save_settings');
        $form->addAmount('profit_rate', $L['profit-rate'] ?? '', (string) ($conf['profit_rate'] ?? 25));
        $form->addSwitch('auto_sync', $L['auto-sync'] ?? '', $L['auto-sync-desc'] ?? '', '1', (int) ($conf['auto_sync'] ?? 0) === 1);

        return [
            'content'          => $form->render(),
            'page_title'       => $L['area-title'] ?? 'Acme',
            'page_title_after' => $this->license_state_badge('acme'),
        ];
    }

    public function op_save_settings(Operation $operation): bool
    {
        $operation->demo();

        $values = [
            'profit_rate' => (float) Filter::init('POST/profit_rate', 'amount'),
            'auto_sync'   => (int) Filter::init('POST/auto_sync', 'rnumbers') === 1,
        ];

        if ($values['profit_rate'] < 0)
            throw new Exception($this->lang['err-negative-rate'] ?? 'The profit rate cannot be negative.');

        $this->save_settings($values);

        $adminId = (int) (UserManager::LoginData('admin')['id'] ?? 0);
        User::addAction($adminId, 'update', 'acme-settings-updated', ['changes' => $values]);

        return $operation->output([
            'status'   => 'successful',
            'redirect' => 'reload',
        ]);
    }
}
Acme/router.php
<?php
namespace WISECP\Modules\Servers\Acme;

include_once __DIR__ . DS . 'AdminArea.php';

\ModuleAdminArea::register(AdminArea::class);

Okuma tarafı dağıtıcıdır; metodunuza ulaşılıp ulaşılmayacağına o karar verir:

module-page.php
$area_info = ModuleAdminArea::get($type, $name);
if (!$area_info) return $this->page_404();

if (($area_info['privileges'] ?? []) && !Admin::isPrivilege($area_info['privileges'])) return 'Access Denied';

$area = new $area_info['class']();
if (!$area->available()) return $this->page_404();

if ($operation = Filter::init('REQUEST/operation')) return $this->area_operation($area, $operation);

$page    = Filter::route($this->params[2] ?? '') ?: 'home';
$subpage = Filter::route($this->params[3] ?? '');

$filter_method = fn ($param) => str_replace(['-', '.', ','], '_', $param);
$method        = $filter_method('page_' . $page);            // page_logs
$method2       = $filter_method($method . '_' . $subpage);   // page_logs_archive

$page_params = array_slice($this->params, 2);

if ($subpage && method_exists($area, $method2)) $result = $area->$method2($page_params);
elseif (method_exists($area, $method))          $result = $area->$method($page_params);
else return $this->page_404();

if (!is_array($result)) $result = ['content' => (string) $result];

Tuzaklar

Sessizce sonlanan sayfa metodu yönlendirme değil ölümcül hata sorunudur

Tanımsız bir property ya da unutulmuş bir import hiç çıktı üretmez ve Exception üzerine kurulu try/catch bunu yakalamaz, çünkü Error bir Exception değildir. En sık vaka Admin::$data['id']'dir: öyle bir statik property yoktur, operatörü UserManager::LoginData('admin') ile okuyun.

Çekirdek rotasıyla çakışan slug bağlantı üretimini bozar

Gerçek controller dosyaları dağıtımda kazanır, yani services olarak kaydedilen alana ulaşılamaz ve o anahtarın bağlantıları ezilir. Çekirdeğin kullanmadığı bir slug seçin.

Eksik bir ebeveyn menü öğesini tek kelime etmeden gizler

Menü kancası ağaçta aşağı yürür ve bir adım yoksa döner. Ebeveyn dal yetkilerle budanmışsa öğeniz eklenmez, sayfa URL ile erişilebilir kalır.

Sistem lisansı etkin değilken operation'lar bloklanır

Alan dağıtıcısı metodunuzu aramadan önce lisansı denetler ve JSON hatasıyla yanıtlar. Sayfalar view katmanında etkilenir.

Alan ayarları modül adı altında yaşar

Configurations tablosundaki satır anahtarı, dizinin yazdığı hâliyle modül adıdır; dizini yeniden adlandırmak ayarları öksüz bırakır. Taşımayı settings() ezerek yapın.

Faydalı oldu mu?

Geri bildiriminiz için teşekkürler!

Hâlâ Yardıma mı İhtiyacınız Var?

Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.