Eklenti Modülü Yazma
Eklenti, sabit bir görevi olmayan modül tipidir. Bir ayar formu, bir admin sayfası, bir müşteri sayfası ve bir kanca dosyası alır; oradan çekirdeğe dokunmadan ürünün her yerine uzanır.
Genel Bakış
Diğer her modül tipi tanımlı bir soruyu yanıtlar. Sunucu modülü hesap sağlar, ödeme modülü para tahsil eder, servis sağlayıcı modülü alan adı kaydeder. Eklenti belirli bir soruyu yanıtlamaz; en geniş ve en çok yanlış kullanılan tip olmasının sebebi budur.
coremio/modules/Addons altındaki dokuz modülün ortak yanı yalnızca taban sınıftır. Bir talep asistanı, canlı sohbet, kimlik doğrulama, iki muhasebe köprüsü, virüs tarayıcı, çeviri aracı, lisans yöneticisi ve kum havuzu örneği.
Adlar çakışıyor. Ürün ek hizmeti bir faturalama kavramıdır: müşterinin bir hizmetin yanında satın aldığı, satın alma akışıyla faturalanan ve etkinleştirilen bir ekstradır. Eklenti modülü ise kurduğunuz bir eklentidir. Bu makalede birincisinden hiç söz edilmez.
use_ ile başlayan bir metot panelin kendi dağıtıcısı üzerinden çağrılabilir; başka hiçbiri çağrılamaz.
Ön Koşullar
- Modül Anatomisi ve Modül Yapılandırması; bu makale yalnız eklentiye özgü kısımları anlatır.
- Neyi genişlettiğinizi bilin: yeni bir ekran için admin sayfası, davranış değişikliği için var olan bir kanca gerekir.
- İzleyiciye erken karar verin. Yönetim amaçlı bir eklenti müşteri sayfası açmamalıdır; bu, istek köprüsünü kimliği doğrulanmamış çağıranlara da açar.
Yapı
coremio/modules/Addons/Acme/
├── Acme.php sınıf: extends AddonModule
├── config.php künye, durum, erişim yetkileri, kaydedilmiş ayarlar
├── hooks.php isteğe bağlı: her istekte kaydedilen dinleyiciler
├── logo.png
├── lang/en.php $this->lang, künye bloğu dahil
├── views/ $this->view() ile render edilen şablonlar
│ ├── index.php admin genel bakışı
│ └── client.php müşteri sayfası
└── src/ kendi yardımcı sınıflarınız, düz new ile kurulur
return [
'created_at' => 1561714288,
'meta' => [
'name' => 'Acme',
'version' => '1.0',
'author' => 'Your name goes here',
'opening-type' => 'normal',
// Müşteri paneli menü simgesi. 'font' simgenin bir sınıf, 'image' bir yol ya da URL olduğunu söyler.
'icon_type' => 'font',
'icon' => 'bi bi-puzzle',
// İsteğe bağlı kısa adres: sayfa /addon/Acme yanında /{slug} adresinden de açılır.
'slug' => 'acme',
],
'show_on_adminArea' => true, // admin sayfasını bas
'show_on_clientArea' => true, // müşteri sayfasını ve menü girdisini bas
'status' => true, // açık; panel anahtarı buraya geri yazar
'access_ps' => [], // onu açmasına izin verilen admin yetkileri
'settings' => [], // ayar formunun yazdığı, kodunuzun okuduğu
];
| Erişim | Nasıl | Bunu yapan bir modül |
|---|---|---|
| Admin panelde bir sayfa | adminArea() artı bir görünüm | Ekranı olan her eklenti |
| Müşteri panelinde bir sayfa | clientArea() artı yapılandırma bayrağı | Kum havuzu örneği |
| Çekirdek ekranına işaretleme enjekte etme | Bir ui: kancası | Yapay zekâ asistanı, talep yanıt düzenleyicisinde |
| Zamanlanmış iş | register:cronjobs artı bir kuyruk işleyici sınıfı | Muhasebe köprüsü, fatura durumunu yoklarken |
| Çekirdek olayına tepki verme | Bir action: kancası | Muhasebe köprüsü, fatura resmileştiğinde |
| Kendi API uçları | filter:api.routes artı işleyici metotlar | Canlı sohbet aracı |
| Ek admin menü girdisi | register:admin.menu | Sohbet ve virüs tarayıcı |
| Çekirdek controller'a ek operation | register:admin.operations | Virüs tarayıcı ve muhasebe köprüsü |
Adım Adım
Sınıfı Yazın
İşi yapıcı üstlenir. Adı sınıftan türetir, dizini ve genel URL'i çözer, yapılandırmayı ve dil dosyasını yükler. $this->admin ile $this->user alanlarını da oturumdakinden doldurur.
- Sınıfa dizinle birebir aynı adı verin. Taban adı yansımayla türetir; uyuşmazlık, kurduğu her yolu bozar.
- Ayar formu için
fields()tanımlayın; formu diğer modül tipleriyle aynı alan motoru kurar. - Kurulum bir şey yapacaksa
enable()tanımlayın: tablo oluşturmak, satır tohumlamak, bir önkoşulu denetlemek.
Ayar Formunu Verin
Formu siz kurmazsınız, kaydetme yolunu da yazmazsınız. fields() tanımları döndürür, panel onları gösterir ve taban sınıf değerleri yapılandırma dosyanızdaki settings altına yazar.
fields()tanım haritasını döndürür; her kayıt mevcut değerini$this->config['settings']içinden okur.save_fields()isteğe bağlıdır: gönderilen değerleri alır, doğrular ve saklanacak diziyi döndürür. Sırları burada şifreleyin.settings_notice()isteğe bağlıdır: HTML döndürün, tüm alanların üstünde bir bant olarak görünür.
Sayfaları Ekleyin
İki sayfa metodu da aynı tanımı döndürür: bir başlık, kırıntı yolu ve içerik. Yönlendirme otomatiktir, kaydedilecek bir şey yoktur. Alt sayfalar bir görünüm dosyasını adlandıran istek parametresiyle sürülür; dosya yoksa geri düşülür.
Çekirdeğe Uzanın
Tipi işe yarar kılan kısım budur, disiplin de burada. Dinleyicileri hooks.php içine koyun ve eklentinin açık olmasına bağlayın. Bir kanca noktası modülünüzün adını asla bilmemelidir.
- Yapılandırmayı dosyanın başında ucuza yükleyin ve dinleyicileri bir durum denetimine sarın. Kuyruk işleyicilerini kaydetmek bilinçli istisnadır ve denetimin dışında kalır.
- Dinleyicileri kaydedin. İşaretleme üreten her şey bir closure içinde dursun ki onu göstermeyecek sayfa için hiçbir şey kurulmasın.
- İhtiyacınız olan nokta yoksa, çekirdeği yerinde düzenlemek yerine onu düzgün biçimde açın. Jenerik bir kanca artı koşulunuzun
hooks.phpiçinde durması desteklenen biçimdir.
Referans
Taban Sınıfın Verdikleri
class AddonModule
{
public string|bool $error = ''; // eski nesil; yeni kod bunun yerine fırlatır
public array $config = []; // config.php, ayrıştırılmış hali
public array $lang = []; // etkin dil için lang/{lang}.php
public string $area_link = ''; // eklentinin kendi sayfa adresi
public string $_name = ''; // dizin ve sınıf adı
public array $user = []; // oturum açmış müşteri, varsa
public array $admin = []; // oturum açmış yönetici, varsa
public string $url; // modül dizininin genel URL'i
public string $dir; // modül dizininin dosya sistemi yolu
protected string $cryptKey = 'system';
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;
protected function encode_str(string $str = '', string $key = ''): string;
protected function decode_str(string $str = '', string $key = ''): string;
public function use_default_settings($formElements = null);
public function isEnabled();
}
views/{$file} dosyasını, verilen değişkenler açılmış halde yükler. Dosya adını uzantısıyla geçin.
$cryptKey ile adlandırılan, sisteme bağlı anahtarla şifreler ve çözer; varsayılan sistem anahtarıdır. Boş boş kalır; başarısız çözme şifreli metni değil boş dize döndürür.
İsteğe Bağlı Metotlar ve Ne Zaman Koştukları
Bunların hiçbiri taban sınıfta yoktur. Her biri method_exists() ile yoklanır ve yoksa atlanır; yani sözleşme imzanın kendisidir.
// Ayar formu.
public function fields(): array;
public function save_fields($fields = []): array|bool; // saklanacak diziyi döndürün ya da fırlatın
public function settings_notice(): string; // alanların üstünde HTML bant
public function edit_settings_tab(\WISECP\Components\Tab $tab): void; // ayar sayfasına bir sekme ekler
// Kurulum yaşam döngüsü. false döndürmek durum değişikliğini iptal eder.
public function enable(): bool;
public function disable(): bool;
public function uninstall(): bool;
// Sayfalar. Her biri bir sayfa tanımı döndürür.
public function adminArea(): array;
public function clientArea(): array;
public function main(): string; // oturum açmamış ziyaretçiler için genel bir sayfa
// İstek köprüsü: yalnız use_ ön ekli bir metoda erişilebilir.
public function use_sample_method(): array|string;
| Metot | Ne zaman çalışır | false döndürmek ne demek |
|---|---|---|
| fields | Ayar ekranı açılırken ve kayıtta bir kez daha | geçerli değil |
| save_fields | Ayarlar kaydedilirken, hiçbir şey yazılmadan önce | Hata özelliğindeki mesajla iptal |
| enable | Operatör eklentiyi açtığında | Kapalı kalır |
| disable | Operatör eklentiyi kapattığında | Açık kalır |
| uninstall | Eklenti kaldırılırken | Kaldırma reddedilir |
| adminArea | Yönetici eklenti sayfasını açtığında | geçerli değil |
| clientArea | Oturum açmış bir müşteri eklenti sayfasını açtığında | geçerli değil |
| main | Bir ziyaretçi genel sayfayı açtığında | geçerli değil |
Sayfa Tanımı
return [
'page_title' => 'Acme',
'breadcrumbs' => [
['link' => $this->area_link, 'title' => 'Acme'],
['link' => '', 'title' => 'Reports'], // boş bir link geçerli sayfayı işaretler
],
// Yalnız admin. Sayfa başlığının yanına basılan düğmeler.
'page_title_buttons' => [
[
'outerHTML' => '', // doluysa aşağıdaki üç anahtarı devre dışı bırakır
'element' => 'button',
'attributes' => ['class' => 'btn btn-primary', 'onclick' => "acmeRefresh();"],
'content' => 'Refresh',
],
],
'content' => $this->view('index.php', $variables),
];
İstek Köprüsü
Bir eklentiye iki dağıtıcı ulaşır: biri admin oturumunun arkasında, biri genel sitede. İkisi de aynı kuralı uygular: istenen adın başına use_ eklenir ve başka hiçbir şey çağrılabilir değildir.
$method = (string) Filter::init("REQUEST/method", "route");
// Boşluk, tire ve nokta alt çizgiye normalleştirilir, sonra ön ek eklenir.
$method = "use_" . str_replace([' ', '-', '.'], '_', $method);
if (!method_exists($instance, $method))
throw new Exception("Module does not have a method named {$method}.");
$result = $instance->$method();
// Yanlış değerli bir dönüş başarısızlık sayılır; başarıda asla boş dizi döndürmeyin.
if (!$result) throw new Exception($instance->error ?: "Unknown error");
true, [] ve '' üçü de başarısızlık okunur ve exception'a döner.
Eklentilerin Gerçekten Kullandığı Kanca Noktaları
Örnek
Küçük ama eksiksiz bir eklenti: ayarlar, onu işe yarar kılan kanca dosyası ve ayarları geri okuyan kod. Kimsenin okumadığı bir ayar bu tipteki en yaygın kusurdur.
namespace WISECP\Modules\Addons;
use AddonModule;
use Exception;
use Filter;
class Acme extends AddonModule
{
public string $version = '1.0';
public function fields(): array
{
$settings = $this->config['settings'] ?? [];
return [
'api_key' => [
'name' => $this->lang['api-key'],
'description' => $this->lang['api-key-desc'],
'type' => 'password',
'wrap_width' => 100,
// Saklanan değeri yalnız maske olarak gösterin; gerçek anahtar şifreli kalır.
'value' => ($settings['api_key'] ?? '') !== '' ? '********' : '',
],
'notify' => [
'name' => $this->lang['notify'],
'type' => 'switch',
'wrap_width' => 100,
// Anahtar alanı 'value' değil 'checked' okur.
'checked' => (int) ($settings['notify'] ?? 0) === 1,
],
'threshold' => [
'name' => $this->lang['threshold'],
'type' => 'text',
'wrap_width' => 100,
'value' => $settings['threshold'] ?? '100',
// Yalnız üstteki anahtar açıkken görünür.
'parent' => 'notify',
'parentEffect' => 'hide',
],
];
}
public function save_fields($fields = []): array|bool
{
// Maske "değişmedi" demektir: saklanan değer neyse korunur.
if (($fields['api_key'] ?? '') === '********')
$fields['api_key'] = $this->config['settings']['api_key'] ?? '';
elseif (($fields['api_key'] ?? '') !== '')
$fields['api_key'] = $this->encode_str($fields['api_key']);
if ((int) ($fields['notify'] ?? 0) === 1 && (int) ($fields['threshold'] ?? 0) <= 0)
throw new Exception($this->lang['err-threshold']);
return $fields;
}
public function enable(): bool
{
// Kurulumun gerektirdiği ne varsa. false döndürmek eklentiyi kapalı bırakır.
return true;
}
public function adminArea(): array
{
$action = Filter::init("REQUEST/action", "route") ?: 'index';
if (!is_file($this->dir . 'views' . DS . $action . '.php')) $action = 'index';
return [
'page_title' => $this->lang['meta']['name'],
'breadcrumbs' => [['link' => '', 'title' => $this->lang['meta']['name']]],
'content' => $this->view($action . '.php', [
'link' => $this->area_link,
'name' => $this->lang['meta']['name'],
'version' => $this->config['meta']['version'],
]),
];
}
// ?operation=use_addon_method&method=refresh ile erişilir
public function use_refresh(): array
{
$id = (int) Filter::init("POST/id", "rnumbers");
if (!$id) throw new Exception($this->lang['err-id-required']);
// Başarıda asla boş dizi döndürmeyin: köprü yanlış değeri başarısızlık okur.
return ['status' => 'successful', 'id' => $id];
}
}
<?php
// Ucuz yükleme: sınıfı dahil etmeden yalnız yapılandırma. Bu dosya istisnasız
// her istekte koşar; eklenti kapalıyken neredeyse hiçbir maliyeti olmamalı.
Modules::Load('Addons', 'Acme', true);
$acme_config = Modules::Config('Addons', 'Acme') ?: [];
// Kuyruk işleyicileri durum denetiminin DIŞINDA, her sistemde kaydedilir: kuyrukta
// bekleyen bir iş, eklenti kapatıldıktan sonra da işleyici sınıfını bulabilmeli.
// Kapalı olmak yalnız bu tipte yeni iş gönderilmemesi demektir.
Hook::add('register:cronjobs', 1, function () {
require_once __DIR__ . DS . 'cronjobs' . DS . 'AcmeSync.php';
CronJobQueue::register(AcmeSync::TYPE, AcmeSync::class);
});
if ($acme_config['status'] ?? false) {
// Bir çekirdek olayına tepki verin. Örnek dinleyicinin dışında değil içinde kurulur,
// böylece hiç tetiklenmeyen bir eklenti bunun bedelini de hiç ödemez.
Hook::add('action:invoice.formalized', 1, function ($invoice, $userId = 0) {
$m = Modules::getInstance('Addons', 'Acme');
if (!$m) return;
$m->queue_invoice((int) ($invoice['id'] ?? 0));
});
// Çekirdek ekranına işaretleme enjekte edin.
Hook::add('ui:admin.tickets_detail.bottom', 1, function ($ticket) {
$m = Modules::getInstance('Addons', 'Acme');
return $m ? $m->ticket_panel($ticket) : '';
});
}
public function queue_invoice(int $invoiceId): void
{
if (!$invoiceId) return;
$settings = $this->config['settings'] ?? [];
// Asla empty(): saklanan bir "0" yok sayılır ve anahtarı sessizce açık gösterir.
if ((int) ($settings['notify'] ?? 0) !== 1) return;
// Yalnız kullanım anında çözün, asla bir özelliğin içine değil.
$apiKey = $this->decode_str($settings['api_key'] ?? '');
if ($apiKey === '') throw new Exception($this->lang['err-not-configured']);
$threshold = (int) ($settings['threshold'] ?? 0);
// ... faturayı sağlayıcıya teslim edin
}
Tuzaklar
Yapılandırmayı sınıfı dahil etmeyen hafif çağrıyla yükleyin. Örneği en üstte bir kez değil her dinleyicinin içinde kurun: dosya kapsamında API istemcisi kuran bir eklenti her sayfayı yavaşlatır.
Birleştirme yapmaz. Mevcut yapılandırmayı okuyun, anahtarlarınızı değiştirin, diziyi eksiksiz geri verin. Yalnız değiştirdiğinizi geçmek meta bloğunu, durum işaretini ve yetki listesini düşürür.
Yalnız admin sayfası olan bir eklentiye genel dağıtıcıdan erişilemez. Müşteri sayfası ya da genel sayfa eklemek bunu use_ metotlarının tamamı için değiştirir, yalnız açmak istedikleriniz için değil.
Dağıtıcı yanlış değerli her dönüşü başarısızlık sayar ve exception fırlatır. Bildirecek bir şeyi olmayan metot bile boş olmayan bir yük döndürür, örneğin bir durum anahtarı.
İhtiyacınız olan davranışa erişilemiyorsa jenerik bir kanca noktası açın ve koşulunuzu kendi kanca dosyanıza yazın. Modülünüzün adını anan bir çekirdek dosyası, bir sonraki güncellemenin üzerine yazacağı bir değişikliktir.
Örnek modül hâlâ hata özelliğine atama yapıp false döndüren eski deseni gösteriyor. Önceki neslin kalıntısıdır; üretim eklentileri bunun yerine fırlatır.
İlgili Makaleler
Geri bildiriminiz için teşekkürler!
Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.