'datetime',
'promotion_until' => 'datetime',
'pos_state_until' => 'datetime',
'last_action' => 'datetime',
];
public static $commentFields = [
'id', 'name', 'added', 'visible', 'banned', 'owner', 'sp_state', 'promotion_time_type', 'promotion_until', 'pos_state',
'hr', 'picktype', 'picktime', 'last_action', 'leechers', 'seeders', 'times_completed', 'views', 'size', 'cover', 'anonymous',
'approval_status', 'pos_state_until', 'category', 'source', 'medium', 'codec', 'standard', 'processing', 'team', 'audiocodec',
'price',
];
public static $basicRelations = [
'basic_category', 'basic_audio_codec', 'basic_codec', 'basic_media',
'basic_source', 'basic_standard', 'basic_team',
];
const POS_STATE_STICKY_NONE = 'normal';
const POS_STATE_STICKY_FIRST = 'sticky';
/**
* alphabet 'r' is after 'n' and before 's', so it will fit: order by pos_state desc,
* first sticky, then r_sticky, then normal
*/
const POS_STATE_STICKY_SECOND = 'r_sticky';
public static $posStates = [
self::POS_STATE_STICKY_NONE => ['text' => 'Normal', 'icon_counts' => 0],
self::POS_STATE_STICKY_SECOND => ['text' => 'Sticky second', 'icon_counts' => 1],
self::POS_STATE_STICKY_FIRST => ['text' => 'Sticky first', 'icon_counts' => 2],
];
const HR_YES = 1;
const HR_NO = 0;
public static $hrStatus = [
self::HR_NO => ['text' => 'NO'],
self::HR_YES => ['text' => 'YES'],
];
const PROMOTION_NORMAL = 1;
const PROMOTION_FREE = 2;
const PROMOTION_TWO_TIMES_UP = 3;
const PROMOTION_FREE_TWO_TIMES_UP = 4;
const PROMOTION_HALF_DOWN = 5;
const PROMOTION_HALF_DOWN_TWO_TIMES_UP = 6;
const PROMOTION_ONE_THIRD_DOWN = 7;
public static array $promotionTypes = [
self::PROMOTION_NORMAL => [
'text' => 'Normal',
'up_multiplier' => 1,
'down_multiplier' => 1,
'color' => ''
],
self::PROMOTION_FREE => [
'text' => 'Free',
'up_multiplier' => 1,
'down_multiplier' => 0,
'color' => 'linear-gradient(to right, rgba(0,52,206,0.5), rgba(0,52,206,1), rgba(0,52,206,0.5))'
],
self::PROMOTION_TWO_TIMES_UP => [
'text' => '2X',
'up_multiplier' => 2,
'down_multiplier' => 1,
'color' => 'linear-gradient(to right, rgba(0,153,0,0.5), rgba(0,153,0,1), rgba(0,153,0,0.5))'
],
self::PROMOTION_FREE_TWO_TIMES_UP => [
'text' => '2X Free',
'up_multiplier' => 2,
'down_multiplier' => 0,
'color' => 'linear-gradient(to right, rgba(0,153,0,1), rgba(0,52,206,1)'
],
self::PROMOTION_HALF_DOWN => [
'text' => '50%',
'up_multiplier' => 1,
'down_multiplier' => 0.5,
'color' => 'linear-gradient(to right, rgba(220,0,3,0.5), rgba(220,0,3,1), rgba(220,0,3,0.5))'
],
self::PROMOTION_HALF_DOWN_TWO_TIMES_UP => [
'text' => '2X 50%',
'up_multiplier' => 2,
'down_multiplier' => 0.5,
'color' => 'linear-gradient(to right, rgba(0,153,0,1), rgba(220,0,3,1)'
],
self::PROMOTION_ONE_THIRD_DOWN => [
'text' => '30%',
'up_multiplier' => 1,
'down_multiplier' => 0.3,
'color' => 'linear-gradient(to right, rgba(65,23,73,0.5), rgba(65,23,73,1), rgba(65,23,73,0.5))'
],
];
const PICK_NORMAL = 'normal';
const PICK_HOT = 'hot';
const PICK_CLASSIC = 'classic';
const PICK_RECOMMENDED = 'recommended';
public static array $pickTypes = [
self::PICK_NORMAL => ['text' => self::PICK_NORMAL, 'color' => ''],
self::PICK_HOT => ['text' => self::PICK_HOT, 'color' => '#e78d0f'],
self::PICK_CLASSIC => ['text' => self::PICK_CLASSIC, 'color' => '#77b300'],
self::PICK_RECOMMENDED => ['text' => self::PICK_RECOMMENDED, 'color' => '#820084'],
];
const PROMOTION_TIME_TYPE_GLOBAL = 0;
const PROMOTION_TIME_TYPE_PERMANENT = 1;
const PROMOTION_TIME_TYPE_DEADLINE = 2;
public static array $promotionTimeTypes = [
self::PROMOTION_TIME_TYPE_GLOBAL => ['text' => 'Global'],
self::PROMOTION_TIME_TYPE_PERMANENT => ['text' => 'Permanent'],
self::PROMOTION_TIME_TYPE_DEADLINE => ['text' => 'Until'],
];
const BONUS_REWARD_VALUES = [50, 100, 200, 500, 1000];
const APPROVAL_STATUS_NONE = 0;
const APPROVAL_STATUS_ALLOW = 1;
const APPROVAL_STATUS_DENY = 2;
public static array $approvalStatus = [
self::APPROVAL_STATUS_NONE => [
'text' => 'None',
'badge_color' => 'primary',
'icon' => '',
],
self::APPROVAL_STATUS_ALLOW => [
'text' => 'Allow',
'badge_color' => 'success',
'icon' => '',
],
self::APPROVAL_STATUS_DENY => [
'text' => 'Deny',
'badge_color' => 'danger',
'icon' => '',
],
];
const NFO_VIEW_STYLE_DOS = 'magic';
const NFO_VIEW_STYLE_WINDOWS = 'latin-1';
public static array $nfoViewStyles = [
self::NFO_VIEW_STYLE_DOS => ['text' => 'DOS-vy'],
self::NFO_VIEW_STYLE_WINDOWS => ['text' => 'Windows-vy'],
];
public function getPickInfoAttribute()
{
$info = self::$pickTypes[$this->picktype] ?? null;
if ($info) {
$info['text'] = nexus_trans('torrent.pick_info.' . $this->picktype);
return $info;
}
}
public function getPromotionInfoAttribute()
{
return self::$promotionTypes[$this->sp_state_real] ?? null;
}
public function getSpStateRealTextAttribute()
{
$spStateReal = $this->sp_state_real;
return self::$promotionTypes[$spStateReal]['text'] ?? '';
}
public function getSpStateRealAttribute()
{
if ($this->getRawOriginal('sp_state') === null) {
throw new \RuntimeException('no select sp_state field');
}
$spState = $this->sp_state;
$global = get_global_sp_state();
$log = sprintf('torrent: %s sp_state: %s, global sp state: %s', $this->id, $spState, $global);
if ($global != self::PROMOTION_NORMAL) {
$spState = $global;
$log .= sprintf(", global != %s, set sp_state to global: %s", self::PROMOTION_NORMAL, $global);
}
if (!isset(self::$promotionTypes[$spState])) {
$log .= ", but now sp_state: $spState, is invalid, reset to: " . self::PROMOTION_NORMAL;
$spState = self::PROMOTION_NORMAL;
}
do_log($log, 'debug');
return $spState;
}
protected function getPosStateTextAttribute()
{
$text = nexus_trans('torrent.pos_state_' . $this->pos_state);
if ($this->pos_state != Torrent::POS_STATE_STICKY_NONE) {
if ($this->pos_state_until) {
$append = format_datetime($this->pos_state_until);
} else {
$append = nexus_trans('label.permanent');
}
$text .= "($append)";
}
return $text;
}
protected function approvalStatusText(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => nexus_trans('torrent.approval.status_text.' . $attributes['approval_status'])
);
}
protected function spStateText(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => self::$promotionTypes[$this->sp_state]['text'] ?? ''
);
}
public static function getFieldsForList($appendTableName = false): array|bool
{
$fields = 'id, sp_state, promotion_time_type, promotion_until, banned, picktype, pos_state, category, source, medium, codec, standard, processing, team, audiocodec, leechers, seeders, name, small_descr, times_completed, size, added, comments,anonymous,owner,url,cache_stamp, hr, approval_status, cover, price';
$fields = preg_split('/[,\s]+/', $fields);
if ($appendTableName) {
foreach ($fields as &$value) {
$value = "torrents." . $value;
}
}
return $fields;
}
public static function listApprovalStatus($onlyKeyValue = false, $valueField = 'text'): array
{
$result = self::$approvalStatus;
$keyValue = [];
foreach ($result as $status => &$info) {
$text = nexus_trans("torrent.approval.status_text.$status");
$info['text'] = $text;
$keyValue[$status] = $info[$valueField];
}
if ($onlyKeyValue) {
return $keyValue;
}
return $result;
}
public static function listPromotionTypes($onlyKeyValue = false, $valueField = 'text'): array
{
$result = self::$promotionTypes;
$keyValue = [];
foreach ($result as $status => &$info) {
$text = $info['text'];
$info['text'] = $text;
$keyValue[$status] = $info[$valueField];
}
if ($onlyKeyValue) {
return $keyValue;
}
return $result;
}
public static function listPromotionTimeTypes($onlyKeyValue = false, $valueField = 'text'): array
{
return self::listStaticProps(self::$promotionTimeTypes, 'torrent.promotion_time_types', $onlyKeyValue, $valueField);
}
public static function listPickInfo($onlyKeyValue = false, $valueField = 'text'): array
{
$result = self::$pickTypes;
$keyValue = [];
foreach ($result as $status => &$info) {
$text = nexus_trans('torrent.pick_info.' . $status);
$info['text'] = $text;
$keyValue[$status] = $info[$valueField];
}
if ($onlyKeyValue) {
return $keyValue;
}
return $result;
}
public function getHrRealAttribute(): string
{
$searchBoxId = $this->basic_category->mode ?? 0;
if ($searchBoxId == 0) {
do_log(sprintf('[INVALID_CATEGORY], Torrent: %s, category: %s invalid', $this->id, $this->category), 'error');
return self::HR_NO;
}
$hrMode = HitAndRun::getConfig('mode', $searchBoxId);
if ($hrMode == HitAndRun::MODE_GLOBAL) {
return self::HR_YES;
}
if ($hrMode == HitAndRun::MODE_DISABLED) {
return self::HR_NO;
}
return $this->getRawOriginal('hr');
}
public function getHrTextAttribute()
{
return self::$hrStatus[$this->hr] ?? '';
}
public function getTagsFormattedAttribute(): string
{
$html = [];
foreach ($this->tags as $tag) {
$html[] = sprintf(
'%s',
$tag->font_color, $tag->color, $tag->border_radius, $tag->font_size, $tag->padding, $tag->margin, $tag->name
);
}
return implode('', $html);
}
public static function listPosStates($onlyKeyValue = false, $valueField = 'text'): array
{
$result = self::$posStates;
$keyValues = [];
foreach ($result as $key => &$value) {
$value['text'] = nexus_trans('torrent.pos_state_' . $key);
$keyValues[$key] = $value[$valueField];
}
if ($onlyKeyValue) {
return $keyValues;
}
return $result;
}
public static function getFieldLabels(): array
{
$fields = [
'comments', 'times_completed', 'peers_count', 'thank_users_count', 'numfiles', 'bookmark_yes', 'bookmark_no',
'reward_yes', 'reward_no', 'reward_logs', 'download', 'thanks_yes', 'thanks_no'
];
$result = [];
foreach($fields as $field) {
$result[$field] = nexus_trans("torrent.show.{$field}_label");
}
return $result;
}
public function checkIsNormal(array $fields = ['visible', 'banned'])
{
if (in_array('visible', $fields) && $this->getAttribute('visible') != self::VISIBLE_YES) {
throw new \InvalidArgumentException(sprintf('Torrent: %s is not visible.', $this->id));
}
if (in_array('banned', $fields) && $this->getAttribute('banned') == self::BANNED_YES) {
throw new \InvalidArgumentException(sprintf('Torrent: %s is banned.', $this->id));
}
return true;
}
public function getSubCategoryLabel($field): string
{
return $this->basic_category->search_box->getTaxonomyLabel($field);
}
public function bookmarks(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Bookmark::class, 'torrentid');
}
public function user()
{
return $this->belongsTo(User::class, 'owner')->withDefault(User::getDefaultUserAttributes());
}
public function thanks()
{
return $this->hasMany(Thank::class, 'torrentid');
}
public function thank_users()
{
return $this->belongsToMany(User::class, 'thanks', 'torrentid', 'userid');
}
/**
* 同伴
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function peers()
{
return $this->hasMany(Peer::class, 'torrent');
}
/**
* 完成情况
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function snatches()
{
return $this->hasMany(Snatch::class, 'torrentid');
}
public function upload_peers()
{
return $this->peers()->where('seeder', Peer::SEEDER_YES);
}
public function download_peers()
{
return $this->peers()->where('seeder', Peer::SEEDER_NO);
}
public function finish_peers()
{
return $this->peers()->where('finishedat', '>', 0);
}
public function files()
{
return $this->hasMany(File::class, 'torrent');
}
public function basic_category()
{
return $this->belongsTo(Category::class, 'category');
}
public function basic_source()
{
return $this->belongsTo(Source::class, 'source');
}
public function basic_medium()
{
return $this->belongsTo(Media::class, 'medium');
}
public function basic_codec()
{
return $this->belongsTo(Codec::class, 'codec');
}
public function basic_standard()
{
return $this->belongsTo(Standard::class, 'standard');
}
public function basic_processing()
{
return $this->belongsTo(Processing::class, 'processing');
}
public function basic_team()
{
return $this->belongsTo(Team::class, 'team');
}
public function basic_audiocodec()
{
return $this->belongsTo(AudioCodec::class, 'audiocodec');
}
public function claim_users(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(User::class, 'claims', 'torrent_id');
}
public function claims()
{
return $this->hasMany(Claim::class, 'torrent_id');
}
public function scopeVisible($query, $visible = self::VISIBLE_YES)
{
$query->where('visible', $visible);
}
public function scopeNormal($query)
{
$query->where('visible', self::VISIBLE_YES)->where('banned', self::BANNED_NO);
}
public function torrent_tags(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(TorrentTag::class, 'torrent_id');
}
public function tags(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Tag::class, 'torrent_tags', 'torrent_id', 'tag_id')
->orderByRaw(sprintf("field(`tags`.`id`,%s)", TagRepository::getOrderByFieldIdString()));
}
public function reward_logs(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Reward::class, 'torrentid');
}
public function operationLogs(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(TorrentOperationLog::class, 'torrent_id');
}
public function extra(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(TorrentExtra::class, 'torrent_id');
}
}