Skip to content

Commit 68e530f

Browse files
authored
Merge branch 'master' into score-info-mods-wrap
2 parents 2b61d99 + ec117ea commit 68e530f

File tree

96 files changed

+1986
-825
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+1986
-825
lines changed

Diff for: .env.example

+13-11
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,6 @@ CLIENT_CHECK_VERSION=false
246246

247247
# USER_MAX_SCORE_PINS=10
248248
# USER_MAX_SCORE_PINS_SUPPORTER=50
249-
# USER_HIDE_PINNED_SOLO_SCORES=true
250249

251250
# the content is in markdown format
252251
# USER_PROFILE_SCORES_NOTICE=
@@ -265,7 +264,7 @@ CLIENT_CHECK_VERSION=false
265264
# NOTIFICATION_CLEANUP_MAX_DELETE=50000
266265

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

270269
# OAUTH_MAX_USER_CLIENTS=1
271270

@@ -292,16 +291,16 @@ CLIENT_CHECK_VERSION=false
292291
# PAGINATION_MAX_COUNT=10000
293292

294293
## 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
294+
# BEATMAPSET_UPLOAD_ALLOWED=4
295+
# BEATMAPSET_UPLOAD_BONUS_PER_RANKED=1
296+
# BEATMAPSET_UPLOAD_BONUS_PER_RANKED_MAX=2
297+
# BEATMAPSET_UPLOAD_ALLOWED_SUPPORTER=8
298+
# BEATMAPSET_UPLOAD_BONUS_PER_RANKED_SUPPORTER=1
299+
# BEATMAPSET_UPLOAD_BONUS_PER_RANKED_MAX_SUPPORTER=12
301300

302-
#RECAPTCHA_SECRET=
303-
#RECAPTCHA_SITEKEY=
304-
#RECAPTCHA_THRESHOLD=
301+
# RECAPTCHA_SECRET=
302+
# RECAPTCHA_SITEKEY=
303+
# RECAPTCHA_THRESHOLD=
305304

306305
# TWITCH_CLIENT_ID=
307306
# TWITCH_CLIENT_SECRET=
@@ -336,3 +335,6 @@ CLIENT_CHECK_VERSION=false
336335

337336
# USER_COUNTRY_CHANGE_MAX_MIXED_MONTHS=2
338337
# USER_COUNTRY_CHANGE_MIN_MONTHS=6
338+
339+
# USER_INACTIVE_DAYS_VERIFICATION=180
340+
# USER_INACTIVE_FORCE_PASSWORD_RESET=false

Diff for: app/Exceptions/ImageProcessorServiceException.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99

1010
class ImageProcessorServiceException extends Exception
1111
{
12-
// doesn't really contain anything
12+
const INVALID_IMAGE = 1;
1313
}

Diff for: app/Http/Controllers/Forum/TopicsController.php

+1
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ public function show($id)
428428

429429
$pollSummary = PollOption::summary($topic, $currentUser);
430430

431+
$topic->incrementViewCount($currentUser, \Request::ip());
431432
$posts->last()->markRead($currentUser);
432433

433434
$coverModel = $topic->cover ?? new TopicCover();

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

+34-34
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
namespace App\Http\Controllers;
77

88
use App\Jobs\RenumberUserScorePins;
9-
use App\Libraries\MorphMap;
10-
use App\Models\Beatmap;
119
use App\Models\ScorePin;
1210
use App\Models\Solo;
1311
use Exception;
@@ -23,29 +21,31 @@ public function __construct()
2321

2422
public function destroy()
2523
{
26-
auth()->user()->scorePins()->where($this->getScoreParams(request()->all()))->delete();
24+
\Auth::user()->scorePins()->whereKey(get_int(request('score_id')))->delete();
2725

2826
return response()->noContent();
2927
}
3028

