Müşteri Paneli Köprüsü

1.8k görüntülenme Markdown

Müşterinin tarayıcısından eklenti modülünüzün bir metodunu çağırırsınız. Operation zaten var: kendinize ait controller, rota ya da uç kaydetmezsiniz.

Genel Bakış

Müşteri panelindeki bir eklenti sayfası, modülünüzün ürettiği HTML'dir. Sonra bir şey yapması gerekir: tercih kaydetme, durum çekme, iş başlatma. Website tarafında hazır bağlanmış tek bir operation var ve adı use_ ile başlayan her metodu çağırır.

Admin panelde de aynı köprü var, tools controller'ının arkasında. Fark, kimin geçmesine izin verildiğidir; bu makalenin güvenlik kısmı da o farktır.

Ön Koşullar

  • Yapılandırmasında status açık olan bir Addon modülü; kapalı bir eklenti köprü için görünmezdir.
  • Bir web yüzü: müşteri paneli tercihi (show_on_clientArea artı clientArea()) ya da public bir main() sayfası. Biri yoksa köprü her çağrıyı reddeder.
  • coremio/modules/Addons/SampleAddon, iki yüzün de çalışan bir gösterimi.

Yapı

controllers/website/addon.php Website yüzü: /addon/{Ad} adresini çözer, sayfayı üretir ve köprü operation'ını dağıtır.
operations/ClientAddon.php use_addon_method operation'ını ve eklentinizin web yüzü olup olmadığına karar veren kapıyı taşıyan trait.
operations/AdminTools.php Admin ikizi: aynı operation adı ve önek kuralı, yönetici oturumunun arkasında.
AddonModule Taban sınıfınız. $area_link, $error, $config, $lang, $dir, $url ve view() verir.
bir çağrı, uçtan uca
tarayıcı POST /addon/MyAddon   operation=use_addon_method & method=save-preference
   |
   +-- addon controller  ->  addon_ctx()     etkin yapılandırma + modül örneği + web yüzü?
   +-- ClientAddon       ->  yüz müşteri paneliyse üye oturumu gerekir
   +-- ad normalize      ->  'save-preference'  use_save_preference'a dönüşür
   +-- method_exists     ->  yoksa reddedilir
   +-- $module->use_save_preference()          HİÇBİR argüman verilmeden çağrılır
   +-- falsy dönüş       ->  Exception($module->error)
   '-- dizi ya da dize   ->  JSON gövdesi

Adım Adım

Eklentiye Bir Web Yüzü Verin

  1. config.php içinde show_on_clientArea değerini açın ve ['page_title' => …, 'breadcrumbs' => …, 'content' => …] döndüren bir clientArea() metodu ekleyin. Bu, /addon/{Ad} adresini müşteri sayfası yapar ve giriş yapmış bir üye ister.
  2. Ya da public bir sayfa için main() metodu ekleyin; o sayfaya hiçbir oturum garantisi verilmez.
  3. Güzel bir adres için meta.slug ayarlayın; controller $area_link değerini ona göre yazar.

use_ Metodunu Yazın

  1. Modül sınıfında use_{bir_şey} olarak adlandırın; başka hiçbir şey çağrılabilir değildir ve dağıtım katmanındaki sınırın tamamı bu önektir.
  2. Parametre almayın. Girdiyi tıpkı bir operation gibi kendiniz Filter::init("POST/…") ile okuyun.
  3. Boş olmayan bir dizi ya da dize döndürün; falsy dönüş başarısızlık sayılır ve $this->error taşıyan bir exception fırlatır.
  4. Gerçek bir hata için fırlatın: çağıran taraf bunu standart hata zarfına çevirir.

Sayfadan Çağırın

  1. Controller'ın doğru adrese yönelttiği $this->area_link adresine POST edin. Yolu elle yazmayın.
  2. operation=use_addon_method ve method={öneksiz ad} gönderin, metodunuzun okuduğu diğer alanlarla birlikte.
  3. Website'te X-Requested-With başlığıyla düz fetch, admin panelde WcpRequest kullanın.

Referans

İki Uç

YüzAdresKim geçer
müşteri paneli/addon/{Ad} ya da yapılandırılmış sluggiriş yapmış bir üye, eklenti tercih ettiyse
public sayfa/addon/{Ad}herkes, oturumsuz
eski takma ad/addon/{Ad}/clientmüşteri paneliyle aynı; tercih yoksa 404
admin paneltools eklenti adresitools yetkisi olan bir yönetici
website'ten çağrılan salt-admin eklentiyukarıdakilerden herhangi birikimse: operation Addon not found. fırlatır
Web yüzü kontrolü gerçek bir güvenlik sınırıdır

