Skip to content

Commit f636546

Browse files
authored
Merge branch 'master' into feature/ts-store-supporter-tag
2 parents 5cf485e + 6bbe641 commit f636546

36 files changed

+752
-111
lines changed

Diff for: .env.example

+13-10
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ CLIENT_CHECK_VERSION=false
265265
# NOTIFICATION_CLEANUP_MAX_DELETE=50000
266266

267267
# The open source bounty info page/form url
268-
#OS_BOUNTY_URL=http://example.com/bounty_form
268+
# OS_BOUNTY_URL=http://example.com/bounty_form
269269

270270
# OAUTH_MAX_USER_CLIENTS=1
271271

@@ -292,16 +292,16 @@ CLIENT_CHECK_VERSION=false
292292
# PAGINATION_MAX_COUNT=10000
293293

294294
## Limits for the allowed number of simultaneous beatmapset uploads (displayed on the support page: /home/support)
295-
#BEATMAPSET_UPLOAD_ALLOWED=4
296-
#BEATMAPSET_UPLOAD_BONUS_PER_RANKED=1
297-
#BEATMAPSET_UPLOAD_BONUS_PER_RANKED_MAX=2
298-
#BEATMAPSET_UPLOAD_ALLOWED_SUPPORTER=8
299-
#BEATMAPSET_UPLOAD_BONUS_PER_RANKED_SUPPORTER=1
300-
#BEATMAPSET_UPLOAD_BONUS_PER_RANKED_MAX_SUPPORTER=12
295+
# BEATMAPSET_UPLOAD_ALLOWED=4
296+
# BEATMAPSET_UPLOAD_BONUS_PER_RANKED=1
297+
# BEATMAPSET_UPLOAD_BONUS_PER_RANKED_MAX=2
298+
# BEATMAPSET_UPLOAD_ALLOWED_SUPPORTER=8
299+
# BEATMAPSET_UPLOAD_BONUS_PER_RANKED_SUPPORTER=1
300+
# BEATMAPSET_UPLOAD_BONUS_PER_RANKED_MAX_SUPPORTER=12
301301

302-
#RECAPTCHA_SECRET=
303-
#RECAPTCHA_SITEKEY=
304-
#RECAPTCHA_THRESHOLD=
302+
# RECAPTCHA_SECRET=
303+
# RECAPTCHA_SITEKEY=
304+
# RECAPTCHA_THRESHOLD=
305305

306306
# TWITCH_CLIENT_ID=
307307
# TWITCH_CLIENT_SECRET=
@@ -336,3 +336,6 @@ CLIENT_CHECK_VERSION=false
336336

337337
# USER_COUNTRY_CHANGE_MAX_MIXED_MONTHS=2
338338
# USER_COUNTRY_CHANGE_MIN_MONTHS=6
339+
340+
# USER_INACTIVE_DAYS_VERIFICATION=180
341+
# USER_INACTIVE_FORCE_PASSWORD_RESET=false

Diff for: app/Http/Controllers/ScoresController.php

+26-16
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77

88
use App\Models\Score\Best\Model as ScoreBest;
99
use App\Models\Solo\Score as SoloScore;
10-
use App\Models\UserCountryHistory;
1110
use App\Transformers\ScoreTransformer;
1211
use App\Transformers\UserCompactTransformer;
13-
use Carbon\CarbonImmutable;
1412