3129
public function reorder()
3230
{
33-
$rawParams = request()->all();
34-
$targetParams = $this->getScoreParams($rawParams);
31+
$rawParams = \Request::all();
32+
$targetId = get_int($rawParams['score_id'] ?? null);
3533

36-
$pinsQuery = auth()->user()->scorePins();
37-
$target = $pinsQuery->clone()->where($targetParams)->firstOrFail();
34+
$pinsQuery = \Auth::user()->scorePins();
35+
$target = $pinsQuery->clone()->findOrFail($targetId);
36+
$rulesetId = $target->ruleset_id;
37+
$pinsQuery->where('ruleset_id', $rulesetId);
3838

3939
$adjacentScores = [];
4040
foreach (['order1', 'order3'] as $position) {
41-
$adjacentScores[$position] = $this->getScoreParams(get_arr($rawParams[$position] ?? null) ?? []);
41+
$adjacentScoreIds[$position] = get_int($rawParams[$position]['score_id'] ?? null);
4242
}
4343

44-
$order1Item = isset($adjacentScores['order1']['score_id'])
45-
? $pinsQuery->clone()->where($adjacentScores['order1'])->first()
44+
$order1Item = isset($adjacentScoreIds['order1'])
45+
? $pinsQuery->clone()->find($adjacentScoreIds['order1'])
4646
: null;
47-
$order3Item = $order1Item === null && isset($adjacentScores['order3']['score_id'])
48-
? $pinsQuery->clone()->where($adjacentScores['order3'])->first()
47+
$order3Item = $order1Item === null && isset($adjacentScoreIds['order3'])
48+
? $pinsQuery->clone()->find($adjacentScoreIds['order3'])
4949
: null;
5050

5151
abort_if($order1Item === null && $order3Item === null, 422, 'no valid pinned score reference is specified');
@@ -63,7 +63,7 @@ public function reorder()
6363
$order2 = ($order1 + $order3) / 2;
6464

6565
if ($order3 - $order1 < 0.1) {
66-
dispatch(new RenumberUserScorePins($target->user_id, $target->score_type));
66+
dispatch(new RenumberUserScorePins($target->user_id, $target->ruleset_id));
6767
}
6868

6969
$target->update(['display_order' => $order2]);
@@ -73,27 +73,37 @@ public function reorder()
7373

7474
public function store()
7575
{
76-
$params = $this->getScoreParams(request()->all());
77-
78-
abort_if(!ScorePin::isValidType($params['score_type']), 422, 'invalid score_type');
79-
80-
$score = MorphMap::getClass($params['score_type'])::find($params['score_id']);
76+
$id = get_int(request('score_id'));
77+
$score = Solo\Score::find($id);
8178

8279
abort_if($score === null, 422, "specified score couldn't be found");
8380

84-
$user = auth()->user();
81+
$user = \Auth::user();
8582

86-
$pin = ScorePin::where(['user_id' => $user->getKey()])->whereMorphedTo('score', $score)->first();
83+
$pin = $user->scorePins()->find($id);
8784

8885
if ($pin === null) {
8986
priv_check('ScorePin', $score)->ensureCan();
9087

91-
$rulesetId = Beatmap::MODES[$score->getMode()];
88+
$rulesetId = $score->ruleset_id;
9289
$currentMinDisplayOrder = $user->scorePins()->where('ruleset_id', $rulesetId)->min('display_order') ?? 2500;
9390

9491
try {
95-
(new ScorePin(['display_order' => $currentMinDisplayOrder - 100, 'ruleset_id' => $rulesetId]))
96-
->user()->associate($user)
92+
(new ScorePin([
93+
'display_order' => $currentMinDisplayOrder - 100,
94+
'ruleset_id' => $rulesetId,
95+
96+
/**
97+
* TODO:
98+
* 1. update score_id = new_score_id
99+
* 2. remove duplicated score_id
100+
* 3. use score_id as primary key (both model and database)
101+
* 4. remove setting score_type below
102+
* 5. remove new_score_id and score_type columns
103+
*/
104+
'score_id' => $score->getKey(),
105+
'score_type' => $score->getMorphClass(),
106+
]))->user()->associate($user)
97107
->score()->associate($score)
98108
->saveOrExplode();
99109
} catch (Exception $ex) {
@@ -102,19 +112,9 @@ public function store()
102112
}
103113
}
104114

105-
if ($score instanceof Solo\Score) {
106-
$score->update(['preserve' => true]);
107-
}
115+
$score->update(['preserve' => true]);
108116
}
109117

110118
return response()->noContent();
111119
}
112-
113-
private function getScoreParams(array $form)
114-
{
115-
return get_params($form, null, [
116-
'score_type:string',
117-
'score_id:int',
118-
], ['null_missing' => true]);
119-
}
120120
}

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

+34-20
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55

66
namespace App\Http\Controllers;
77

8+
use App\Enums\Ruleset;
89
use App\Models\Score\Best\Model as ScoreBest;
910
use App\Models\Solo\Score as SoloScore;
10-
use App\Models\UserCountryHistory;
1111
use App\Transformers\ScoreTransformer;
1212
use App\Transformers\UserCompactTransformer;
13-
use Carbon\CarbonImmutable;
1413

1514
class ScoresController extends Controller
1615
{
16+
const REPLAY_DOWNLOAD_COUNT_INTERVAL = 86400; // 1 day
17+
1718
public function __construct()
1819
{
1920
parent::__construct();
@@ -52,20 +53,30 @@ public function download($rulesetOrSoloId, $id = null)
5253
abort(404);
5354
}
5455

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

@@ -80,10 +91,13 @@ public function download($rulesetOrSoloId, $id = null)
8091

8192
public function show($rulesetOrSoloId, $legacyId = null)
8293
{
83-
[$scoreClass, $id] = $legacyId === null
84-
? [SoloScore::class, $rulesetOrSoloId]
85-
: [ScoreBest::getClass($rulesetOrSoloId), $legacyId];
86-
$score = $scoreClass::whereHas('beatmap.beatmapset')->visibleUsers()->findOrFail($id);
94+
$scoreQuery = $legacyId === null
95+
? SoloScore::whereKey($rulesetOrSoloId)
96+
: SoloScore::where([
97+
'ruleset_id' => Ruleset::tryFromName($rulesetOrSoloId) ?? abort(404, 'unknown ruleset name'),
98+
'legacy_score_id' => $legacyId,
99+
]);
100+
$score = $scoreQuery->whereHas('beatmap.beatmapset')->visibleUsers()->firstOrFail();
87101

88102
$userIncludes = array_map(function ($include) {
89103
return "user.{$include}";

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/Jobs/RenumberUserScorePins.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class RenumberUserScorePins implements ShouldQueue
1515
{
1616
use InteractsWithQueue, Queueable;
1717

18-
public function __construct(private int $userId, private string $scoreType)
18+
public function __construct(private int $userId, private int $rulesetId)
1919
{
2020
}
2121

@@ -24,7 +24,7 @@ public function handle()
2424
DB::transaction(function () {
2525
$pins = ScorePin
2626
::where([
27-
'score_type' => $this->scoreType,
27+
'ruleset_id' => $this->rulesetId,
2828
'user_id' => $this->userId,
2929
])->orderBy('display_order', 'asc')
3030
->lockForUpdate()

0 commit comments

Comments
 (0)