O kontrol yokken salt-admin bir eklentinin use_* metotlarına website'ten oturumsuz ulaşılabiliyordu. Ayarlar eziliyor, talep verisi okunuyor, ücretli API kredisi yakılıyordu. Kapıyı dış duvar sayın, tek duvar değil.

Operation ve Ad Kuralı

imza
public function use_addon_method(Operation $operation): bool;
ad normalizasyonu
$method = (string) Filter::init("REQUEST/method", "route");        // a-zA-Z0-9 - _ . korunur
$method = "use_" . str_replace([' ', '-', '.'], '_', $method);     // boşluk, tire, nokta -> alt çizgi

if ($method === "use_" || !method_exists($module, $method))
    throw new \Exception("Undefined addon method.");

$result = $module->{$method}();                                     // argüman YOK
if (!$result) throw new \Exception((string) (($module->error ?? '') ?: 'An error occurred'));

return $operation->output($result);

Yani save-preference, save.preference ve save_preference üçü de use_save_preference() metoduna ulaşır. route filtresi en başta çalışır ve yalnız a-zA-Z0-9, tire, alt çizgi ile noktayı korur; bu yüzden bir ad başka bir sınıfa kaçamaz.

Metodunuz Ne Döndürmeli

imza
public function use_sample_method(): array|string;
DöndürdüğünüzMüşterinin aldığıNe için
boş olmayan dizio dizi, JSON olarakolağan durum; standart anahtarları koruyun
boş olmayan dizedize, olduğu gibi yazılırDOM'a yerleştirilecek hazır HTML parçası
[], '', false ya da null$this->error değerinden kurulan {"status":"error","message":"…"}hiçbir şey: eski bir yol, buna göre tasarlamayın
fırlatılan exception{"status":"error","message":"mesajınız"}her gerçek hata

Hedeflenecek zarf panelin geri kalanıyla aynıdır:

hedeflenecek biçim
return [
    'status'  => 'successful',
    'message' => $this->lang['saved'] ?? 'Saved.',
    'data'    => ['preference' => $value, 'updated_at' => time()],
];

Taban Sınıfın Size Verdikleri

tam imzalar
class AddonModule
{
    public string|bool $error = '';        // metodunuz yanlış değer döndürünce köprü bunu okur
    public array  $config    = [];         // kaydedilmiş ayarlarla birleştirilmiş config.php
    public array  $lang      = [];         // lang/{selected}.php
    public string $area_link = '';         // POST edilecek adres; her yüze göre YENİDEN YAZILIR
    public string $_name     = '';         // dizin adı
    public array  $user      = [];         // oturum açmış üye, varsa
    public array  $admin     = [];         // oturum açmış yönetici, varsa
    public string $url       = CORE_FOLDER . DS . MODULES_FOLDER . DS . 'Addons' . DS;
    public string $dir;                    // modül dizininin dosya sistemi yolu
    // Kurucu $url sonuna {Name} ekleyip onu public bir URL'e çözer; $dir, $config, $lang,
    // $user ve $admin alanlarını doldurur ve $area_link değerini o anki yüze yöneltir.

    public function __construct();
    protected function view($file = '', $variables = []): string;
    public function privileges();
    public function save_settings($pFields, $accessPs): bool;
    public function change_addon_status($arg = '');
    public function save_config($data = []): bool;
    public function use_default_settings($formElements = null);
    public function isEnabled();
}
area_link sabit tek bir değer değildir

Panelde tools eklenti adresini gösterir. Website'te eklenti controller'ı onu public adresle değiştirir. Yol kurmak yerine onu gösterin.

Çekirdek Verisi Okuma

imza
// $groupAction, rota kayıt defterindeki "Group/Action" değeridir; müşteri kayıt defteri için
// başına "client:" ekleyin, o durumda $input ayrıca owner_id taşımalıdır.
public static function internal(string $groupAction, array $input = [], array $query = []): array;
use_ metodu içinde
use WISECP\Api\Kernel;

// Süreç içinde: HTTP yok, kimlik doğrulama yok, hız sınırı yok; ama dış API'nin döndürdüğü
// zarfın AYNISI. $input önce yol parametrelerini, sonra gövdeyi doldurur.
$resp    = Kernel::internal('Tickets/GetTicketMessages', ['id' => $ticketId], ['limit' => 50]);
$replies = $resp['data'] ?? [];

Bunu doğrudan yardımcı sınıf çağırmaya tercih edin: kaynak katmanı satırları çözülmüş, izin listesinden geçmiş ve normalize edilmiş verir.

Örnek

Tek bir müşteri tercihini saklayan bir müşteri sayfası.

MyAddon.php
<?php
namespace WISECP\Modules\Addons\MyAddon;

use Exception;
use Filter;
use Language;
use User;
use UserManager;

