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
6declare(strict_types=1);
7
8namespace App\Libraries\User;
9
10use App\Models\Tournament;
11use App\Models\TournamentRegistration;
12use App\Models\User;
13use Carbon\CarbonImmutable;
14
15class CountryChangeTarget
16{
17 const MIN_DAYS_MONTH = 15;
18
19 public static function currentMonth(): CarbonImmutable
20 {
21 $now = CarbonImmutable::now();
22 $subMonths = $now->day > static::MIN_DAYS_MONTH ? 0 : 1;
23
24 return $now->startOfMonth()->subMonths($subMonths);
25 }
26
27 public static function get(User $user): ?string
28 {
29 if (static::isUserInTournament($user)) {
30 return null;
31 }
32
33 $until = static::currentMonth();
34 $minMonths = static::minMonths();
35
36 $yearMonths = $user
37 ->userCountryHistory()
38 ->whereBetween('year_month', [
39 // one year maximum range. Offset by 1 because the range is inclusive
40 format_month_column($until->subMonths(11)),
41 format_month_column($until),
42 ])->distinct()
43 ->orderBy('year_month', 'DESC')
44 ->limit($minMonths)
45 ->pluck('year_month');
46
47 $history = $user
48 ->userCountryHistory()
49 ->whereIn('year_month', $yearMonths)
50 ->whereHas('country')
51 ->get();
52
53 // First group countries by year_month
54 $byMonth = [];
55 foreach ($history as $entry) {
56 $byMonth[$entry->year_month] ??= [];
57 $byMonth[$entry->year_month][] = $entry->country_acronym;
58 }
59
60 // For each year_month, summarise each countries
61 $byCountry = [];
62 foreach ($byMonth as $countries) {
63 $mixed = count($countries) > 1;
64 foreach ($countries as $country) {
65 $byCountry[$country] ??= [
66 'total' => 0,
67 'mixed' => 0,
68 ];
69 $byCountry[$country]['total']++;
70 if ($mixed) {
71 $byCountry[$country]['mixed']++;
72 }
73 }
74 }
75
76 // Finally find the first country which fulfills the requirement
77 foreach ($byCountry as $country => $data) {
78 if ($data['total'] === $minMonths && $data['mixed'] <= static::maxMixedMonths()) {
79 if ($user->country_acronym === $country) {
80 return null;
81 } else {
82 return $country;
83 }
84 }
85 }
86
87 return null;
88 }
89
90 public static function maxMixedMonths(): int
91 {
92 return $GLOBALS['cfg']['osu']['user']['country_change']['max_mixed_months'];
93 }
94
95 public static function minMonths(): int
96 {
97 return $GLOBALS['cfg']['osu']['user']['country_change']['min_months'];
98 }
99
100 private static function isUserInTournament(User $user): bool
101 {
102 return TournamentRegistration
103 ::where('user_id', $user->getKey())
104 ->whereIn(
105 'tournament_id',
106 Tournament
107 ::where('end_date', '>', CarbonImmutable::now())
108 ->orWhereNull('end_date')
109 ->select('tournament_id'),
110 )->exists();
111 }
112}