1513
class ScoresController extends Controller
1614
{
15+
const REPLAY_DOWNLOAD_COUNT_INTERVAL = 86400; // 1 day
16+
1717
public function __construct()
1818
{
1919
parent::__construct();
@@ -52,20 +52,30 @@ public function download($rulesetOrSoloId, $id = null)
5252
abort(404);
5353
}
5454

55-
if (\Auth::user()->getKey() !== $score->user_id) {
56-
$score->user->statistics($score->getMode(), true)->increment('replay_popularity');
57-
58-
$month = CarbonImmutable::now();
59-
$currentMonth = UserCountryHistory::formatDate($month);
60-
61-
$score->user->replaysWatchedCounts()
62-
->firstOrCreate(['year_month' => $currentMonth], ['count' => 0])
63-
->incrementInstance('count');
64-
65-
if ($score instanceof ScoreBest) {
66-
$score->replayViewCount()
67-
->firstOrCreate([], ['play_count' => 0])
68-
->incrementInstance('play_count');
55+
$currentUser = \Auth::user();
56+
if (
57+
!$currentUser->isRestricted()
58+
&& $currentUser->getKey() !== $score->user_id
59+
&& ($currentUser->token()?->client->password_client ?? false)
60+
) {
61+
$countLock = \Cache::lock(
62+
"view:score_replay:{$score->getKey()}:{$currentUser->getKey()}",
63+
static::REPLAY_DOWNLOAD_COUNT_INTERVAL,
64+
);
65+
66+
if ($countLock->get()) {
67+
$score->user->statistics($score->getMode(), true)->increment('replay_popularity');
68+
69+
$currentMonth = format_month_column(new \DateTime());
70+
$score->user->replaysWatchedCounts()
71+
->firstOrCreate(['year_month' => $currentMonth], ['count' => 0])
72+
->incrementInstance('count');
73+
74+
if ($score instanceof ScoreBest) {
75+
$score->replayViewCount()
76+
->firstOrCreate([], ['play_count' => 0])
77+
->incrementInstance('play_count');
78+
}
6979
}
7080
}
7181

Diff for: app/Http/Controllers/SessionsController.php

+2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public function store()
7777
$forceReactivation = new ForceReactivation($user, $request);
7878

7979
if ($forceReactivation->isRequired()) {
80+
DatadogLoginAttempt::log('password_reset');
8081
$forceReactivation->run();
8182

8283
\Session::flash('password_reset_start', [
@@ -87,6 +88,7 @@ public function store()
8788
return ujs_redirect(route('password-reset'));
8889
}
8990

91+
DatadogLoginAttempt::log(null);
9092
$this->login($user, $remember);
9193

9294
return [

Diff for: app/Http/Controllers/UserCoverPresetsController.php

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4+
// See the LICENCE file in the repository root for full licence text.
5+
6+
namespace App\Http\Controllers;
7+
8+
use App\Models\UserCoverPreset;
9+
use Symfony\Component\HttpFoundation\Response;
10+
11+
class UserCoverPresetsController extends Controller
12+
{
13+
public function __construct()
14+
{
15+
$this->middleware('auth');
16+
17+
parent::__construct();
18+
}
19+
20+
public function batchActivate(): Response
21+
{
22+
$params = get_params(\Request::all(), null, [
23+
'ids:int[]',
24+
'active:bool',
25+
]);
26+
if (!isset($params['active'])) {
27+
abort(422, 'parameter "active" is missing');
28+
}
29+
UserCoverPreset::whereKey($params['ids'] ?? [])->update(['active' => $params['active']]);
30+
31+
return response(null, 204);
32+
}
33+
34+
public function index(): Response
35+
{
36+
priv_check('UserCoverPresetManage')->ensureCan();
37+
38+
return ext_view('user_cover_presets.index', [
39+
'items' => UserCoverPreset::orderBy('id', 'ASC')->get(),
40+
]);
41+
}
42+
43+
public function store(): Response
44+
{
45+
priv_check('UserCoverPresetManage')->ensureCan();
46+
47+
try {
48+
$files = \Request::file('files') ?? [];
49+
foreach ($files as $file) {
50+
$item = \DB::transaction(function () use ($file) {
51+
$item = UserCoverPreset::create();
52+
$item->file()->store($file->getRealPath());
53+
$item->saveOrExplode();
54+
55+
return $item;
56+
});
57+
$hash ??= "#cover-{$item->getKey()}";
58+
}
59+
\Session::flash('popup', osu_trans('user_cover_presets.store.ok'));
60+
} catch (\Throwable $e) {
61+
\Session::flash('popup', osu_trans('user_cover_presets.store.failed', ['error' => $e->getMessage()]));
62+
}
63+
64+
return ujs_redirect(route('user-cover-presets.index').($hash ?? ''));
65+
}
66+
67+
public function update(string $id): Response
68+
{
69+
priv_check('UserCoverPresetManage')->ensureCan();
70+
71+
$item = UserCoverPreset::findOrFail($id);
72+
$params = get_params(\Request::all(), null, [
73+
'file:file',
74+
'active:bool',
75+
], ['null_missing' => true]);
76+
77+
if ($params['file'] !== null) {
78+
$item->file()->store($params['file']);
79+
$item->save();
80+
}
81+
if ($params['active'] !== null) {
82+
$item->update(['active' => $params['active']]);
83+
}
84+
85+
return ujs_redirect(route('user-cover-presets.index').'#cover-'.$item->getKey());
86+
}
87+
}

Diff for: app/Libraries/Search/BeatmapsetSearch.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,8 @@ private function addTextFilter(BoolQuery $query, string $paramField, array $fiel
425425
private function getPlayedBeatmapIds(?array $rank = null)
426426
{
427427
$query = Solo\Score
428-
::where('user_id', $this->params->user->getKey())
428+
::indexable()
429+
->where('user_id', $this->params->user->getKey())
429430
->whereIn('ruleset_id', $this->getSelectedModes());
430431

431432
if ($rank === null) {

Diff for: app/Libraries/User/CountryChangeTarget.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use App\Models\Tournament;
1111
use App\Models\TournamentRegistration;
1212
use App\Models\User;
13-
use App\Models\UserCountryHistory;
1413
use Carbon\CarbonImmutable;
1514

1615
class CountryChangeTarget
@@ -38,8 +37,8 @@ public static function get(User $user): ?string
3837
->userCountryHistory()
3938
->whereBetween('year_month', [
4039
// one year maximum range. Offset by 1 because the range is inclusive
41-
UserCountryHistory::formatDate($until->subMonths(11)),
42-
UserCountryHistory::formatDate($until),
40+
format_month_column($until->subMonths(11)),
41+
format_month_column($until),
4342
])->distinct()
4443
->orderBy('year_month', 'DESC')
4544
->limit($minMonths)

Diff for: app/Libraries/User/ForceReactivation.php

+12-5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
class ForceReactivation
1515
{
16+
const INACTIVE = 'inactive';
1617
const INACTIVE_DIFFERENT_COUNTRY = 'inactive_different_country';
1718

1819
private $country;
@@ -27,8 +28,12 @@ public function __construct($user, $request)
2728

2829
$this->country = request_country($this->request);
2930

30-
if ($this->user->isInactive() && $this->user->country_acronym !== $this->country) {
31-
$this->reason = static::INACTIVE_DIFFERENT_COUNTRY;
31+
if ($this->user->isInactive()) {
32+
if ($this->user->country_acronym !== $this->country) {
33+
$this->reason = static::INACTIVE_DIFFERENT_COUNTRY;
34+
} elseif ($GLOBALS['cfg']['osu']['user']['inactive_force_password_reset']) {
35+
$this->reason = static::INACTIVE;
36+
}
3237
}
3338
}
3439

@@ -62,9 +67,11 @@ public function run()
6267

6368
private function addHistoryNote()
6469
{
65-
if ($this->reason === static::INACTIVE_DIFFERENT_COUNTRY) {
66-
$message = "First login after {$this->user->user_lastvisit->diffInDays()} days from {$this->country}. Forcing password reset.";
67-
}
70+
$message = match ($this->reason) {
71+
static::INACTIVE => "First login after {$this->user->user_lastvisit->diffInDays()} days. Forcing password reset.",
72+
static::INACTIVE_DIFFERENT_COUNTRY => "First login after {$this->user->user_lastvisit->diffInDays()} days from {$this->country}. Forcing password reset.",
73+
default => null,
74+
};
6875

6976
if ($message !== null) {
7077
UserAccountHistory::addNote($this->user, $message);

Diff for: app/Models/Comment.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ protected function newReportableExtraParams(): array
302302
{
303303
return [
304304
'reason' => 'Spam',
305-
'user_id' => $this->user_id,
305+
'user_id' => $this->user_id ?? 0,
306306
];
307307
}
308308

Diff for: app/Models/Multiplayer/UserScoreAggregate.php

+1
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ private function refreshStatistics(): void
203203
{
204204
$agg = PlaylistItemUserHighScore
205205
::whereHas('playlistItem', fn ($q) => $q->where('room_id', $this->room_id))
206+
->whereNotNull('score_id')
206207
->selectRaw('
207208
SUM(accuracy) AS accuracy_sum,
208209
SUM(total_score) AS total_score_sum,

Diff for: app/Models/Solo/Score.php

+12-3
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
use App\Models\ScoreToken;
2020
use App\Models\Traits;
2121
use App\Models\User;
22+
use Illuminate\Contracts\Filesystem\Filesystem;
2223
use Illuminate\Database\Eloquent\Builder;
2324
use LaravelRedis;
24-
use Storage;
2525

2626
/**
2727
* @property float $accuracy
@@ -127,6 +127,16 @@ public static function extractParams(array $rawParams, ScoreToken|MultiplayerSco
127127
return $params;
128128
}
129129

130+
public static function replayFileDiskName(): string
131+
{
132+
return "{$GLOBALS['cfg']['osu']['score_replays']['storage']}-solo-replay";
133+
}
134+
135+
public static function replayFileStorage(): Filesystem
136+
{
137+
return \Storage::disk(static::replayFileDiskName());
138+
}
139+
130140
public function beatmap()
131141
{
132142
return $this->belongsTo(Beatmap::class, 'beatmap_id');
@@ -253,8 +263,7 @@ public function getMode(): string
253263

254264
public function getReplayFile(): ?string
255265
{
256-
return Storage::disk($GLOBALS['cfg']['osu']['score_replays']['storage'].'-solo-replay')
257-
->get($this->getKey());
266+
return static::replayFileStorage()->get($this->getKey());
258267
}
259268

260269
public function isLegacy(): bool

Diff for: app/Models/User.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -2078,9 +2078,9 @@ public static function attemptLogin($user, $password, $ip = null)
20782078
}
20792079
}
20802080

2081-
DatadogLoginAttempt::log($authError);
20822081

20832082
if ($authError !== null) {
2083+
DatadogLoginAttempt::log($authError);
20842084
LoginAttempt::logAttempt($ip, $user, 'fail', $password);
20852085

20862086
return osu_trans('users.login.failed');

Diff for: app/Models/UserCountryHistory.php

+1-6
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ class UserCountryHistory extends Model
2828
protected $primaryKeys = ['user_id', 'year_month', 'country_acronym'];
2929
protected $table = 'user_country_history';
3030

31-
public static function formatDate(\DateTimeInterface $date): string
32-
{
33-
return $date->format('ym');
34-
}
35-
3631
public function country(): BelongsTo
3732
{
3833
return $this->belongsTo(Country::class, 'country_acronym');
@@ -46,7 +41,7 @@ public function user(): BelongsTo
4641
public function setYearMonthAttribute(\DateTimeInterface|string $value): void
4742
{
4843
$this->attributes['year_month'] = $value instanceof \DateTimeInterface
49-
? static::formatDate($value)
44+
? format_month_column($value)
5045
: $value;
5146
}
5247
}

Diff for: app/Models/UserCoverPreset.php

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4+
// See the LICENCE file in the repository root for full licence text.
5+
6+
namespace App\Models;
7+
8+
use App\Libraries\Uploader;
9+
use App\Libraries\User\Cover;
10+
11+
/**
12+
* @property bool $active
13+
* @property \Carbon\Carbon|null $created_at
14+
* @property string|null $filename
15+
* @property int $id
16+
* @property \Carbon\Carbon|null $updated_at
17+
*/
18+
class UserCoverPreset extends Model
19+
{
20+
private Uploader $file;
21+
22+
public function file(): Uploader
23+
{
24+
return $this->file ??= new Uploader(
25+
'user-cover-presets',
26+
$this,
27+
'filename',
28+
['image' => ['maxDimensions' => Cover::CUSTOM_COVER_MAX_DIMENSIONS]],
29+
);
30+
}
31+
}

0 commit comments

Comments
 (0)