$name, ]; $now = Carbon::now()->toDateTimeString(); $values = [ 'value' => $value, 'created_at' => $now, 'updated_at' => $now, ]; return Setting::query()->firstOrCreate($attributes, $values); } public function runExtraQueries() { $toolRep = new ToolRepository(); $redis = NExusDB::redis(); /** * @since 1.7.13 */ foreach (['adminpanel', 'modpanel', 'sysoppanel'] as $table) { $columnInfo = NexusDB::getMysqlColumnInfo($table, 'id'); if ($columnInfo['DATA_TYPE'] == 'tinyint' || empty($columnInfo['EXTRA']) || $columnInfo['EXTRA'] != 'auto_increment') { sql_query("alter table $table modify id int(11) unsigned not null AUTO_INCREMENT"); } } //custom field menu $url = 'fields.php'; $table = 'adminpanel'; $count = get_row_count($table, "where url = " . sqlesc($url)); if ($count == 0) { $insert = [ 'name' => 'Custom Field Manage', 'url' => $url, 'info' => 'Manage custom fields', ]; $id = NexusDB::insert($table, $insert); $this->doLog("[ADD CUSTOM FIELD MENU] insert: " . json_encode($insert) . " to table: $table, id: $id"); } //since beta8 if (WITH_LARAVEL && !NexusDB::hasColumn('categories', 'icon_id')) { $this->doLog('[INIT CATEGORY ICON_ID]'); $this->runMigrate('database/migrations/2022_03_08_040415_add_icon_id_to_categories_table.php'); $icon = Icon::query()->orderBy('id', 'asc')->first(); if ($icon) { Category::query()->where('icon_id', 0)->update(['icon_id' => $icon->id]); } } //fix base url, since beta8 if (WITH_LARAVEL && NexusDB::hasTable('settings')) { $settingBasic = get_setting('basic'); if (isset($settingBasic['BASEURL']) && Str::startsWith($settingBasic['BASEURL'], 'localhost')) { $this->doLog('[RESET CONFIG basic.BASEURL]'); Setting::query()->where('name', 'basic.BASEURL')->update(['value' => '']); } if (isset($settingBasic['announce_url']) && Str::startsWith($settingBasic['announce_url'], 'localhost')) { $this->doLog('[RESET CONFIG basic.announce_url]'); Setting::query()->where('name', 'basic.announce_url')->update(['value' => '']); } } //torrent support sticky second level if (WITH_LARAVEL) { $columnInfo = NexusDB::getMysqlColumnInfo('torrents', 'pos_state'); $this->doLog("[TORRENT POS_STATE], column info: " . json_encode($columnInfo)); if ($columnInfo['DATA_TYPE'] == 'enum') { $sql = "alter table torrents modify `pos_state` varchar(32) NOT NULL DEFAULT 'normal'"; $this->doLog("[ALTER TORRENT POS_STATE TYPE TO VARCHAR], $sql"); sql_query($sql); } } /** * @since 1.6.0-beta9 * * attendance change, do migrate */ if (WITH_LARAVEL) { if (!NexusDB::hasTable('attendance')) { //no table yet, no need to migrate $this->runMigrate('database/migrations/2021_06_08_113437_create_attendance_table.php'); } if (!NexusDB::hasColumn('attendance', 'total_days')) { $this->runMigrate('database/migrations/2021_06_13_215440_add_total_days_to_attendance_table.php'); $attendanceRep = new AttendanceRepository(); $count = $attendanceRep->migrateAttendance(); $this->doLog("[MIGRATE_ATTENDANCE] $count"); } } /** * @since 1.6.0-beta13 * * add seed points to user */ if (WITH_LARAVEL && !NexusDB::hasColumn('users', 'seed_points')) { $this->runMigrate('database/migrations/2021_06_24_013107_add_seed_points_to_users_table.php'); //Don't do this, initial seed points = 0; // $result = $this->initSeedPoints(); $this->doLog("[INIT SEED POINTS]"); } /** * @since 1.6.0-beta14 * * add id to agent_allowed_exception */ if (WITH_LARAVEL && !NexusDB::hasColumn('agent_allowed_exception', 'id')) { $this->runMigrate('database/migrations/2022_02_25_021356_add_id_to_agent_allowed_exception_table.php'); $this->doLog("[ADD_ID_TO_AGENT_ALLOWED_EXCEPTION]"); } /** * @since 1.6.0 * * init tag */ if (WITH_LARAVEL && !NexusDB::hasTable('tags')) { $this->runMigrate('database/migrations/2022_03_07_012545_create_tags_table.php'); $this->initTag(); $this->doLog("[INIT_TAG]"); } /** * @since 1.6.3 * * add usersearch.php and unco.php */ $menus = [ ['name' => 'Search user', 'url' => 'usersearch.php', 'info' => 'Search user'], ['name' => 'Confirm user', 'url' => 'unco.php', 'info' => 'Confirm user to complete registration'], ]; $table = 'modpanel'; foreach ($menus as $menu) { $count = get_row_count($table, "where url = " . sqlesc($menu['url'])); if ($count == 0) { $id = NexusDB::insert($table, $menu); $this->doLog("[ADD MENU] insert: " . json_encode($menu) . " to table: $table, id: $id"); } } /** * @since 1.7.0 * * add attendance_card to users */ if (WITH_LARAVEL && !NexusDB::hasColumn('users', 'attendance_card')) { $this->runMigrate('database/migrations/2022_04_02_163930_create_attendance_logs_table.php'); $this->runMigrate('database/migrations/2022_04_03_041642_add_attendance_card_to_users_table.php'); $rep = new AttendanceRepository(); $count = $rep->migrateAttendanceLogs(); $this->doLog("[ADD_ATTENDANCE_CARD_TO_USERS], migrateAttendanceLogs: $count"); } /** * @since 1.7.12 */ $menus = [ ['name' => 'Add Bonus/Attend card/Invite/upload', 'url' => 'increment-bulk.php', 'info' => 'Add Bonus/Attend card/Invite/upload to certain classes'], ]; $table = 'sysoppanel'; $this->addMenu($table, $menus); $menuToDel = ['amountupload.php', 'amountattendancecard.php', 'amountbonus.php', 'deletedisabled.php']; $this->removeMenu($menuToDel); /** * @since 1.7.19 */ $this->removeMenu(['freeleech.php']); NexusDB::cache_del('nexus_rss'); NexusDB::cache_del('nexus_is_ip_seed_box'); /** * @since 1.7.24 */ if (!NexusDB::hasColumn('searchbox', 'extra')) { $this->runMigrate('database/migrations/2022_09_02_031539_add_extra_to_searchbox_table.php'); SearchBox::query()->update(['extra' => [ SearchBox::EXTRA_DISPLAY_COVER_ON_TORRENT_LIST => 1, SearchBox::EXTRA_DISPLAY_SEED_BOX_ICON_ON_TORRENT_LIST => 1, ]]); } /** * @since 1.8.0 */ $shouldMigrateSearchBox = false; if (!NexusDB::hasColumn('searchbox', 'section_name')) { $shouldMigrateSearchBox = true; $searchBoxLog = "no section_name field"; } else { $columnInfo = NexusDB::getMysqlColumnInfo('searchbox', 'section_name'); $searchBoxLog = "has section_name, searchbox.section DATA_TYPE: " . $columnInfo['DATA_TYPE']; if ($columnInfo['DATA_TYPE'] != 'json') { $searchBoxLog .= ", not json"; $shouldMigrateSearchBox = true; } } $this->doLog("$searchBoxLog, shouldMigrateSearchBox: $shouldMigrateSearchBox"); if ($shouldMigrateSearchBox) { $this->runMigrate('database/migrations/2021_06_08_113437_create_searchbox_table.php'); $this->runMigrate('database/migrations/2022_03_08_041951_add_custom_fields_to_searchbox_table.php'); $this->runMigrate('database/migrations/2022_09_02_031539_add_extra_to_searchbox_table.php'); $this->runMigrate('database/migrations/2022_09_05_230532_add_mode_to_section_related.php'); $this->runMigrate('database/migrations/2022_09_06_004318_add_section_name_to_searchbox_table.php'); $this->runMigrate('database/migrations/2022_09_06_030324_change_searchbox_field_extra_to_json.php'); $this->migrateSearchBoxModeRelated(); $this->doLog("[MIGRATE_TAXONOMY_TO_MODE_RELATED]"); } $this->removeMenu(['catmanage.php']); if (!NexusDB::hasColumn('users', 'seed_points_updated_at')) { $this->runMigrate('database/migrations/2022_11_23_042152_add_seed_points_seed_times_update_time_to_users_table.php'); foreach (User::$notificationOptions as $option) { $sql = "update users set notifs = concat(notifs, '[$option]') where instr(notifs, '[$option]') = 0"; NexusDB::statement($sql); } } if (!$this->isSnatchedTableTorrentUserUnique()) { $toolRep->removeDuplicateSnatch(); $this->runMigrate('database/migrations/2023_03_29_021950_handle_snatched_user_torrent_unique.php'); $this->doLog("removeDuplicateSnatch and migrate 2023_03_29_021950_handle_snatched_user_torrent_unique"); } if (!NexusDB::hasIndex("peers", "unique_torrent_peer_user")) { $toolRep->removeDuplicatePeer(); $this->runMigrate('database/migrations/2023_04_01_005409_add_unique_torrent_peer_user_to_peers_table.php'); $this->doLog("removeDuplicatePeer and migrate 2023_04_01_005409_add_unique_torrent_peer_user_to_peers_table"); } /** * @since 1.8.3 */ $hasTableSetting = NexusDB::hasTable('settings'); if ($hasTableSetting) { $updateSettings = []; if (get_setting("system.meilisearch_enabled") == 'yes') { $updateSettings["enabled"] = "yes"; } if (get_setting("system.meilisearch_search_description") == 'yes') { $updateSettings["search_description"] = "yes"; } if (!empty($updateSettings)) { $this->saveSettings(['meilisearch' => $updateSettings]); } } /** * @since 1.8.10 */ if ($hasTableSetting) { Setting::query()->firstOrCreate( ["name" => "system.alarm_email_receiver"], ["value" => User::query()->where("class", User::CLASS_STAFF_LEADER)->first(["id"])->id] ); } /** * @since 1.9.0 */ if (!Schema::hasTable("torrent_extras")) { $this->runMigrate("database/migrations/2025_01_08_133552_create_torrent_extra_table.php"); Artisan::call("upgrade:migrate_torrents_table_text_column"); Language::updateTransStatus(); $this->addSetting('main.complain_enabled', 'yes'); $this->addSetting('image_hosting.driver', 'local'); $this->addSetting('permission.user_token_allowed', json_encode(TokenRepository::listUserTokenPermissions(false))); } if (!$redis->exists(Setting::USER_TOKEN_PERMISSION_ALLOWED_CACHE_KRY)) { Setting::updateUserTokenPermissionAllowedCache(TokenRepository::listUserTokenPermissions(false)); } } public function runExtraMigrate() { if (!WITH_LARAVEL) { $this->doLog(__METHOD__ . ", laravel is not available"); return; } if (NexusDB::hasColumn('torrents', 'tags')) { if (Torrent::query()->where('tags', '>', 0)->count() > 0 && TorrentTag::query()->count() == 0) { $this->doLog("[MIGRATE_TORRENT_TAG]..."); $tagRep = new TagRepository(); $tagRep->migrateTorrentTag(); $this->doLog("[MIGRATE_TORRENT_TAG] done!"); } $sql = 'alter table torrents drop column tags'; sql_query($sql); $this->doLog($sql); } else { $this->doLog("torrents table does not has column: tags"); } clear_setting_cache(); } private function addMenu($table, array $menus) { foreach ($menus as $menu) { $count = get_row_count($table, "where url = " . sqlesc($menu['url'])); if ($count == 0) { $id = NexusDB::insert($table, $menu); $this->doLog("[ADD MENU] insert: " . json_encode($menu) . " to table: $table, id: $id"); } } } private function removeMenu(array $menus, array $tables = ['sysoppanel', 'adminpanel', 'modpanel']) { $this->doLog("[REMOVE MENU]: " . json_encode($menus)); if (empty($menus)) { return; } foreach ($tables as $table) { NexusDB::table($table)->whereIn('url', $menus)->delete(); } } public function listVersions() { $url = "https://api.github.com/repos/xiaomlove/nexusphp/releases"; $versions = $this->requestGithub($url); return array_reverse($versions); } public function getLatestCommit() { $url = "https://api.github.com/repos/xiaomlove/nexusphp/commits/php8"; return $this->requestGithub($url); } public function requestGithub($url) { $client = new Client(); $logPrefix = "Request github: $url"; $response = $client->get($url, ['timeout' => 10,]); if (($statusCode = $response->getStatusCode()) != 200) { throw new \RuntimeException("$logPrefix fail, status code:$statusCode"); } if ($response->getBody()->getSize() <= 0) { throw new \RuntimeException("$logPrefix fail, response empty"); } $bodyString = $response->getBody()->getContents(); $this->doLog("[REQUEST_GITHUB_RESPONSE]: $bodyString"); $results = json_decode($bodyString, true); if (empty($results) || !is_array($results)) { throw new \RuntimeException("$logPrefix response invalid"); } return $results; } public function downAndExtractCode($url, array $includes = []): string { $requireCommand = 'rsync'; if (!command_exists($requireCommand)) { throw new \RuntimeException("command: $requireCommand not exists!"); } $arr = explode('/', $url); $basename = last($arr); $isZip = false; if (Str::contains($basename,'.zip')) { $isZip = true; $basename = strstr($basename, '.zip', true); $suffix = ".zip"; } else { $suffix = '.tar.gz'; } $filename = sprintf('%s/nexusphp-%s-%s%s', sys_get_temp_dir(), $basename, date('YmdHis'), $suffix); $this->doLog("download from: $url, save to filename: $filename"); $client = new Client(); $response = $client->request('GET', $url, ['sink' => $filename]); if (($statusCode = $response->getStatusCode()) != 200) { throw new \RuntimeException("Download fail, status code:$statusCode"); } if (($bodySize = $response->getBody()->getSize()) <= 0) { throw new \RuntimeException("Download fail, file size:$bodySize"); } if (!file_exists($filename)) { throw new \RuntimeException("Download fail, file not exists:$filename"); } if (filesize($filename) <= 0) { throw new \RuntimeException("Download fail, file: $filename size = 0"); } $this->doLog('SUCCESS_DOWNLOAD'); $extractDir = str_replace($suffix, "", $filename); $command = "mkdir -p $extractDir"; $this->executeCommand($command); if ($isZip) { $command = "unzip -q $filename -d $extractDir"; } else { $command = "tar -xf $filename -C $extractDir"; } $this->executeCommand($command); foreach (glob("$extractDir/*") as $path) { if (is_dir($path)) { $excludes = array_merge(ToolRepository::BACKUP_EXCLUDES, ['public/favicon.ico', '.env', 'public/pic/category/chd/*']); if (!in_array('composer', $includes)) { $excludes[] = 'composer.lock'; $excludes[] = 'composer.json'; } // $command = sprintf('cp -raf %s/. %s', $path, ROOT_PATH); $command = "rsync -rvq $path/ " . ROOT_PATH; $command .= " --include=public/vendor"; foreach ($excludes as $exclude) { $command .= " --exclude=$exclude"; } $this->executeCommand($command); //remove original file unlink($filename); break; } } $this->doLog('SUCCESS_EXTRACT'); return $extractDir; } public function initSeedPoints(): int { $size = 10000; $tableName = (new User())->getTable(); $result = 0; do { $affectedRows = NexusDB::table($tableName) ->whereNull('seed_points') ->limit($size) ->update([ 'seed_points' => NexusDB::raw('seedbonus') ]); $result += $affectedRows; $this->doLog("affectedRows: $affectedRows, query: " . last_query()); } while ($affectedRows > 0); return $result; } public function updateDependencies() { $command = "composer install -d " . ROOT_PATH; $this->executeCommand($command); $this->doLog("[COMPOSER INSTALL] SUCCESS"); } public function initTag() { $priority = count(Tag::DEFAULTS); $dateTimeStringNow = date('Y-m-d H:i:s'); foreach (Tag::DEFAULTS as $value) { $attributes = [ 'name' => $value['name'], ]; $values = [ 'priority' => $priority, 'color' => $value['color'], 'created_at' => $dateTimeStringNow, 'updated_at' => $dateTimeStringNow, ]; Tag::query()->firstOrCreate($attributes, $values); $priority--; } } private function isSnatchedTableTorrentUserUnique(): bool { $tableName = 'snatched'; $result = NexusDB::select('show index from ' . $tableName); foreach ($result as $item) { if (in_array($item['Column_name'], ['torrentid', 'userid']) && $item['Non_unique'] == 0) { return true; } } return false; } public function updateEnvFile() { $envFile = ROOT_PATH . '.env'; $envExample = ROOT_PATH . '.env.example'; $envData = readEnvFile($envFile); $envExampleData = readEnvFile($envExample); foreach ($envExampleData as $key => $value) { if (!isset($envData[$key])) { $envData[$key] = $value; } } $fp = @fopen($envFile, 'w'); if ($fp === false) { throw new \RuntimeException("can't create env file, make sure php has permission to create file at: " . ROOT_PATH); } $content = ""; foreach ($envData as $key => $value) { $content .= "{$key}={$value}\n"; } fwrite($fp, $content); fclose($fp); } }