| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- <?php
- namespace App\Models\Char;
- use Illuminate\Database\Eloquent\Model;
- class CharMail extends Model
- {
- protected $connection = 'Char';
- protected $table = 'Table_CharMail';
- protected $primaryKey = 'MailDBKey';
- protected $dates = ['CreateDate', 'ReadDate'];
- protected $fillable = [
- 'RecverDBKey',
- 'Slot',
- 'Deleted',
- 'MailDBKey',
- 'SentMailDBKey',
- 'SenderDBKeyAccount',
- 'SenderDBKey',
- 'SendName',
- 'RecvName',
- 'bRead',
- 'Title',
- 'MailType',
- 'CreateDate',
- 'ReadDate',
- 'Money',
- 'AlreadyMoney',
- 'Letter',
- 'ItemCount',
- 'ItemSlot0',
- 'ItemSlot1',
- 'ItemSlot2',
- 'ItemSlot3',
- 'ItemSlot4',
- 'AlreadyItem0',
- 'AlreadyItem1',
- 'AlreadyItem2',
- 'AlreadyItem3',
- 'AlreadyItem4',
- 'AlreadyItem5',
- 'AlreadyItem6',
- 'AlreadyItem7',
- 'AlreadyItem8',
- 'AlreadyItem9',
- 'Confirm',
- 'binLetter',
- 'MailSerial',
- 'binTitle',
- ];
- public $timestamps = false;
- public function getBinLetterAttribute($value)
- {
- return $this->decodeBinaryValue($value);
- }
- public function setBinLetterAttribute($value)
- {
- $this->attributes['binLetter'] = $this->encodeBinaryValue($value);
- }
- public function getBinTitleAttribute($value)
- {
- return $this->decodeBinaryValue($value);
- }
- public function setBinTitleAttribute($value)
- {
- $this->attributes['binTitle'] = $this->encodeBinaryValue($value);
- }
- private function decodeBinaryValue(?string $value): ?string
- {
- // Удаляем завершающие нули – они часто встречаются в char( n ) столбцах
- $value = $this->stripNulls($value);
- if ($value === null || $value === '') {
- return $value;
- }
- // Пробуем декодировать максимум два раза (схема хранения – двойной base64)
- $decoded = $value;
- for ($i = 0; $i < 2; $i++) {
- $tmp = base64_decode($decoded, true);
- if ($tmp === false) {
- // Строка не похожа на base64 – прекращаем попытки
- break;
- }
- $decoded = $this->stripNulls($tmp);
- }
- return $decoded === null ? null : $this->ensureUtf8($decoded);
- }
- private function encodeBinaryValue(?string $value): ?string
- {
- $value = $this->stripNulls($value);
- if ($value === null || $value === '') {
- return $value;
- }
- // Если строка уже дважды закодирована – ничего не делаем
- if ($this->isBase64($value)) {
- $inner = base64_decode($value, true);
- if ($inner !== false && $this->isBase64($inner)) {
- return $value; // уже двойной base64
- }
- // Если только один слой – добавляем ещё один
- return base64_encode($value);
- }
- // Конвертируем в CP1251 (игровой клиент ожидает именно эту кодировку)
- $prepared = @iconv('UTF-8', 'CP1251//IGNORE', $value);
- if ($prepared === false) {
- $prepared = $value; // если iconv не смог – используем как есть
- }
- // Двойное кодирование base64
- return base64_encode(base64_encode($prepared));
- }
- private function stripNulls(?string $value): ?string
- {
- return $value === null ? null : rtrim($value, "\0");
- }
- // Более лояльная проверка base64 (без строгого ===, учитываем отсутствие отступов и символы \r\n)
- private function isBase64(string $value): bool
- {
- if ($value === '') {
- return false;
- }
- // Проверяем набор допустимых символов и кратность 4
- if (preg_match('/^[A-Za-z0-9+\/\r\n]+=*$/', $value) !== 1) {
- return false;
- }
- return (strlen($value) % 4 === 0);
- }
- private function ensureUtf8(string $value): string
- {
- if ($value === '') {
- return '';
- }
- if (mb_check_encoding($value, 'UTF-8')) {
- return $this->removeControlChars($value);
- }
- $candidates = [];
- if (function_exists('iconv')) {
- $candidates[] = fn () => @iconv('CP1251', 'UTF-8//IGNORE', $value);
- }
- if (function_exists('mb_convert_encoding')) {
- $candidates[] = fn () => @mb_convert_encoding($value, 'UTF-8', 'CP1251');
- $candidates[] = fn () => @mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1');
- }
- $candidates[] = fn () => utf8_encode($value);
- foreach ($candidates as $candidate) {
- $converted = $candidate();
- if (is_string($converted) && mb_check_encoding($converted, 'UTF-8')) {
- return $this->removeControlChars($converted);
- }
- }
- if (function_exists('iconv')) {
- $sanitized = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
- if (is_string($sanitized) && mb_check_encoding($sanitized, 'UTF-8')) {
- return $this->removeControlChars($sanitized);
- }
- }
- return '';
- }
- private function removeControlChars(string $value): string
- {
- $clean = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $value);
- return is_string($clean) ? $clean : '';
- }
- public function toArray()
- {
- $array = parent::toArray();
- // Явно декодируем бинарные поля, на случай если accessor не сработал при сериализации
- if (array_key_exists('binTitle', $this->attributes)) {
- $array['binTitle'] = $this->decodeBinaryValue($this->attributes['binTitle']);
- } elseif (array_key_exists('binTitle', $array)) {
- $array['binTitle'] = $this->decodeBinaryValue($array['binTitle']);
- }
- if (array_key_exists('binLetter', $this->attributes)) {
- $array['binLetter'] = $this->decodeBinaryValue($this->attributes['binLetter']);
- } elseif (array_key_exists('binLetter', $array)) {
- $array['binLetter'] = $this->decodeBinaryValue($array['binLetter']);
- }
- array_walk_recursive($array, function (&$v) {
- if (is_string($v)) {
- $v = $this->ensureUtf8($v);
- }
- });
- return $array;
- }
- }
|