*/ protected $guarded = []; /** * @var array */ protected static $cbpm = ['collections', 'binaries', 'parts', 'missed_parts']; // @phpstan-ignore property.defaultValue /** * @var array */ protected static $cbppTableNames; /** * @var bool */ protected $allasmgr; /** * Group constructor. * * @throws \Exception */ public function __construct() { parent::__construct(); } /** * @return HasMany */ public function release(): HasMany { return $this->hasMany(Release::class, 'groups_id'); } /** * Returns an associative array of groups for list selection. * * @return array */ public static function getGroupsForSelect(): array { $groups = self::getActive(); $temp_array = []; $temp_array[-1] = '--Please Select--'; foreach ($groups as $group) { $temp_array[$group['name']] = $group['name']; } return $temp_array; } /** * Get all properties of a single group by its ID. * * * @return Model|null|static */ public static function getGroupByID(mixed $id) { return self::query()->where('id', $id)->first(); } public static function getActive(): mixed { return self::query()->where('active', '=', 1)->orderBy('name')->get(); } /** * Get active backfill groups ordered by name ascending. */ public static function getActiveBackfill(mixed $order): mixed { return match ($order) { '', 'normal' => self::query()->where('backfill', '=', 1)->where('last_record', '<>', 0)->orderBy('name')->get(), 'date' => self::query()->where('backfill', '=', 1)->where('last_record', '<>', 0)->orderByDesc('first_record_postdate')->get(), default => [], }; } /** * Get all active group IDs. */ public static function getActiveIDs(): mixed { return self::query()->where('active', '=', 1)->orderBy('name')->get(['id']); } /** * Get all group columns by Name. * * * @return Model|null|static */ public static function getByName(mixed $grp) { return self::query()->where('name', $grp)->first(); } /** * Get a group name using its ID. * * @param int|string $id The group ID. * @return string Empty string on failure, groupName on success. */ public static function getNameByID($id): string { $res = self::query()->where('id', $id)->first(['name']); return $res !== null ? $res->name : ''; } /** * Get a group ID using its name. * * @param string $name The group name. * @return false|int false on failure, groups_id on success. */ public static function getIDByName(string $name) { $res = self::query()->where('name', $name)->first(['id']); return $res === null ? false : $res->id; } /** * Gets a count of all groups in the table limited by parameters. * * @param string $groupname Constrain query to specific group name * @param int $active Constrain query to active status * @return mixed */ public static function getGroupsCount(string $groupname = '', int $active = -1) { $res = self::query(); if ($groupname !== '') { $res->where('name', 'like', '%'.$groupname.'%'); } if ($active > -1) { $res->where('active', $active); } return $res === null ? 0 : $res->count('id'); } public static function getGroupsRange(string $groupname = '', mixed $active = null): LengthAwarePaginator // @phpstan-ignore missingType.generics { $groups = self::query() ->select([ 'id', 'name', 'description', 'first_record_postdate', 'last_record_postdate', 'last_updated', 'active', 'backfill', 'minfilestoformrelease', 'minsizetoformrelease', 'backfill_target', ]) ->orderBy('name'); if ($groupname !== '') { $groups->where('name', 'like', '%'.$groupname.'%'); } if ($active === true) { $groups->where('active', '=', 1); } elseif ($active === false) { $groups->where('active', '=', 0); } return $groups->paginate(config('nntmux.items_per_page')); } /** * Update an existing group. */ public static function updateGroup(mixed $group): int { return self::query()->where('id', $group['id'])->update( [ 'name' => trim($group['name']), 'description' => trim($group['description']), 'backfill_target' => $group['backfill_target'], 'first_record' => $group['first_record'], 'last_record' => $group['last_record'], 'last_updated' => now(), 'active' => $group['active'], 'backfill' => $group['backfill'], 'minsizetoformrelease' => $group['minsizetoformrelease'] === '' ? null : $group['minsizetoformrelease'], 'minfilestoformrelease' => $group['minfilestoformrelease'] === '' ? null : $group['minfilestoformrelease'], ] ); } /** * Checks group name is standard and replaces any shorthand prefixes. * * @param string $groupName The full name of the usenet group being evaluated * @return string|bool The name of the group replacing shorthand prefix or false if groupname was malformed */ public static function isValidGroup(string $groupName) { if (preg_match('/^([\w\-]+\.)+[\w\-]+$/i', $groupName)) { return preg_replace('/^a\.b\./i', 'alt.binaries.', $groupName, 1); } return false; } /** * Add a new group. * * * @return int|mixed */ public static function addGroup(mixed $group) { $checkOld = UsenetGroup::query()->where('name', trim($group['name']))->first(); if (empty($checkOld)) { return self::query()->insertGetId([ 'name' => trim($group['name']), 'description' => isset($group['description']) ? trim($group['description']) : '', 'backfill_target' => $group['backfill_target'] ?? 1, 'first_record' => $group['first_record'] ?? 0, 'last_record' => $group['last_record'] ?? 0, 'active' => $group['active'] ?? 0, 'backfill' => $group['backfill'] ?? 0, 'minsizetoformrelease' => $group['minsizetoformrelease'] ?? null, 'minfilestoformrelease' => $group['minfilestoformrelease'] ?? null, ]); } return $checkOld->id; } /** * Delete a group. * * @param int|string $id ID of the group. * * @throws \Exception */ public static function deleteGroup($id): bool { self::purge($id); return self::query()->where('id', $id)->delete(); } /** * Reset a group. * * @param string|int $id The group ID. * * @throws \Exception */ public static function reset($id): int { // Remove rows from part repair. MissedPart::query()->where('groups_id', $id)->delete(); // Reset the group stats. return self::query()->where('id', $id)->update( [ 'backfill_target' => 1, 'first_record' => 0, 'first_record_postdate' => null, 'last_record' => 0, 'last_record_postdate' => null, 'last_updated' => null, 'active' => 0, ] ); } /** * Reset all groups. */ public static function resetall(): int { // Disable foreign key checks to allow truncating tables with foreign key constraints DB::statement('SET FOREIGN_KEY_CHECKS=0'); try { // Truncate tables in reverse order to respect foreign key relationships // (child tables first: missed_parts, parts, binaries, then parent: collections) foreach (array_reverse(self::$cbpm) as $tablePrefix) { DB::statement("TRUNCATE TABLE {$tablePrefix}"); } } finally { // Always re-enable foreign key checks, even if truncate fails DB::statement('SET FOREIGN_KEY_CHECKS=1'); } // Reset the group stats. return self::query()->update( [ 'backfill_target' => 1, 'first_record' => 0, 'first_record_postdate' => null, 'last_record' => 0, 'last_record_postdate' => null, 'last_updated' => null, 'active' => 0, ] ); } /** * Purge a single group or all groups. * * @param int|string|bool $id The group ID. If false, purge all groups. * * @throws \Exception */ public static function purge($id = false): void { if ($id === false) { self::resetall(); } else { self::reset($id); } $res = Release::query()->select(['id', 'guid']); if ($id !== false) { $res->where('groups_id', $id); } $releases = $res->get(); $releaseManagement = app(ReleaseManagementService::class); $nzb = app(NzbService::class); $releaseImage = new ReleaseImageService; foreach ($releases as $row) { $releaseManagement->deleteSingleWithService( [ 'g' => $row->guid, 'i' => $row->id, ], $nzb, $releaseImage ); } } /** * Adds new newsgroups based on a regular expression match against USP available. * * @return array|string * * @throws \Exception */ public static function addBulk(string $groupList, int $active = 1, int $backfill = 1) { if (preg_match('/^\s*$/m', $groupList)) { $ret = 'No group list provided.'; } else { $nntp = new NNTPService; if ($nntp->doConnect() !== true) { return 'Problem connecting to usenet.'; } $groups = $nntp->getGroups(); $nntp->doQuit(); if ($nntp->isError($groups)) { return 'Problem fetching usenet_groups from usenet.'; } $regFilter = '/'.$groupList.'/i'; $ret = []; foreach ($groups as $group) { if (preg_match($regFilter, $group['group']) > 0) { $res = self::getIDByName($group['group']); if ($res === false) { self::addGroup( [ 'name' => $group['group'], 'active' => $active, 'backfill' => $backfill, 'description' => 'Added by bulkAdd', ] ); $ret[] = ['group' => $group['group'], 'msg' => 'Created']; } } } if (\count($ret) === 0) { $ret[] = ['group' => '', 'msg' => 'No groups found with your regex or groups already exist in database, try again!']; } } return $ret; } /** * Updates the group active/backfill status. * * @param int $id Which group ID * @param string $column Which column active/backfill * @param int $status Which status we are setting */ public static function updateGroupStatus(int $id, string $column, int $status = 0): string { self::query()->where('id', $id)->update( [ $column => $status, ] ); return "Group {$id} has been ".(($status === 0) ? 'deactivated' : 'activated').'.'; } /** * Disable group that does not exist on USP server. * * @param int $id The Group ID to disable */ public static function disableIfNotExist(int $id): void { self::updateGroupStatus($id, 'active'); cli()->error('Group does not exist on server, disabling'); } }