class MyAddon extends \AddonModule
{
    /** config.php içinde show_on_clientArea true olduğunda /addon/MyAddon adresinde basılır. */
    public function clientArea(): array
    {
        $member = UserManager::LoginData('member');

        return [
            'page_title'  => $this->lang['meta']['name'] ?? 'My Addon',
            'breadcrumbs' => [['link' => '', 'title' => $this->lang['meta']['name'] ?? 'My Addon']],
            'content'     => $this->view('client.php', [
                'link'       => $this->area_link,                                  // buraya POST edin
                'preference' => (string) (User::getInfo((int) $member['id'], ['my_addon_pref'])['my_addon_pref'] ?? ''),
            ]),
        ];
    }

    /**
     * method=save-preference ile erişilir. Parametresizdir: girdisini tıpkı bir operation
     * gibi kendisi okur ve hesabı, isteğin gönderdiği hiçbir şeye güvenmek yerine
     * oturumdan yeniden okur.
     *
     * @throws Exception
     */
    public function use_save_preference(): array
    {
        $member = UserManager::LoginData('member');
        if (!$member) throw new Exception(Language::gc('website/index/addon-login-required') ?: 'Login required.');

        $value = Filter::init('POST/preference', 'route');
        if ($value === '' || !in_array($value, ['daily', 'weekly', 'never'], true))
            throw new Exception($this->lang['err-bad-preference'] ?? 'Choose one of the offered options.');

        User::AddInfo((int) $member['id'], ['my_addon_pref' => $value]);

        return [
            'status'  => 'successful',
            'message' => $this->lang['saved'] ?? 'Saved.',
            'data'    => ['preference' => $value],
        ];
    }
}
views/client.php
<select id="ma-pref" class="form-select">
    <option value="daily">Daily</option>
    <option value="weekly">Weekly</option>
    <option value="never">Never</option>
</select>
<button type="button" id="ma-save" class="btn btn-primary mt-2">Save</button>

<script>
(function () {
    var link = ;
    var btn  = document.getElementById('ma-save');
    var sel  = document.getElementById('ma-pref');
    if (!btn || !sel) return;

    btn.addEventListener('click', function () {
        btn.disabled = true;

        var body = new URLSearchParams({
            operation:  'use_addon_method',
            method:     'save-preference',   // tire sorun değil, use_save_preference olur
            preference: sel.value
        });

        fetch(link, {
            method:  'POST',
            headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded' },
            body:    body
        })
        .then(function (r) { return r.json(); })
        .then(function (r) {
            if (r.status === 'successful') window.WCPTheme && window.WCPTheme.toast(r.message, 'success');
            else window.WCPTheme && window.WCPTheme.toast(r.message, 'error');
        })
        .finally(function () { btn.disabled = false; });
    });
})();
</script>

Aynı çağrının admin sayfasından yapılışı:

admin ikizi
WcpRequest(AREA_LINK, {
    method: 'POST',
    data:   { operation: 'use_addon_method', method: 'save-preference', preference: 'weekly' },
    button: runBtn,
    buttonLoader: window.saving_loader,
    done: function (response) {           // `done` yazmazsanız standart işleme uygulanır
        output.textContent = JSON.stringify(response, null, 2);
    }
});

Tuzaklar

Public sayfa tüm use_ metotlarını da public yapar

Web yüzü main() olan bir eklentiye, eski modüllerle uyum gereği tasarım olarak oturumsuz erişilir. Her metot için "bunu kim çağırabilmeli" diye sorun ve o kontrolü metoda yazın.

İstekle gelen hesap kimliğine güvenmeyin

Köprü ziyaretçiyi doğrular, kaydı değil. Müşteri kimliğini gövdeden okuyan bir metot, isteyene başka bir müşterinin verisini verir. Hesabı üye oturumundan okuyun.

Boş ama başarılı bir sonuç hata olarak okunur

Haklı olarak boş dönen bir sorgudan sonra [] döndürmek, hata özelliğinde ne varsa onu taşıyan bir hata zarfı üretir; çoğu zaman boş bir mesaj. Veri alanı boş liste olan bir dizi döndürün.

Ayar operation'ı üzerinden kaydetmek durumu ve erişim yetkilerini ezer

Standart ayar kaydı, alanlarınızla birlikte durum işaretini ve erişim yetkisi listesini de yazar; onu yeniden kullanan özel bir kaydetme eklentinizi kapatabilir. Özel kaydetmeye ayrı bir use_ metodu verin.

Kapalı eklenti hiçbir şey yanıtlamaz

Bağlam çözücü örneği kurmadan önce etkinlik işaretine bakar, bu yüzden kapalı bir eklentiye yapılan çağrı metot hatası değil Addon not found. ile düşer. Önce durum işaretini kontrol edin.

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.