Pipe Modülü Yazma
Pipe modülü bir destek posta kutusunu okur ve normalleştirilmiş bir mesaj dizisi döndürür; cron zinciri onu talebe ve yanıta çevirir.
Genel Bakış
Pipe, destek talebi e-posta alım tipidir. Üç modül gelir: Google, Microsoft ve Pop3. Taban sınıf yoktur.
Yapılandırma birimi modül değil departmandır; bağlantı durumuna dokunan her metot bir departman kimliği alır.
İstek döngüsünde modülünüze yalnız ayar ekranı ve OAuth geri çağrısı dokunur. Okumayı cron yapar: keşif getirme işini başlatır, getirme inbox() çağırır, üçüncü iş talebi oluşturur.
Ön Koşullar
coremio/modules/Pipe/altına yazma erişimi.- Erişilebilir bir posta kutusu ve
imapeklentisi ya da OAuth uygulaması. - Önceden oluşturulmuş destek talebi departmanları.
- Zamanlanmış Görev Ekleme.
Yapı
Pipe sınıfları ad alanlıdır: WISECP\Modules\Pipe. Cron bu adı sağlayıcı dizesinden kurar.
coremio/modules/Pipe/Acme/
├── Acme.php namespace WISECP\Modules\Pipe; class Acme
├── config.php genel sağlayıcı bloğu + departman id başına bir blok
├── lang/en.php alan etiketleri, description, setup-guide
├── lang/tr.php
├── logo.svg
└── views/
├── credentialsForm.php departman başına alanlar, ayarlar sekmesine render edilir
└── providerForm.php yalnız OAuth uygulamaları: genel client id ve secret
OAuth yönlendiricisi paylaşılandır ve her parçayı bir modüle eşler.
Adım Adım
Modül Sınıfını Kurma
coremio/modules/Pipe/Acme/Acme.phpdosyasınınamespace WISECP\Modules\Pipe;veclass Acmeile oluşturun.- Sınıfa dizinle eşleşen public bir
$nameverin. save_config(array $data)özyinelemeli birleştirme olmalı.is_connected(int $did)uygulayın; false diyen departman atlanır.inbox(int $did)uygulayın.test_connection(int $did)uygulayın.
Departman Formu
views/credentialsForm.phpoluşturun;$mv,$mkve$didalır.- Alanları
module[{modülAnahtarı}][{did}][{alan}]olarak adlandırın. - Mevcut değerleri
$mv["init"]->config[$did], etiketleri$mv["lang"]içinden okuyun. - Uzun anlatım
setup-guideanahtarına konur.
OAuth Ekleme
is_configured()bildirin; varlığı sağlayıcı kartının anahtarıdır.get_global_config(),save_provider_config()veviews/providerForm.phpekleyin.- Yönlendirme adresini paylaşılan rota anahtarıyla üretin:
LinkGenerator::client('pipe-oauth-callback', [strtolower($this->name)]). - Yetkilendirme adresini döndüren
oAuth2(int $did)vecallback_handle()ekleyin. - Parçanızı yönlendiriciye kaydedin.
clear_tokens(int $did)ekleyin.
Referans
Metot Sözleşmesi
Her biri çağrılmadan önce method_exists ile yoklanır.
| Metot | Çağıran | Yoksa ne olur |
|---|---|---|
inbox(int $did): array | getirme cron'u | iş inbox-missing ile düşer |
is_connected(int $did): bool | keşif cron'u | departman yine başlatılır |
save_config(array $data) | ayar kaydetme | alanlar saklanmaz |
test_connection($did): array | ayar ekranı | test butonu çalışmaz |
is_configured(): bool | ayar ekranı | sağlayıcı kartı çıkmaz |
get_global_config(): array | ayar ekranı | kart boş görünür |
save_provider_config($id, $secret): bool | sağlayıcı kaydetme | kaydetme istisna fırlatır |
oAuth2($did): array | ayar ekranı | bağlan butonu çalışmaz |
callback_handle(): array | paylaşılan OAuth rotası | işleyici bulunamaz |
clear_tokens($did) | ayar ekranı | bağlantı kesilemez |
get_connected_email($did): string | kimlik bilgisi formu | posta kutusu gösterilmez |
Panelden yalnız oAuth2, test_connection ve clear_tokens çağrılabilir.
İmzalar
public function __construct();
// Özyinelemeli birleştirme, sonra config.php yeniden yazılır. Düz bir atama diğer departmanları siler.
public function save_config($data = []);
// TEK bir departmanın hazırlığı. Token'lar var, ya da hostname+username+password dolu.
public function is_connected(int $did): bool;
// Hattın olmadan yapamayacağı tek metot. Dönüş biçimi aşağıda.
public function inbox(int $did): array;
// ['status' => 'successful'] ya da ['status' => 'error', 'message' => '...'].
public function test_connection($did = 0): array;
// Yalnız OAuth modülleri.
public function is_configured(): bool;
public function get_global_config(): array; // client_id, client_secret, redirect_uri
public function save_provider_config(string $client_id, string $client_secret_plain): bool;
public function oAuth2($did = 0): array; // ['status' => 'successful', 'redirect' => $url]
public function callback_handle(): array; // ['status' => 'connected', 'did' => 4, 'email' => '...']
public function clear_tokens($did = 0);
public function get_connected_email(int $did): string;
Asıl sözleşme mesaj dizisidir; eksik anahtar sessizce eksilir.
return [
'status' => 'successful', // başka her şey yumuşak hata sayılır
'data' => [
[
'ip' => '203.0.113.9', // başlıklar taşıyorsa gönderen IP'si, yoksa ''
'date' => '2026-08-03 09:41:00', // Y-m-d H:i:s, yinelenen özetinde kullanılır
'subject' => 'Cannot reach my panel', // talebin konusu olur
'spam' => false, // true işleyicinin mesajı düşürmesini sağlar
'msgid' => '', // isteğe bağlı; boşken kararlı bir özet üretilir
'from' => ['name' => 'Ada L.', 'address' => '[email protected]'],
'to' => ['name' => 'Support', 'address' => '[email protected]'],
'message' => '<p>The panel times out.</p>',
'attachments' => [
[
'file_name' => 'screenshot.png', // gönderenin kullandığı ad
'name' => 'a1b2c3d4e5f6.png', // rastgeleleştirilmiş saklanan ad
'file_ext' => 'png',
'size' => 20481,
'content' => 'iVBORw0KGgoAAAANS', // ham baytların base64'ü
],
],
],
],
];
// Hatada ya fırlatın ya da yumuşak hata biçimini döndürün:
return ['status' => 'error', 'message' => 'Cannot connect to server', 'data' => []];
Fırlatmak devre kesiciyi kurar ve işi başarısız kaydeder; dönen error durumu işi iptal işaretler. Yapılandırma için döndürün, taşıma için fırlatın.
Config Düzeni
config.php modül geneli için dize, departman durumu için tam sayı anahtar taşır. save_config() bu yüzden birleştirmelidir.
<?php
return [
'lookback_days' => 3, // modül geneli
'provider' => [ // modül geneli, yalnız OAuth uygulamaları
'client_id' => '...',
'client_secret' => '...', // crypt/system ile Crypt::encode
],
4 => [ // departman 4
'tokens' => '...', // token JSON'unun Crypt::encode'u
'email' => '[email protected]',
],
7 => [ // departman 7, kimlik bilgisi tabanlı
'protocol' => 'imap',
'hostname' => 'mail.example.com',
'port' => 993,
'username' => '[email protected]',
'password' => '...',
'ssl' => true,
],
];
Posta kutusu ile departman eşlemesi paylaşılan seçenekler dosyasındadır:
pipe-disabled ile iptal döner.
provider, from, fname satırına eşler.
REF.
Config::save("options", Config::set("options", ['ticket-pipe' => $ticketPipe])).
Cron Zinciri
Hattı üç kuyruk işleyicisi kurar:
| İşleyici | Ne yapar | Modülünüze dokunduğu yer |
|---|---|---|
| discover | Departmanları gezer, her biri için bir getirme işi başlatır | is_connected() ve sınıf kontrolü |
| fetch | Mesajları toplar, her biri için bir mesaj işi başlatır | inbox() |
| message | Mesajı talebe ya da yanıta çevirir | hiçbir şey; dizinizi okur |
Dört koşul keşfin bir departmanı hatasız atlamasına yol açar: sınıf yok, is_connected() false, getirme havada, ya da bekleme penceresi. Pencere üç saattir.
Örnek
Kimlik bilgisi tabanlı bir modül, sonra onu çağıran cron kodu.
<?php
namespace WISECP\Modules\Pipe;
class Acme
{
public $name = "Acme";
public $config = [];
public $lang = [];
public $test = false;
public function __construct()
{
$this->config = \Modules::Config("Pipe", $this->name);
$this->lang = \Modules::Lang("Pipe", $this->name);
}
// Özyinelemeli birleştirme: ayar ekranı her seferinde tek departman gönderir.
public function save_config($data = [])
{
$merged = array_replace_recursive($this->config ?: [], $data);
$this->config = $merged;
return \FileManager::file_write(__DIR__ . DS . "config.php", \Utility::array_export($merged, ['pwith' => true]));
}
public function is_connected(int $did): bool
{
$cfg = $this->config[$did] ?? null;
if (!is_array($cfg)) return false;
return !empty($cfg['hostname']) && !empty($cfg['username']) && !empty($cfg['password']);
}
public function test_connection($did = 0): array
{
try {
// Form module[Acme][{did}][field] olarak gönderir, okunacak yol budur.
$host = trim((string) \Filter::init("POST/module/" . $this->name . "/" . $did . "/hostname"));
$user = trim((string) \Filter::init("POST/module/" . $this->name . "/" . $did . "/username"));
$pass = (string) \Filter::init("POST/module/" . $this->name . "/" . $did . "/password", "password");
if ($host === '' || $user === '' || $pass === '')
throw new \Exception($this->lang["credentials-required"] ?? 'Hostname, username and password are required.');
$this->open($host, $user, $pass);
}
catch (\Exception $e) {
return ['status' => "error", 'message' => $e->getMessage()];
}
return ['status' => "successful"];
}
public function inbox(int $did): array
{
$cfg = $this->config[$did] ?? null;
// Yapılandırma sorunu yumuşak hatadır: iş başarısız değil iptal edilir.
if (!$cfg) return ['status' => 'error', 'message' => 'Department config not found', 'data' => []];
// Taşıma sorunu fırlatır: kesici açılır ve iş başarısız olarak kaydedilir.
$session = $this->open($cfg['hostname'] ?? '', $cfg['username'] ?? '', $cfg['password'] ?? '');
$lookback = max(1, (int) ($this->config['lookback_days'] ?? 3));
$since = date('Y-m-d', strtotime('-' . $lookback . ' days'));
$messages = [];
foreach ($this->unread($session, $since) as $raw) {
$messages[] = [
'ip' => (string) ($raw['sender_ip'] ?? ''),
'date' => \DateManager::format("Y-m-d H:i:s", $raw['date'] ?? ''),
'subject' => (string) ($raw['subject'] ?? ''),
'spam' => false,
'from' => ['name' => (string) ($raw['from_name'] ?? ''), 'address' => (string) ($raw['from'] ?? '')],
'to' => ['name' => (string) ($raw['to_name'] ?? ''), 'address' => (string) ($raw['to'] ?? '')],
'message' => (string) ($raw['html'] ?? ($raw['text'] ?? '')),
'attachments' => $this->attachments($raw['parts'] ?? []),
];
}
return ['status' => "successful", 'data' => $messages];
}
}
// new değil getInstance: önce Modules::Load koşar ve modül config önbelleğini doldurur.
// Çıplak bir new ile yapıcı Modules::Config içinden null okur ve inbox() metodu
// "Department config not found" diyerek çıkar.
$module = \Modules::getInstance("Pipe", $provider);
if (!$module) return ['success' => false, 'result' => ['reason' => 'module-missing']];
if (!method_exists($module, 'inbox')) return ['success' => false, 'result' => ['reason' => 'inbox-missing']];
try {
$response = $module->inbox($did);
}
catch (\Throwable $e) {
self::set_cooldown($did, $e->getMessage());
Admin::notify('ticket-pipe-failure', self::failure_payload($did, $provider, $depName, $depFrom, $e->getMessage()), 'error', [
'dedupe_keys' => ['did', 'provider'],
'recurring' => true,
]);
throw $e;
}
$status = (string) ($response['status'] ?? '');
$data = (array) ($response['data'] ?? []);
if ($status !== 'successful') {
self::set_cooldown($did, (string) ($response['message'] ?? 'Unknown module error'));
// ... bildir, sonra iptal edilmiş bir iş döndür
}
// Temiz koşu: bekleyen hata bildirimini düşür ve kesiciyi serbest bırak.
Admin::notify_resolve('ticket-pipe-failure', ['did' => $did, 'provider' => $provider]);
self::clear_cooldown($did);
Modüller mesaj kimliği döndürmüyor; getirme işleyicisi departman, gönderen, tarih ve konudan özet üretir.
Tuzaklar
Her şey options/ticket-pipe altındadır. Benzer adlı üst seviye anahtar şikâyetsiz kaydedilir ve geri okunur, cron ise eski değeri görür.
Ayar gönderimi yalnız düzenlenen departmanı taşır. Atama yapan bir save_config() diğer departmanların kimlik bilgilerini siler; tek belirti o kutuların yoklanmamasıdır.
Modules::getInstance() önce yükleyiciyi çalıştırıp config önbelleğini doldurur. Çıplak bir new config'i null bırakır; hata inbox() içinde çıkar.
Devre kesici, bozuk bir kutunun dakika başı bildirim çıkarmasını engeller. Test sırasında düzeltmeniz işe yaramamış görünür: departman cooldown ile atlanır. Getirme işini doğrudan yeniden başlatın.
OAuth token'ları ve client secret'lar Crypt::encode($value, Config::get("crypt/system")) ile saklanır: kullanıcı anahtarı değil, sistem anahtarı. Token JSON'unu kodlanmış saklayın.
İ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.