1 | <?php |
||
2 | |||
3 | /** |
||
4 | * webtrees: online genealogy |
||
5 | * Copyright (C) 2025 webtrees development team |
||
6 | * This program is free software: you can redistribute it and/or modify |
||
7 | * it under the terms of the GNU General Public License as published by |
||
8 | * the Free Software Foundation, either version 3 of the License, or |
||
9 | * (at your option) any later version. |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * You should have received a copy of the GNU General Public License |
||
15 | * along with this program. If not, see <https://www.gnu.org/licenses/>. |
||
16 | */ |
||
17 | |||
18 | declare(strict_types=1); |
||
19 | |||
20 | namespace Fisharebest\Webtrees; |
||
21 | |||
22 | use Fisharebest\Webtrees\Contracts\UserInterface; |
||
23 | use Fisharebest\Webtrees\Elements\UnknownElement; |
||
24 | use Fisharebest\Webtrees\Http\RequestHandlers\MessagePage; |
||
25 | use Fisharebest\Webtrees\Module\IndividualListModule; |
||
26 | use Fisharebest\Webtrees\Module\ModuleInterface; |
||
27 | use Fisharebest\Webtrees\Module\ModuleListInterface; |
||
28 | use Fisharebest\Webtrees\Services\MessageService; |
||
29 | use Fisharebest\Webtrees\Services\ModuleService; |
||
30 | use Fisharebest\Webtrees\Services\UserService; |
||
31 | use Illuminate\Database\Query\Builder; |
||
32 | use Illuminate\Database\Query\Expression; |
||
33 | use Illuminate\Database\Query\JoinClause; |
||
34 | use Illuminate\Support\Collection; |
||
35 | |||
36 | use function abs; |
||
37 | use function array_keys; |
||
38 | use function array_reverse; |
||
39 | use function array_search; |
||
40 | use function array_shift; |
||
41 | use function array_slice; |
||
42 | use function arsort; |
||
43 | use function asort; |
||
44 | use function count; |
||
45 | use function e; |
||
46 | use function explode; |
||
47 | use function floor; |
||
48 | use function implode; |
||
49 | use function in_array; |
||
50 | use function preg_match; |
||
51 | use function preg_quote; |
||
52 | use function route; |
||
53 | use function str_replace; |
||
54 | use function strip_tags; |
||
55 | use function uksort; |
||
56 | use function view; |
||
57 | |||
58 | readonly class StatisticsData |
||
59 | { |
||
60 | public function __construct( |
||
61 | private Tree $tree, |
||
62 | private UserService $user_service, |
||
63 | ) { |
||
64 | } |
||
65 | |||
66 | public function averageChildrenPerFamily(): float |
||
67 | { |
||
68 | return (float) DB::table('families') |
||
69 | ->where('f_file', '=', $this->tree->id()) |
||
70 | ->avg('f_numchil'); |
||
71 | } |
||
72 | |||
73 | public function averageLifespanDays(string $sex): int |
||
74 | { |
||
75 | return (int) $this->birthAndDeathQuery($sex) |
||
76 | ->select([new Expression('AVG(' . DB::prefix('death.d_julianday2') . ' - ' . DB::prefix('birth.d_julianday1') . ') AS days')]) |
||
77 | ->value('days'); |
||
78 | } |
||
79 | |||
80 | /** |
||
81 | * @return Collection<string,int> |
||
82 | */ |
||
83 | public function commonGivenNames(string $sex, int $threshold, int $limit): Collection |
||
84 | { |
||
85 | $query = DB::table('name') |
||
86 | ->where('n_file', '=', $this->tree->id()) |
||
87 | ->where('n_type', '<>', '_MARNM') |
||
88 | ->where('n_givn', '<>', Individual::PRAENOMEN_NESCIO) |
||
89 | ->where(new Expression('LENGTH(n_givn)'), '>', 1); |
||
90 | |||
91 | if ($sex !== 'ALL') { |
||
92 | $query |
||
93 | ->join('individuals', static function (JoinClause $join): void { |
||
94 | $join |
||
95 | ->on('i_file', '=', 'n_file') |
||
96 | ->on('i_id', '=', 'n_id'); |
||
97 | }) |
||
98 | ->where('i_sex', '=', $sex); |
||
99 | } |
||
100 | |||
101 | $rows = $query |
||
102 | ->groupBy(['n_givn']) |
||
103 | ->pluck(new Expression('COUNT(DISTINCT n_id)'), 'n_givn') |
||
104 | ->map(static fn (int|string $count): int => (int) $count); |
||
105 | |||
106 | |||
107 | $given_names = []; |
||
108 | |||
109 | foreach ($rows as $n_givn => $count) { |
||
110 | // Split “John Thomas” into “John” and “Thomas” and count against both totals |
||
111 | foreach (explode(' ', (string) $n_givn) as $given) { |
||
112 | // Exclude initials and particles. |
||
113 | if (preg_match('/^([A-Z]|[a-z]{1,3})$/', $given) !== 1) { |
||
114 | $given_names[$given] ??= 0; |
||
115 | $given_names[$given] += (int) $count; |
||
116 | } |
||
117 | } |
||
118 | } |
||
119 | |||
120 | return (new Collection($given_names)) |
||
121 | ->sortDesc() |
||
122 | ->slice(0, $limit) |
||
123 | ->filter(static fn (int $count): bool => $count >= $threshold); |
||
124 | } |
||
125 | |||
126 | /** |
||
127 | * @return array<array<int>> |
||
128 | */ |
||
129 | public function commonSurnames(int $limit, int $threshold, string $sort): array |
||
130 | { |
||
131 | // Use the count of base surnames. |
||
132 | $top_surnames = DB::table('name') |
||
133 | ->where('n_file', '=', $this->tree->id()) |
||
134 | ->where('n_type', '<>', '_MARNM') |
||
135 | ->whereNotIn('n_surn', ['', Individual::NOMEN_NESCIO]) |
||
136 | ->select(['n_surn']) |
||
137 | ->groupBy(['n_surn']) |
||
138 | ->orderByRaw('COUNT(n_surn) DESC') |
||
139 | ->orderBy(new Expression('COUNT(n_surn)'), 'DESC') |
||
140 | ->having(new Expression('COUNT(n_surn)'), '>=', $threshold) |
||
141 | ->take($limit) |
||
142 | ->pluck('n_surn') |
||
143 | ->all(); |
||
144 | |||
145 | $surnames = []; |
||
146 | |||
147 | foreach ($top_surnames as $top_surname) { |
||
148 | $surnames[$top_surname] = DB::table('name') |
||
149 | ->where('n_file', '=', $this->tree->id()) |
||
150 | ->where('n_type', '<>', '_MARNM') |
||
151 | ->where('n_surn', '=', $top_surname) |
||
152 | ->select(['n_surn', new Expression('COUNT(n_surn) AS count')]) |
||
153 | ->groupBy(['n_surn']) |
||
154 | ->orderBy('n_surn') |
||
155 | ->pluck('count', 'n_surn') |
||
156 | ->map(static fn (string $count): int => (int) $count) |
||
157 | ->all(); |
||
158 | } |
||
159 | |||
160 | switch ($sort) { |
||
161 | default: |
||
162 | case 'alpha': |
||
163 | uksort($surnames, I18N::comparator()); |
||
164 | break; |
||
165 | case 'count': |
||
166 | break; |
||
167 | case 'rcount': |
||
168 | $surnames = array_reverse($surnames, true); |
||
169 | break; |
||
170 | } |
||
171 | |||
172 | return $surnames; |
||
173 | } |
||
174 | |||
175 | /** |
||
176 | * @param array<string> $events |
||
177 | */ |
||
178 | public function countAllEvents(array $events): int |
||
179 | { |
||
180 | return DB::table('dates') |
||
181 | ->where('d_file', '=', $this->tree->id()) |
||
182 | ->whereIn('d_fact', $events) |
||
183 | ->count(); |
||
184 | } |
||
185 | |||
186 | public function countAllPlaces(): int |
||
187 | { |
||
188 | return DB::table('places') |
||
189 | ->where('p_file', '=', $this->tree->id()) |
||
190 | ->count(); |
||
191 | } |
||
192 | |||
193 | public function countAllRecords(): int |
||
194 | { |
||
195 | return |
||
196 | $this->countIndividuals() + |
||
197 | $this->countFamilies() + |
||
198 | $this->countMedia() + |
||
199 | $this->countNotes() + |
||
200 | $this->countRepositories() + |
||
201 | $this->countSources(); |
||
202 | } |
||
203 | |||
204 | public function countChildren(): int |
||
205 | { |
||
206 | return (int) DB::table('families') |
||
207 | ->where('f_file', '=', $this->tree->id()) |
||
208 | ->sum('f_numchil'); |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * @return array<array{place:Place,count:int}> |
||
213 | */ |
||
214 | public function countCountries(int $limit): array |
||
215 | { |
||
216 | return DB::table('places') |
||
217 | ->join('placelinks', static function (JoinClause $join): void { |
||
218 | $join |
||
219 | ->on('pl_file', '=', 'p_file') |
||
220 | ->on('pl_p_id', '=', 'p_id'); |
||
221 | }) |
||
222 | ->where('p_file', '=', $this->tree->id()) |
||
223 | ->where('p_parent_id', '=', 0) |
||
224 | ->groupBy(['p_place']) |
||
225 | ->orderByDesc(new Expression('COUNT(*)')) |
||
226 | ->orderBy('p_place') |
||
227 | ->take($limit) |
||
228 | ->select([new Expression('COUNT(*) AS total'), 'p_place AS place']) |
||
229 | ->get() |
||
230 | ->map(fn (object $row): array => [ |
||
231 | 'place' => new Place($row->place, $this->tree), |
||
232 | 'count' => (int) $row->total, |
||
233 | ]) |
||
234 | ->all(); |
||
235 | } |
||
236 | |||
237 | private function countEventQuery(string $event, int $year1 = 0, int $year2 = 0): Builder |
||
238 | { |
||
239 | $query = DB::table('dates') |
||
240 | ->where('d_file', '=', $this->tree->id()) |
||
241 | ->where('d_fact', '=', $event) |
||
242 | ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@']); |
||
243 | |||
244 | if ($year1 !== 0 && $year2 !== 0) { |
||
245 | $query->whereBetween('d_year', [$year1, $year2]); |
||
246 | } else { |
||
247 | $query->where('d_year', '<>', 0); |
||
248 | } |
||
249 | |||
250 | return $query; |
||
251 | } |
||
252 | |||
253 | /** |
||
254 | * @return array<string,int> |
||
255 | */ |
||
256 | public function countEventsByMonth(string $event, int $year1, int $year2): array |
||
257 | { |
||
258 | return $this->countEventQuery($event, $year1, $year2) |
||
259 | ->groupBy(['d_month']) |
||
260 | ->pluck(new Expression('COUNT(*)'), 'd_month') |
||
261 | ->map(static fn (string $total): int => (int) $total) |
||
262 | ->all(); |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * @return array<object{month:string,sex:string,total:int}> |
||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||
267 | */ |
||
268 | public function countEventsByMonthAndSex(string $event, int $year1, int $year2): array |
||
269 | { |
||
270 | return $this->countEventQuery($event, $year1, $year2) |
||
271 | ->join('individuals', static function (JoinClause $join): void { |
||
272 | $join |
||
273 | ->on('i_id', '=', 'd_gid') |
||
274 | ->on('i_file', '=', 'd_file'); |
||
275 | }) |
||
276 | ->groupBy(['i_sex', 'd_month']) |
||
277 | ->select(['d_month', 'i_sex', new Expression('COUNT(*) AS total')]) |
||
278 | ->get() |
||
279 | ->map(static fn (object $row): object => (object) [ |
||
280 | 'month' => $row->d_month, |
||
281 | 'sex' => $row->i_sex, |
||
282 | 'total' => (int) $row->total, |
||
283 | ]) |
||
284 | ->all(); |
||
285 | } |
||
286 | |||
287 | /** |
||
288 | * @return array<int,array{0:string,1:int}> |
||
289 | */ |
||
290 | public function countEventsByCentury(string $event): array |
||
291 | { |
||
292 | return $this->countEventQuery($event, 0, 0) |
||
293 | ->select([new Expression('ROUND((d_year + 49) / 100, 0) AS century'), new Expression('COUNT(*) AS total')]) |
||
294 | ->groupBy(['century']) |
||
295 | ->orderBy('century') |
||
296 | ->get() |
||
297 | ->map(fn (object $row): array => [$this->centuryName((int) $row->century), (int) $row->total]) |
||
298 | ->all(); |
||
299 | } |
||
300 | |||
301 | public function countFamilies(): int |
||
302 | { |
||
303 | return DB::table('families') |
||
304 | ->where('f_file', '=', $this->tree->id()) |
||
305 | ->count(); |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * @param array<string> $events |
||
310 | */ |
||
311 | public function countFamiliesWithEvents(array $events): int |
||
312 | { |
||
313 | return DB::table('dates') |
||
314 | ->join('families', static function (JoinClause $join): void { |
||
315 | $join |
||
316 | ->on('f_id', '=', 'd_gid') |
||
317 | ->on('f_file', '=', 'd_file'); |
||
318 | }) |
||
319 | ->where('d_file', '=', $this->tree->id()) |
||
320 | ->whereIn('d_fact', $events) |
||
321 | ->count(); |
||
322 | } |
||
323 | |||
324 | public function countFamiliesWithNoChildren(): int |
||
325 | { |
||
326 | return DB::table('families') |
||
327 | ->where('f_file', '=', $this->tree->id()) |
||
328 | ->where('f_numchil', '=', 0) |
||
329 | ->count(); |
||
330 | } |
||
331 | |||
332 | public function countFamiliesWithSources(): int |
||
333 | { |
||
334 | return DB::table('families') |
||
335 | ->select(['f_id']) |
||
336 | ->distinct() |
||
337 | ->join('link', static function (JoinClause $join): void { |
||
338 | $join->on('f_id', '=', 'l_from') |
||
339 | ->on('f_file', '=', 'l_file'); |
||
340 | }) |
||
341 | ->where('l_file', '=', $this->tree->id()) |
||
342 | ->where('l_type', '=', 'SOUR') |
||
343 | ->count('f_id'); |
||
344 | } |
||
345 | |||
346 | /** |
||
347 | * @return array<string,int> |
||
348 | */ |
||
349 | public function countFirstChildrenByMonth(int $year1, int $year2): array |
||
350 | { |
||
351 | return $this->countFirstChildrenQuery($year1, $year2) |
||
352 | ->groupBy(['d_month']) |
||
353 | ->pluck(new Expression('COUNT(*)'), 'd_month') |
||
354 | ->map(static fn (string $total): int => (int) $total) |
||
355 | ->all(); |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * @return array<object{month:string,sex:string,total:int}> |
||
0 ignored issues
–
show
|
|||
360 | */ |
||
361 | public function countFirstChildrenByMonthAndSex(int $year1, int $year2): array |
||
362 | { |
||
363 | return $this->countFirstChildrenQuery($year1, $year2) |
||
364 | ->join('individuals', static function (JoinClause $join): void { |
||
365 | $join |
||
366 | ->on('i_file', '=', 'l_file') |
||
367 | ->on('i_id', '=', 'l_to'); |
||
368 | }) |
||
369 | ->groupBy(['d_month', 'i_sex']) |
||
370 | ->select(['d_month', 'i_sex', new Expression('COUNT(*) AS total')]) |
||
371 | ->get() |
||
372 | ->map(static fn (object $row): object => (object) [ |
||
373 | 'month' => $row->d_month, |
||
374 | 'sex' => $row->i_sex, |
||
375 | 'total' => (int) $row->total, |
||
376 | ]) |
||
377 | ->all(); |
||
378 | } |
||
379 | |||
380 | private function countFirstChildrenQuery(int $year1, int $year2): Builder |
||
381 | { |
||
382 | $first_child_subquery = DB::table('link') |
||
383 | ->join('dates', static function (JoinClause $join): void { |
||
384 | $join |
||
385 | ->on('d_gid', '=', 'l_to') |
||
386 | ->on('d_file', '=', 'l_file') |
||
387 | ->where('d_julianday1', '<>', 0) |
||
388 | ->whereIn('d_month', ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']); |
||
389 | }) |
||
390 | ->where('l_file', '=', $this->tree->id()) |
||
391 | ->where('l_type', '=', 'CHIL') |
||
392 | ->select(['l_from AS family_id', new Expression('MIN(d_julianday1) AS min_birth_jd')]) |
||
393 | ->groupBy(['family_id']); |
||
394 | |||
395 | $query = DB::table('link') |
||
396 | ->join('dates', static function (JoinClause $join): void { |
||
397 | $join |
||
398 | ->on('d_gid', '=', 'l_to') |
||
399 | ->on('d_file', '=', 'l_file'); |
||
400 | }) |
||
401 | ->joinSub($first_child_subquery, 'subquery', static function (JoinClause $join): void { |
||
402 | $join |
||
403 | ->on('family_id', '=', 'l_from') |
||
404 | ->on('min_birth_jd', '=', 'd_julianday1'); |
||
405 | }) |
||
406 | ->where('link.l_file', '=', $this->tree->id()) |
||
407 | ->where('link.l_type', '=', 'CHIL'); |
||
408 | |||
409 | if ($year1 !== 0 && $year2 !== 0) { |
||
410 | $query->whereBetween('d_year', [$year1, $year2]); |
||
411 | } |
||
412 | |||
413 | return $query; |
||
414 | } |
||
415 | |||
416 | /** |
||
417 | * @return array<string,int> |
||
418 | */ |
||
419 | public function countFirstMarriagesByMonth(Tree $tree, int $year1, int $year2): array |
||
420 | { |
||
421 | $query = DB::table('families') |
||
422 | ->join('dates', static function (JoinClause $join): void { |
||
423 | $join |
||
424 | ->on('d_gid', '=', 'f_id') |
||
425 | ->on('d_file', '=', 'f_file') |
||
426 | ->where('d_fact', '=', 'MARR') |
||
427 | ->whereIn('d_month', ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']) |
||
428 | ->where('d_julianday2', '<>', 0); |
||
429 | }) |
||
430 | ->where('f_file', '=', $tree->id()); |
||
431 | |||
432 | if ($year1 !== 0 && $year2 !== 0) { |
||
433 | $query->whereBetween('d_year', [$year1, $year2]); |
||
434 | } |
||
435 | |||
436 | $rows = $query |
||
437 | ->orderBy('d_julianday2') |
||
438 | ->select(['f_husb AS husb', 'f_wife AS wife', 'd_month AS month']) |
||
439 | ->get(); |
||
440 | |||
441 | $months = [ |
||
442 | 'JAN' => 0, |
||
443 | 'FEB' => 0, |
||
444 | 'MAR' => 0, |
||
445 | 'APR' => 0, |
||
446 | 'MAY' => 0, |
||
447 | 'JUN' => 0, |
||
448 | 'JUL' => 0, |
||
449 | 'AUG' => 0, |
||
450 | 'SEP' => 0, |
||
451 | 'OCT' => 0, |
||
452 | 'NOV' => 0, |
||
453 | 'DEC' => 0, |
||
454 | ]; |
||
455 | $seen = []; |
||
456 | |||
457 | foreach ($rows as $row) { |
||
458 | if (!in_array($row->husb, $seen, true) && !in_array($row->wife, $seen, true)) { |
||
459 | $months[$row->month]++; |
||
460 | $seen[] = $row->husb; |
||
461 | $seen[] = $row->wife; |
||
462 | } |
||
463 | } |
||
464 | |||
465 | return $months; |
||
466 | } |
||
467 | |||
468 | /** |
||
469 | * @param array<string> $names |
||
470 | */ |
||
471 | public function countGivenNames(array $names): int |
||
472 | { |
||
473 | if ($names === []) { |
||
474 | // Count number of distinct given names. |
||
475 | return DB::table('name') |
||
476 | ->where('n_file', '=', $this->tree->id()) |
||
477 | ->distinct() |
||
478 | ->where('n_givn', '<>', Individual::PRAENOMEN_NESCIO) |
||
479 | ->whereNotNull('n_givn') |
||
480 | ->count('n_givn'); |
||
481 | } |
||
482 | |||
483 | // Count number of occurrences of specific given names. |
||
484 | return DB::table('name') |
||
485 | ->where('n_file', '=', $this->tree->id()) |
||
486 | ->whereIn('n_givn', $names) |
||
487 | ->count('n_givn'); |
||
488 | } |
||
489 | |||
490 | public function countHits(string $page_name, string $page_parameter): int |
||
491 | { |
||
492 | return (int) DB::table('hit_counter') |
||
493 | ->where('gedcom_id', '=', $this->tree->id()) |
||
494 | ->where('page_name', '=', $page_name) |
||
495 | ->where('page_parameter', '=', $page_parameter) |
||
496 | ->sum('page_count'); |
||
497 | } |
||
498 | |||
499 | public function countIndividuals(): int |
||
500 | { |
||
501 | return DB::table('individuals') |
||
502 | ->where('i_file', '=', $this->tree->id()) |
||
503 | ->count(); |
||
504 | } |
||
505 | |||
506 | public function countIndividualsBySex(string $sex): int |
||
507 | { |
||
508 | return DB::table('individuals') |
||
509 | ->where('i_file', '=', $this->tree->id()) |
||
510 | ->where('i_sex', '=', $sex) |
||
511 | ->count(); |
||
512 | } |
||
513 | |||
514 | public function countIndividualsDeceased(): int |
||
515 | { |
||
516 | return DB::table('individuals') |
||
517 | ->where('i_file', '=', $this->tree->id()) |
||
518 | ->where(static function (Builder $query): void { |
||
519 | foreach (Gedcom::DEATH_EVENTS as $death_event) { |
||
520 | $query->orWhere('i_gedcom', 'LIKE', "%\n1 " . $death_event . '%'); |
||
521 | } |
||
522 | }) |
||
523 | ->count(); |
||
524 | } |
||
525 | |||
526 | public function countIndividualsLiving(): int |
||
527 | { |
||
528 | $query = DB::table('individuals') |
||
529 | ->where('i_file', '=', $this->tree->id()); |
||
530 | |||
531 | foreach (Gedcom::DEATH_EVENTS as $death_event) { |
||
532 | $query->where('i_gedcom', 'NOT LIKE', "%\n1 " . $death_event . '%'); |
||
533 | } |
||
534 | |||
535 | return $query->count(); |
||
536 | } |
||
537 | |||
538 | /** |
||
539 | * @param array<string> $events |
||
540 | */ |
||
541 | public function countIndividualsWithEvents(array $events): int |
||
542 | { |
||
543 | return DB::table('dates') |
||
544 | ->join('individuals', static function (JoinClause $join): void { |
||
545 | $join |
||
546 | ->on('i_id', '=', 'd_gid') |
||
547 | ->on('i_file', '=', 'd_file'); |
||
548 | }) |
||
549 | ->where('d_file', '=', $this->tree->id()) |
||
550 | ->whereIn('d_fact', $events) |
||
551 | ->count(); |
||
552 | } |
||
553 | |||
554 | public function countIndividualsWithSources(): int |
||
555 | { |
||
556 | return DB::table('individuals') |
||
557 | ->select(['i_id']) |
||
558 | ->distinct() |
||
559 | ->join('link', static function (JoinClause $join): void { |
||
560 | $join->on('i_id', '=', 'l_from') |
||
561 | ->on('i_file', '=', 'l_file'); |
||
562 | }) |
||
563 | ->where('l_file', '=', $this->tree->id()) |
||
564 | ->where('l_type', '=', 'SOUR') |
||
565 | ->count('i_id'); |
||
566 | } |
||
567 | |||
568 | public function countMarriedFemales(): int |
||
569 | { |
||
570 | return DB::table('families') |
||
571 | ->where('f_file', '=', $this->tree->id()) |
||
572 | ->where('f_gedcom', 'LIKE', "%\n1 MARR%") |
||
573 | ->distinct() |
||
574 | ->count('f_wife'); |
||
575 | } |
||
576 | |||
577 | public function countMarriedMales(): int |
||
578 | { |
||
579 | return DB::table('families') |
||
580 | ->where('f_file', '=', $this->tree->id()) |
||
581 | ->where('f_gedcom', 'LIKE', "%\n1 MARR%") |
||
582 | ->distinct() |
||
583 | ->count('f_husb'); |
||
584 | } |
||
585 | |||
586 | public function countMedia(string $type = 'all'): int |
||
587 | { |
||
588 | $query = DB::table('media_file') |
||
589 | ->where('m_file', '=', $this->tree->id()); |
||
590 | |||
591 | if ($type !== 'all') { |
||
592 | $query->where('source_media_type', '=', $type); |
||
593 | } |
||
594 | |||
595 | return $query->count(); |
||
596 | } |
||
597 | |||
598 | /** |
||
599 | * @return array<array{0:string,1:int}> |
||
600 | */ |
||
601 | public function countMediaByType(): array |
||
602 | { |
||
603 | $element = Registry::elementFactory()->make('OBJE:FILE:FORM:TYPE'); |
||
604 | $values = $element->values(); |
||
605 | |||
606 | return DB::table('media_file') |
||
607 | ->where('m_file', '=', $this->tree->id()) |
||
608 | ->groupBy('source_media_type') |
||
609 | ->select([new Expression('COUNT(*) AS total'), 'source_media_type']) |
||
610 | ->get() |
||
611 | ->map(static fn (object $row): array => [ |
||
612 | $values[$element->canonical($row->source_media_type)] ?? I18N::translate('Other'), |
||
613 | (int) $row->total, |
||
614 | ]) |
||
615 | ->all(); |
||
616 | } |
||
617 | |||
618 | public function countNotes(): int |
||
619 | { |
||
620 | return DB::table('other') |
||
621 | ->where('o_file', '=', $this->tree->id()) |
||
622 | ->where('o_type', '=', 'NOTE') |
||
623 | ->count(); |
||
624 | } |
||
625 | |||
626 | /** |
||
627 | * @param array<string> $events |
||
628 | */ |
||
629 | public function countOtherEvents(array $events): int |
||
630 | { |
||
631 | return DB::table('dates') |
||
632 | ->where('d_file', '=', $this->tree->id()) |
||
633 | ->whereNotIn('d_fact', $events) |
||
634 | ->count(); |
||
635 | } |
||
636 | |||
637 | /** |
||
638 | * @param array<string> $rows |
||
639 | * |
||
640 | * @return array<array{place:Place,count:int}> |
||
641 | */ |
||
642 | private function countPlaces(array $rows, string $event, int $limit): array |
||
643 | { |
||
644 | $places = []; |
||
645 | |||
646 | foreach ($rows as $gedcom) { |
||
647 | if (preg_match('/\n1 ' . $event . '(?:\n[2-9].*)*\n2 PLAC (.+)/', $gedcom, $match) === 1) { |
||
648 | $places[$match[1]] ??= 0; |
||
649 | $places[$match[1]]++; |
||
650 | } |
||
651 | } |
||
652 | |||
653 | arsort($places); |
||
654 | |||
655 | $records = []; |
||
656 | |||
657 | foreach (array_slice($places, 0, $limit) as $place => $count) { |
||
658 | $records[] = [ |
||
659 | 'place' => new Place((string) $place, $this->tree), |
||
660 | 'count' => $count, |
||
661 | ]; |
||
662 | } |
||
663 | |||
664 | return $records; |
||
665 | } |
||
666 | |||
667 | /** |
||
668 | * @return array<array{place:Place,count:int}> |
||
669 | */ |
||
670 | public function countPlacesForFamilies(string $event, int $limit): array |
||
671 | { |
||
672 | $rows = DB::table('families') |
||
673 | ->where('f_file', '=', $this->tree->id()) |
||
674 | ->where('f_gedcom', 'LIKE', "%\n2 PLAC %") |
||
675 | ->pluck('f_gedcom') |
||
676 | ->all(); |
||
677 | |||
678 | return $this->countPlaces($rows, $event, $limit); |
||
679 | } |
||
680 | |||
681 | /** |
||
682 | * @return array<array{place:Place,count:int}> |
||
683 | */ |
||
684 | public function countPlacesForIndividuals(string $event, int $limit): array |
||
685 | { |
||
686 | $rows = DB::table('individuals') |
||
687 | ->where('i_file', '=', $this->tree->id()) |
||
688 | ->where('i_gedcom', 'LIKE', "%\n2 PLAC %") |
||
689 | ->pluck('i_gedcom') |
||
690 | ->all(); |
||
691 | |||
692 | return $this->countPlaces($rows, $event, $limit); |
||
693 | } |
||
694 | |||
695 | public function countRepositories(): int |
||
696 | { |
||
697 | return DB::table('other') |
||
698 | ->where('o_file', '=', $this->tree->id()) |
||
699 | ->where('o_type', '=', 'REPO') |
||
700 | ->count(); |
||
701 | } |
||
702 | |||
703 | public function countSources(): int |
||
704 | { |
||
705 | return DB::table('sources') |
||
706 | ->where('s_file', '=', $this->tree->id()) |
||
707 | ->count(); |
||
708 | } |
||
709 | |||
710 | /** |
||
711 | * @param array<string> $names |
||
712 | */ |
||
713 | public function countSurnames(array $names): int |
||
714 | { |
||
715 | if ($names === []) { |
||
716 | // Count number of distinct surnames |
||
717 | return DB::table('name') |
||
718 | ->where('n_file', '=', $this->tree->id())->distinct() |
||
719 | ->whereNotNull('n_surn') |
||
720 | ->count('n_surn'); |
||
721 | } |
||
722 | |||
723 | // Count number of occurrences of specific surnames. |
||
724 | return DB::table('name') |
||
725 | ->where('n_file', '=', $this->tree->id()) |
||
726 | ->whereIn('n_surn', $names) |
||
727 | ->count('n_surn'); |
||
728 | } |
||
729 | |||
730 | public function countTreeFavorites(): int |
||
731 | { |
||
732 | return DB::table('favorite') |
||
733 | ->where('gedcom_id', '=', $this->tree->id()) |
||
734 | ->count(); |
||
735 | } |
||
736 | |||
737 | public function countTreeNews(): int |
||
738 | { |
||
739 | return DB::table('news') |
||
740 | ->where('gedcom_id', '=', $this->tree->id()) |
||
741 | ->count(); |
||
742 | } |
||
743 | |||
744 | public function countUserfavorites(): int |
||
745 | { |
||
746 | return DB::table('favorite') |
||
747 | ->where('user_id', '=', Auth::id()) |
||
748 | ->count(); |
||
749 | } |
||
750 | |||
751 | public function countUserJournal(): int |
||
752 | { |
||
753 | return DB::table('news') |
||
754 | ->where('user_id', '=', Auth::id()) |
||
755 | ->count(); |
||
756 | } |
||
757 | |||
758 | public function countUserMessages(): int |
||
759 | { |
||
760 | return DB::table('message') |
||
761 | ->where('user_id', '=', Auth::id()) |
||
762 | ->count(); |
||
763 | } |
||
764 | |||
765 | /** |
||
766 | * @return array<int,object{family:Family,children:int}> |
||
0 ignored issues
–
show
|
|||
767 | */ |
||
768 | public function familiesWithTheMostChildren(int $limit): array |
||
769 | { |
||
770 | return DB::table('families') |
||
771 | ->where('f_file', '=', $this->tree->id()) |
||
772 | ->orderByDesc('f_numchil') |
||
773 | ->limit($limit) |
||
774 | ->get() |
||
775 | ->map(fn (object $row): object => (object) [ |
||
776 | 'family' => Registry::familyFactory()->make($row->f_id, $this->tree, $row->f_gedcom), |
||
777 | 'children' => (int) $row->f_numchil, |
||
778 | ]) |
||
779 | ->all(); |
||
780 | } |
||
781 | |||
782 | /** |
||
783 | * @param array<string> $events |
||
784 | * |
||
785 | * @return object{id:string,year:int,fact:string,type:string}|null |
||
786 | */ |
||
787 | private function firstEvent(array $events, bool $ascending): object|null |
||
788 | { |
||
789 | if ($events === []) { |
||
790 | $events = [ |
||
791 | ...Gedcom::BIRTH_EVENTS, |
||
792 | ...Gedcom::DEATH_EVENTS, |
||
793 | ...Gedcom::MARRIAGE_EVENTS, |
||
794 | ...Gedcom::DIVORCE_EVENTS, |
||
795 | ]; |
||
796 | } |
||
797 | |||
798 | return DB::table('dates') |
||
799 | ->select(['d_gid as id', 'd_year as year', 'd_fact AS fact', 'd_type AS type']) |
||
800 | ->where('d_file', '=', $this->tree->id()) |
||
801 | ->whereIn('d_fact', $events) |
||
802 | ->where('d_julianday1', '<>', 0) |
||
803 | ->orderBy('d_julianday1', $ascending ? 'ASC' : 'DESC') |
||
804 | ->limit(1) |
||
805 | ->get() |
||
806 | ->map(static fn (object $row): object => (object) [ |
||
807 | 'id' => $row->id, |
||
808 | 'year' => (int) $row->year, |
||
809 | 'fact' => $row->fact, |
||
810 | 'type' => $row->type, |
||
811 | ]) |
||
812 | ->first(); |
||
813 | } |
||
814 | |||
815 | /** |
||
816 | * @param array<string> $events |
||
817 | */ |
||
818 | public function firstEventName(array $events, bool $ascending): string |
||
819 | { |
||
820 | $row = $this->firstEvent($events, $ascending); |
||
821 | |||
822 | if ($row !== null) { |
||
823 | $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree); |
||
824 | |||
825 | if ($record instanceof GedcomRecord) { |
||
826 | return '<a href="' . e($record->url()) . '">' . $record->fullName() . '</a>'; |
||
827 | } |
||
828 | } |
||
829 | |||
830 | return ''; |
||
831 | } |
||
832 | |||
833 | /** |
||
834 | * @param array<string> $events |
||
835 | */ |
||
836 | public function firstEventPlace(array $events, bool $ascending): string |
||
837 | { |
||
838 | $row = $this->firstEvent($events, $ascending); |
||
839 | |||
840 | if ($row !== null) { |
||
841 | $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree); |
||
842 | $fact = null; |
||
843 | |||
844 | if ($record instanceof GedcomRecord) { |
||
845 | $fact = $record->facts([$row->fact])->first(); |
||
846 | } |
||
847 | |||
848 | if ($fact instanceof Fact) { |
||
849 | return $fact->place()->shortName(); |
||
850 | } |
||
851 | } |
||
852 | |||
853 | return I18N::translate('Private'); |
||
854 | } |
||
855 | |||
856 | /** |
||
857 | * @param array<string> $events |
||
858 | */ |
||
859 | public function firstEventRecord(array $events, bool $ascending): string |
||
860 | { |
||
861 | $row = $this->firstEvent($events, $ascending); |
||
862 | $result = I18N::translate('This information is not available.'); |
||
863 | |||
864 | if ($row !== null) { |
||
865 | $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree); |
||
866 | |||
867 | if ($record instanceof GedcomRecord && $record->canShow()) { |
||
868 | $result = $record->formatList(); |
||
869 | } else { |
||
870 | $result = I18N::translate('This information is private and cannot be shown.'); |
||
871 | } |
||
872 | } |
||
873 | |||
874 | return $result; |
||
875 | } |
||
876 | |||
877 | /** |
||
878 | * @param array<string> $events |
||
879 | */ |
||
880 | public function firstEventType(array $events, bool $ascending): string |
||
881 | { |
||
882 | $row = $this->firstEvent($events, $ascending); |
||
883 | |||
884 | if ($row === null) { |
||
885 | return ''; |
||
886 | } |
||
887 | |||
888 | foreach ([Individual::RECORD_TYPE, Family::RECORD_TYPE] as $record_type) { |
||
889 | $element = Registry::elementFactory()->make($record_type . ':' . $row->fact); |
||
890 | |||
891 | if (!$element instanceof UnknownElement) { |
||
892 | return $element->label(); |
||
893 | } |
||
894 | } |
||
895 | |||
896 | return $row->fact; |
||
897 | } |
||
898 | |||
899 | /** |
||
900 | * @param array<string> $events |
||
901 | */ |
||
902 | public function firstEventYear(array $events, bool $ascending): string |
||
903 | { |
||
904 | $row = $this->firstEvent($events, $ascending); |
||
905 | |||
906 | if ($row === null) { |
||
907 | return '-'; |
||
908 | } |
||
909 | |||
910 | if ($row->year < 0) { |
||
911 | $date = new Date($row->type . ' ' . abs($row->year) . ' B.C.'); |
||
912 | } else { |
||
913 | $date = new Date($row->type . ' ' . $row->year); |
||
914 | } |
||
915 | |||
916 | return $date->display(); |
||
917 | } |
||
918 | |||
919 | public function isUserLoggedIn(int|null $user_id): bool |
||
920 | { |
||
921 | return $user_id !== null && DB::table('session') |
||
922 | ->where('user_id', '=', $user_id) |
||
923 | ->exists(); |
||
924 | } |
||
925 | |||
926 | public function latestUserId(): int|null |
||
927 | { |
||
928 | $user_id = DB::table('user') |
||
929 | ->select(['user.user_id']) |
||
930 | ->leftJoin('user_setting', 'user.user_id', '=', 'user_setting.user_id') |
||
931 | ->where('setting_name', '=', UserInterface::PREF_TIMESTAMP_REGISTERED) |
||
932 | ->orderByDesc('setting_value') |
||
933 | ->value('user_id'); |
||
934 | |||
935 | if ($user_id === null) { |
||
936 | return null; |
||
937 | } |
||
938 | |||
939 | return (int) $user_id; |
||
940 | } |
||
941 | |||
942 | /** |
||
943 | * @return array<object{family:Family,child1:Individual,child2:Individual,age:string}> |
||
0 ignored issues
–
show
|
|||
944 | */ |
||
945 | public function maximumAgeBetweenSiblings(int $limit): array |
||
946 | { |
||
947 | return DB::table('link AS link1') |
||
948 | ->join('link AS link2', static function (JoinClause $join): void { |
||
949 | $join |
||
950 | ->on('link2.l_from', '=', 'link1.l_from') |
||
951 | ->on('link2.l_type', '=', 'link1.l_type') |
||
952 | ->on('link2.l_file', '=', 'link1.l_file'); |
||
953 | }) |
||
954 | ->join('dates AS child1', static function (JoinClause $join): void { |
||
955 | $join |
||
956 | ->on('child1.d_gid', '=', 'link1.l_to') |
||
957 | ->on('child1.d_file', '=', 'link1.l_file') |
||
958 | ->where('child1.d_fact', '=', 'BIRT') |
||
959 | ->where('child1.d_julianday1', '<>', 0); |
||
960 | }) |
||
961 | ->join('dates AS child2', static function (JoinClause $join): void { |
||
962 | $join |
||
963 | ->on('child2.d_gid', '=', 'link2.l_to') |
||
964 | ->on('child2.d_file', '=', 'link2.l_file') |
||
965 | ->where('child2.d_fact', '=', 'BIRT') |
||
966 | ->whereColumn('child2.d_julianday2', '>', 'child1.d_julianday1'); |
||
967 | }) |
||
968 | ->where('link1.l_type', '=', 'CHIL') |
||
969 | ->where('link1.l_file', '=', $this->tree->id()) |
||
970 | ->distinct() |
||
971 | ->select(['link1.l_from AS family', 'link1.l_to AS child1', 'link2.l_to AS child2', new Expression(DB::prefix('child2.d_julianday2') . ' - ' . DB::prefix('child1.d_julianday1') . ' AS age')]) |
||
972 | ->orderBy('age', 'DESC') |
||
973 | ->take($limit) |
||
974 | ->get() |
||
975 | ->map(fn (object $row): object => (object) [ |
||
976 | 'family' => Registry::familyFactory()->make($row->family, $this->tree), |
||
977 | 'child1' => Registry::individualFactory()->make($row->child1, $this->tree), |
||
978 | 'child2' => Registry::individualFactory()->make($row->child2, $this->tree), |
||
979 | 'age' => $this->calculateAge((int) $row->age), |
||
980 | ]) |
||
981 | ->filter(static fn (object $row): bool => $row->family !== null) |
||
982 | ->filter(static fn (object $row): bool => $row->child1 !== null) |
||
983 | ->filter(static fn (object $row): bool => $row->child2 !== null) |
||
984 | ->all(); |
||
985 | } |
||
986 | |||
987 | /** |
||
988 | * @return Collection<int,Individual> |
||
989 | */ |
||
990 | public function topTenOldestAliveQuery(string $sex, int $limit): Collection |
||
991 | { |
||
992 | $query = DB::table('dates') |
||
993 | ->join('individuals', static function (JoinClause $join): void { |
||
994 | $join |
||
995 | ->on('i_id', '=', 'd_gid') |
||
996 | ->on('i_file', '=', 'd_file'); |
||
997 | }) |
||
998 | ->where('d_file', '=', $this->tree->id()) |
||
999 | ->where('d_julianday1', '<>', 0) |
||
1000 | ->where('d_fact', '=', 'BIRT') |
||
1001 | ->where('i_gedcom', 'NOT LIKE', "%\n1 DEAT%") |
||
1002 | ->where('i_gedcom', 'NOT LIKE', "%\n1 BURI%") |
||
1003 | ->where('i_gedcom', 'NOT LIKE', "%\n1 CREM%"); |
||
1004 | |||
1005 | if ($sex === 'F' || $sex === 'M' || $sex === 'U' || $sex === 'X') { |
||
1006 | $query->where('i_sex', '=', $sex); |
||
1007 | } |
||
1008 | |||
1009 | return $query |
||
1010 | ->groupBy(['i_id', 'i_file']) |
||
1011 | ->orderBy(new Expression('MIN(d_julianday1)')) |
||
1012 | ->select(['individuals.*']) |
||
1013 | ->take($limit) |
||
1014 | ->get() |
||
1015 | ->map(Registry::individualFactory()->mapper($this->tree)) |
||
1016 | ->filter(GedcomRecord::accessFilter()); |
||
1017 | } |
||
1018 | |||
1019 | public function commonSurnamesQuery(string $type, bool $totals, int $threshold, int $limit, string $sort): string |
||
1020 | { |
||
1021 | $surnames = $this->commonSurnames($limit, $threshold, $sort); |
||
1022 | |||
1023 | // find a module providing individual lists |
||
1024 | $module = Registry::container()->get(ModuleService::class) |
||
1025 | ->findByComponent(ModuleListInterface::class, $this->tree, Auth::user()) |
||
1026 | ->first(static fn (ModuleInterface $module): bool => $module instanceof IndividualListModule); |
||
1027 | |||
1028 | if ($type === 'list') { |
||
1029 | return view('lists/surnames-bullet-list', [ |
||
1030 | 'surnames' => $surnames, |
||
1031 | 'module' => $module, |
||
1032 | 'totals' => $totals, |
||
1033 | 'tree' => $this->tree, |
||
1034 | ]); |
||
1035 | } |
||
1036 | |||
1037 | return view('lists/surnames-compact-list', [ |
||
1038 | 'surnames' => $surnames, |
||
1039 | 'module' => $module, |
||
1040 | 'totals' => $totals, |
||
1041 | 'tree' => $this->tree, |
||
1042 | ]); |
||
1043 | } |
||
1044 | |||
1045 | /** |
||
1046 | * @return array<object{age:float,century:int,sex:string}> |
||
0 ignored issues
–
show
|
|||
1047 | */ |
||
1048 | public function statsAge(): array |
||
1049 | { |
||
1050 | return DB::table('individuals') |
||
1051 | ->select([ |
||
1052 | new Expression('AVG(' . DB::prefix('death.d_julianday2') . ' - ' . DB::prefix('birth.d_julianday1') . ') / 365.25 AS age'), |
||
1053 | new Expression('ROUND((' . DB::prefix('death.d_year') . ' + 49) / 100, 0) AS century'), |
||
1054 | 'i_sex AS sex' |
||
1055 | ]) |
||
1056 | ->join('dates AS birth', static function (JoinClause $join): void { |
||
1057 | $join |
||
1058 | ->on('birth.d_file', '=', 'i_file') |
||
1059 | ->on('birth.d_gid', '=', 'i_id'); |
||
1060 | }) |
||
1061 | ->join('dates AS death', static function (JoinClause $join): void { |
||
1062 | $join |
||
1063 | ->on('death.d_file', '=', 'i_file') |
||
1064 | ->on('death.d_gid', '=', 'i_id'); |
||
1065 | }) |
||
1066 | ->where('i_file', '=', $this->tree->id()) |
||
1067 | ->where('birth.d_fact', '=', 'BIRT') |
||
1068 | ->where('death.d_fact', '=', 'DEAT') |
||
1069 | ->whereIn('birth.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']) |
||
1070 | ->whereIn('death.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']) |
||
1071 | ->whereColumn('death.d_julianday1', '>=', 'birth.d_julianday2') |
||
1072 | ->where('birth.d_julianday2', '<>', 0) |
||
1073 | ->groupBy(['century', 'sex']) |
||
1074 | ->orderBy('century') |
||
1075 | ->orderBy('sex') |
||
1076 | ->get() |
||
1077 | ->map(static fn (object $row): object => (object) [ |
||
1078 | 'age' => (float) $row->age, |
||
1079 | 'century' => (int) $row->century, |
||
1080 | 'sex' => $row->sex, |
||
1081 | ]) |
||
1082 | ->all(); |
||
1083 | } |
||
1084 | |||
1085 | /** |
||
1086 | * General query on ages. |
||
1087 | * |
||
1088 | * @return array<object{days:int}> |
||
0 ignored issues
–
show
|
|||
1089 | */ |
||
1090 | public function statsAgeQuery(string $sex, int $year1, int $year2): array |
||
1091 | { |
||
1092 | $query = $this->birthAndDeathQuery($sex); |
||
1093 | |||
1094 | if ($year1 !== 0 && $year2 !== 0) { |
||
1095 | $query |
||
1096 | ->whereIn('birth.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']) |
||
1097 | ->whereIn('death.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']) |
||
1098 | ->whereBetween('death.d_year', [$year1, $year2]); |
||
1099 | } |
||
1100 | |||
1101 | return $query |
||
1102 | ->select([new Expression(DB::prefix('death.d_julianday2') . ' - ' . DB::prefix('birth.d_julianday1') . ' AS days')]) |
||
1103 | ->orderBy('days', 'desc') |
||
1104 | ->get() |
||
1105 | ->map(static fn (object $row): object => (object) ['days' => (int) $row->days]) |
||
1106 | ->all(); |
||
1107 | } |
||
1108 | |||
1109 | private function birthAndDeathQuery(string $sex): Builder |
||
1110 | { |
||
1111 | $query = DB::table('individuals') |
||
1112 | ->where('i_file', '=', $this->tree->id()) |
||
1113 | ->join('dates AS birth', static function (JoinClause $join): void { |
||
1114 | $join |
||
1115 | ->on('birth.d_file', '=', 'i_file') |
||
1116 | ->on('birth.d_gid', '=', 'i_id'); |
||
1117 | }) |
||
1118 | ->join('dates AS death', static function (JoinClause $join): void { |
||
1119 | $join |
||
1120 | ->on('death.d_file', '=', 'i_file') |
||
1121 | ->on('death.d_gid', '=', 'i_id'); |
||
1122 | }) |
||
1123 | ->where('birth.d_fact', '=', 'BIRT') |
||
1124 | ->where('death.d_fact', '=', 'DEAT') |
||
1125 | ->whereColumn('death.d_julianday1', '>=', 'birth.d_julianday2') |
||
1126 | ->where('birth.d_julianday2', '<>', 0); |
||
1127 | |||
1128 | if ($sex !== 'ALL') { |
||
1129 | $query->where('i_sex', '=', $sex); |
||
1130 | } |
||
1131 | |||
1132 | return $query; |
||
1133 | } |
||
1134 | |||
1135 | /** |
||
1136 | * @return object{individual:Individual,days:int}|null |
||
1137 | */ |
||
1138 | public function longlifeQuery(string $sex): object|null |
||
1139 | { |
||
1140 | return $this->birthAndDeathQuery($sex) |
||
1141 | ->orderBy('days', 'desc') |
||
1142 | ->select(['individuals.*', new Expression(DB::prefix('death.d_julianday2') . ' - ' . DB::prefix('birth.d_julianday1') . ' AS days')]) |
||
1143 | ->take(1) |
||
1144 | ->get() |
||
1145 | ->map(fn (object $row): object => (object) [ |
||
1146 | 'individual' => Registry::individualFactory()->mapper($this->tree)($row), |
||
1147 | 'days' => (int) $row->days |
||
1148 | ]) |
||
1149 | ->first(); |
||
1150 | } |
||
1151 | |||
1152 | /** |
||
1153 | * @return Collection<int,object{individual:Individual,days:int}> |
||
1154 | */ |
||
1155 | public function topTenOldestQuery(string $sex, int $limit): Collection |
||
1156 | { |
||
1157 | return $this->birthAndDeathQuery($sex) |
||
1158 | ->groupBy(['i_id', 'i_file']) |
||
1159 | ->orderBy('days', 'desc') |
||
1160 | ->select(['individuals.*', new Expression('MAX(' . DB::prefix('death.d_julianday2') . ' - ' . DB::prefix('birth.d_julianday1') . ') AS days')]) |
||
1161 | ->take($limit) |
||
1162 | ->get() |
||
1163 | ->map(fn (object $row): object => (object) [ |
||
1164 | 'individual' => Registry::individualFactory()->mapper($this->tree)($row), |
||
1165 | 'days' => (int) $row->days |
||
1166 | ]); |
||
1167 | } |
||
1168 | |||
1169 | /** |
||
1170 | * @return array<string> |
||
1171 | */ |
||
1172 | private function getIso3166Countries(): array |
||
1173 | { |
||
1174 | // Get the country names for each language |
||
1175 | $country_to_iso3166 = []; |
||
1176 | |||
1177 | $current_language = I18N::languageTag(); |
||
1178 | |||
1179 | foreach (I18N::activeLocales() as $locale) { |
||
1180 | I18N::init($locale->languageTag()); |
||
1181 | |||
1182 | $countries = $this->getAllCountries(); |
||
1183 | |||
1184 | foreach ($this->iso3166() as $three => $two) { |
||
1185 | $country_to_iso3166[$three] = $two; |
||
1186 | $country_to_iso3166[$countries[$three]] = $two; |
||
1187 | } |
||
1188 | } |
||
1189 | |||
1190 | I18N::init($current_language); |
||
1191 | |||
1192 | return $country_to_iso3166; |
||
1193 | } |
||
1194 | |||
1195 | /** |
||
1196 | * Returns the data structure required by google geochart. |
||
1197 | * |
||
1198 | * @param array<int> $places |
||
1199 | * |
||
1200 | * @return array<int,array<int|string|array<string,string>>> |
||
1201 | */ |
||
1202 | private function createChartData(array $places): array |
||
1203 | { |
||
1204 | $data = [ |
||
1205 | [ |
||
1206 | I18N::translate('Country'), |
||
1207 | I18N::translate('Total'), |
||
1208 | ], |
||
1209 | ]; |
||
1210 | |||
1211 | // webtrees uses 3-letter country codes and localised country names, but google uses 2 letter codes. |
||
1212 | foreach ($places as $country => $count) { |
||
1213 | $data[] = [ |
||
1214 | [ |
||
1215 | 'v' => $country, |
||
1216 | 'f' => $this->mapTwoLetterToName($country), |
||
1217 | ], |
||
1218 | $count |
||
1219 | ]; |
||
1220 | } |
||
1221 | |||
1222 | return $data; |
||
1223 | } |
||
1224 | |||
1225 | /** |
||
1226 | * @return array<string,int> |
||
1227 | */ |
||
1228 | private function countIndividualsByCountry(Tree $tree): array |
||
1229 | { |
||
1230 | $rows = DB::table('places') |
||
1231 | ->where('p_file', '=', $tree->id()) |
||
1232 | ->where('p_parent_id', '=', 0) |
||
1233 | ->join('placelinks', static function (JoinClause $join): void { |
||
1234 | $join |
||
1235 | ->on('pl_file', '=', 'p_file') |
||
1236 | ->on('pl_p_id', '=', 'p_id'); |
||
1237 | }) |
||
1238 | ->join('individuals', static function (JoinClause $join): void { |
||
1239 | $join |
||
1240 | ->on('pl_file', '=', 'i_file') |
||
1241 | ->on('pl_gid', '=', 'i_id'); |
||
1242 | }) |
||
1243 | ->groupBy('p_place') |
||
1244 | ->pluck(new Expression('COUNT(*)'), 'p_place') |
||
1245 | ->all(); |
||
1246 | |||
1247 | $totals = []; |
||
1248 | |||
1249 | $country_to_iso3166 = $this->getIso3166Countries(); |
||
1250 | |||
1251 | foreach ($rows as $country => $count) { |
||
1252 | $country_code = $country_to_iso3166[$country] ?? null; |
||
1253 | |||
1254 | if ($country_code !== null) { |
||
1255 | $totals[$country_code] ??= 0; |
||
1256 | $totals[$country_code] += $count; |
||
1257 | } |
||
1258 | } |
||
1259 | |||
1260 | return $totals; |
||
1261 | } |
||
1262 | |||
1263 | /** |
||
1264 | * @return array<string,int> |
||
1265 | */ |
||
1266 | private function countSurnamesByCountry(Tree $tree, string $surname): array |
||
1267 | { |
||
1268 | $rows = |
||
1269 | DB::table('places') |
||
1270 | ->where('p_file', '=', $tree->id()) |
||
1271 | ->where('p_parent_id', '=', 0) |
||
1272 | ->join('placelinks', static function (JoinClause $join): void { |
||
1273 | $join |
||
1274 | ->on('pl_file', '=', 'p_file') |
||
1275 | ->on('pl_p_id', '=', 'p_id'); |
||
1276 | }) |
||
1277 | ->join('name', static function (JoinClause $join): void { |
||
1278 | $join |
||
1279 | ->on('n_file', '=', 'pl_file') |
||
1280 | ->on('n_id', '=', 'pl_gid'); |
||
1281 | }) |
||
1282 | ->where('n_surn', '=', $surname) |
||
1283 | ->groupBy('p_place') |
||
1284 | ->pluck(new Expression('COUNT(*)'), 'p_place'); |
||
1285 | |||
1286 | $totals = []; |
||
1287 | |||
1288 | $country_to_iso3166 = $this->getIso3166Countries(); |
||
1289 | |||
1290 | foreach ($rows as $country => $count) { |
||
1291 | $country_code = $country_to_iso3166[$country] ?? null; |
||
1292 | |||
1293 | if ($country_code !== null) { |
||
1294 | $totals[$country_code] ??= 0; |
||
1295 | $totals[$country_code] += $count; |
||
1296 | } |
||
1297 | } |
||
1298 | |||
1299 | return $totals; |
||
1300 | } |
||
1301 | |||
1302 | /** |
||
1303 | * @return array<string,int> |
||
1304 | */ |
||
1305 | private function countFamilyEventsByCountry(Tree $tree, string $fact): array |
||
1306 | { |
||
1307 | $query = DB::table('places') |
||
1308 | ->where('p_file', '=', $tree->id()) |
||
1309 | ->where('p_parent_id', '=', 0) |
||
1310 | ->join('placelinks', static function (JoinClause $join): void { |
||
1311 | $join |
||
1312 | ->on('pl_file', '=', 'p_file') |
||
1313 | ->on('pl_p_id', '=', 'p_id'); |
||
1314 | }) |
||
1315 | ->join('families', static function (JoinClause $join): void { |
||
1316 | $join |
||
1317 | ->on('pl_file', '=', 'f_file') |
||
1318 | ->on('pl_gid', '=', 'f_id'); |
||
1319 | }) |
||
1320 | ->select(['p_place AS place', 'f_gedcom AS gedcom']); |
||
1321 | |||
1322 | return $this->filterEventPlaces($query, $fact); |
||
1323 | } |
||
1324 | |||
1325 | /** |
||
1326 | * @return array<string,int> |
||
1327 | */ |
||
1328 | private function countIndividualEventsByCountry(Tree $tree, string $fact): array |
||
1329 | { |
||
1330 | $query = DB::table('places') |
||
1331 | ->where('p_file', '=', $tree->id()) |
||
1332 | ->where('p_parent_id', '=', 0) |
||
1333 | ->join('placelinks', static function (JoinClause $join): void { |
||
1334 | $join |
||
1335 | ->on('pl_file', '=', 'p_file') |
||
1336 | ->on('pl_p_id', '=', 'p_id'); |
||
1337 | }) |
||
1338 | ->join('individuals', static function (JoinClause $join): void { |
||
1339 | $join |
||
1340 | ->on('pl_file', '=', 'i_file') |
||
1341 | ->on('pl_gid', '=', 'i_id'); |
||
1342 | }) |
||
1343 | ->select(['p_place AS place', 'i_gedcom AS gedcom']); |
||
1344 | |||
1345 | return $this->filterEventPlaces($query, $fact); |
||
1346 | } |
||
1347 | |||
1348 | /** |
||
1349 | * @return array<string,int> |
||
1350 | */ |
||
1351 | private function filterEventPlaces(Builder $query, string $fact): array |
||
1352 | { |
||
1353 | $totals = []; |
||
1354 | |||
1355 | $country_to_iso3166 = $this->getIso3166Countries(); |
||
1356 | |||
1357 | foreach ($query->cursor() as $row) { |
||
1358 | $country_code = $country_to_iso3166[$row->place] ?? null; |
||
1359 | |||
1360 | if ($country_code !== null) { |
||
1361 | $place_regex = '/\n1 ' . $fact . '(?:\n[2-9].*)*\n2 PLAC.*[, ]' . preg_quote($row->place, '(?:\n|$)/i') . '\n/'; |
||
1362 | |||
1363 | if (preg_match($place_regex, $row->gedcom) === 1) { |
||
1364 | $totals[$country_code] = 1 + ($totals[$country_code] ?? 0); |
||
1365 | } |
||
1366 | } |
||
1367 | } |
||
1368 | |||
1369 | return $totals; |
||
1370 | } |
||
1371 | |||
1372 | /** |
||
1373 | * Create a chart showing where events occurred. |
||
1374 | * |
||
1375 | * @param string $chart_shows The type of chart map to show |
||
1376 | * @param string $chart_type The type of chart to show |
||
1377 | * @param string $surname The surname for surname based distribution chart |
||
1378 | */ |
||
1379 | public function chartDistribution( |
||
1380 | string $chart_shows = 'world', |
||
1381 | string $chart_type = '', |
||
1382 | string $surname = '' |
||
1383 | ): string { |
||
1384 | switch ($chart_type) { |
||
1385 | case 'surname_distribution_chart': |
||
1386 | $chart_title = I18N::translate('Surname distribution chart') . ': ' . $surname; |
||
1387 | $surnames = $this->commonSurnames(1, 0, 'count'); |
||
1388 | $surname = implode(I18N::$list_separator, array_keys(array_shift($surnames) ?? [])); |
||
1389 | $data = $this->createChartData($this->countSurnamesByCountry($this->tree, $surname)); |
||
1390 | break; |
||
1391 | |||
1392 | case 'birth_distribution_chart': |
||
1393 | $chart_title = I18N::translate('Birth by country'); |
||
1394 | $data = $this->createChartData($this->countIndividualEventsByCountry($this->tree, 'BIRT')); |
||
1395 | break; |
||
1396 | |||
1397 | case 'death_distribution_chart': |
||
1398 | $chart_title = I18N::translate('Death by country'); |
||
1399 | $data = $this->createChartData($this->countIndividualEventsByCountry($this->tree, 'DEAT')); |
||
1400 | break; |
||
1401 | |||
1402 | case 'marriage_distribution_chart': |
||
1403 | $chart_title = I18N::translate('Marriage by country'); |
||
1404 | $data = $this->createChartData($this->countFamilyEventsByCountry($this->tree, 'MARR')); |
||
1405 | break; |
||
1406 | |||
1407 | case 'indi_distribution_chart': |
||
1408 | default: |
||
1409 | $chart_title = I18N::translate('Individual distribution chart'); |
||
1410 | $data = $this->createChartData($this->countIndividualsByCountry($this->tree)); |
||
1411 | break; |
||
1412 | } |
||
1413 | |||
1414 | return view('statistics/other/charts/geo', [ |
||
1415 | 'chart_title' => $chart_title, |
||
1416 | 'chart_color2' => '84beff', |
||
1417 | 'chart_color3' => 'c3dfff', |
||
1418 | 'region' => $chart_shows, |
||
1419 | 'data' => $data, |
||
1420 | 'language' => I18N::languageTag(), |
||
1421 | ]); |
||
1422 | } |
||
1423 | |||
1424 | /** |
||
1425 | * @return array<array{family:Family,count:int}> |
||
1426 | */ |
||
1427 | private function topTenGrandFamilyQuery(int $limit): array |
||
1428 | { |
||
1429 | return DB::table('families') |
||
1430 | ->join('link AS children', static function (JoinClause $join): void { |
||
1431 | $join |
||
1432 | ->on('children.l_from', '=', 'f_id') |
||
1433 | ->on('children.l_file', '=', 'f_file') |
||
1434 | ->where('children.l_type', '=', 'CHIL'); |
||
1435 | })->join('link AS mchildren', static function (JoinClause $join): void { |
||
1436 | $join |
||
1437 | ->on('mchildren.l_file', '=', 'children.l_file') |
||
1438 | ->on('mchildren.l_from', '=', 'children.l_to') |
||
1439 | ->where('mchildren.l_type', '=', 'FAMS'); |
||
1440 | })->join('link AS gchildren', static function (JoinClause $join): void { |
||
1441 | $join |
||
1442 | ->on('gchildren.l_file', '=', 'mchildren.l_file') |
||
1443 | ->on('gchildren.l_from', '=', 'mchildren.l_to') |
||
1444 | ->where('gchildren.l_type', '=', 'CHIL'); |
||
1445 | }) |
||
1446 | ->where('f_file', '=', $this->tree->id()) |
||
1447 | ->groupBy(['f_id', 'f_file']) |
||
1448 | ->orderBy(new Expression('COUNT(*)'), 'DESC') |
||
1449 | ->select(['families.*']) |
||
1450 | ->limit($limit) |
||
1451 | ->get() |
||
1452 | ->map(Registry::familyFactory()->mapper($this->tree)) |
||
1453 | ->filter(GedcomRecord::accessFilter()) |
||
1454 | ->map(static function (Family $family): array { |
||
1455 | $count = 0; |
||
1456 | foreach ($family->children() as $child) { |
||
1457 | foreach ($child->spouseFamilies() as $spouse_family) { |
||
1458 | $count += $spouse_family->children()->count(); |
||
1459 | } |
||
1460 | } |
||
1461 | |||
1462 | return [ |
||
1463 | 'family' => $family, |
||
1464 | 'count' => $count, |
||
1465 | ]; |
||
1466 | }) |
||
1467 | ->all(); |
||
1468 | } |
||
1469 | |||
1470 | public function topTenLargestGrandFamily(int $limit = 10): string |
||
1471 | { |
||
1472 | return view('statistics/families/top10-nolist-grand', [ |
||
1473 | 'records' => $this->topTenGrandFamilyQuery($limit), |
||
1474 | ]); |
||
1475 | } |
||
1476 | |||
1477 | public function topTenLargestGrandFamilyList(int $limit = 10): string |
||
1478 | { |
||
1479 | return view('statistics/families/top10-list-grand', [ |
||
1480 | 'records' => $this->topTenGrandFamilyQuery($limit), |
||
1481 | ]); |
||
1482 | } |
||
1483 | |||
1484 | public function noChildrenFamiliesList(string $type = 'list'): string |
||
1485 | { |
||
1486 | $families = DB::table('families') |
||
1487 | ->where('f_file', '=', $this->tree->id()) |
||
1488 | ->where('f_numchil', '=', 0) |
||
1489 | ->get() |
||
1490 | ->map(Registry::familyFactory()->mapper($this->tree)) |
||
1491 | ->filter(GedcomRecord::accessFilter()); |
||
1492 | |||
1493 | $top10 = []; |
||
1494 | |||
1495 | foreach ($families as $family) { |
||
1496 | if ($type === 'list') { |
||
1497 | $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->fullName() . '</a></li>'; |
||
1498 | } else { |
||
1499 | $top10[] = '<a href="' . e($family->url()) . '">' . $family->fullName() . '</a>'; |
||
1500 | } |
||
1501 | } |
||
1502 | |||
1503 | if ($type === 'list') { |
||
1504 | $top10 = implode('', $top10); |
||
1505 | } else { |
||
1506 | $top10 = implode('; ', $top10); |
||
1507 | } |
||
1508 | |||
1509 | if ($type === 'list') { |
||
1510 | return '<ul>' . $top10 . '</ul>'; |
||
1511 | } |
||
1512 | |||
1513 | return $top10; |
||
1514 | } |
||
1515 | |||
1516 | /** |
||
1517 | * Returns the calculated age the time of event. |
||
1518 | * |
||
1519 | * @param int $age The age from the database record |
||
1520 | */ |
||
1521 | private function calculateAge(int $age): string |
||
1522 | { |
||
1523 | if ($age < 31) { |
||
1524 | return I18N::plural('%s day', '%s days', $age, I18N::number($age)); |
||
1525 | } |
||
1526 | |||
1527 | if ($age < 365) { |
||
1528 | $months = (int) ($age / 30.5); |
||
1529 | |||
1530 | return I18N::plural('%s month', '%s months', $months, I18N::number($months)); |
||
1531 | } |
||
1532 | |||
1533 | $years = (int) ($age / 365.25); |
||
1534 | |||
1535 | return I18N::plural('%s year', '%s years', $years, I18N::number($years)); |
||
1536 | } |
||
1537 | |||
1538 | public function topAgeBetweenSiblings(): string |
||
1539 | { |
||
1540 | $rows = $this->maximumAgeBetweenSiblings(1); |
||
1541 | |||
1542 | if ($rows === []) { |
||
1543 | return I18N::translate('This information is not available.'); |
||
1544 | } |
||
1545 | |||
1546 | return $rows[0]->age; |
||
1547 | } |
||
1548 | |||
1549 | public function topAgeBetweenSiblingsFullName(): string |
||
1550 | { |
||
1551 | $rows = $this->maximumAgeBetweenSiblings(1); |
||
1552 | |||
1553 | if ($rows === []) { |
||
1554 | return I18N::translate('This information is not available.'); |
||
1555 | } |
||
1556 | |||
1557 | return view('statistics/families/top10-nolist-age', ['record' => (array) $rows[0]]); |
||
1558 | } |
||
1559 | |||
1560 | public function topAgeBetweenSiblingsList(int $limit, bool $unique_families): string |
||
1561 | { |
||
1562 | $rows = $this->maximumAgeBetweenSiblings($limit); |
||
1563 | $records = []; |
||
1564 | $dist = []; |
||
1565 | |||
1566 | foreach ($rows as $row) { |
||
1567 | if (!$unique_families || !in_array($row->family, $dist, true)) { |
||
1568 | $records[] = [ |
||
1569 | 'child1' => $row->child1, |
||
1570 | 'child2' => $row->child2, |
||
1571 | 'family' => $row->family, |
||
1572 | 'age' => $row->age, |
||
1573 | ]; |
||
1574 | |||
1575 | $dist[] = $row->family; |
||
1576 | } |
||
1577 | } |
||
1578 | |||
1579 | return view('statistics/families/top10-list-age', [ |
||
1580 | 'records' => $records, |
||
1581 | ]); |
||
1582 | } |
||
1583 | |||
1584 | /** |
||
1585 | * @return array<object{f_numchil:int,total:int}> |
||
0 ignored issues
–
show
|
|||
1586 | */ |
||
1587 | public function statsChildrenQuery(int $year1, int $year2): array |
||
1588 | { |
||
1589 | $query = DB::table('families') |
||
1590 | ->where('f_file', '=', $this->tree->id()) |
||
1591 | ->groupBy(['f_numchil']) |
||
1592 | ->select(['f_numchil', new Expression('COUNT(*) AS total')]); |
||
1593 | |||
1594 | if ($year1 !== 0 && $year2 !== 0) { |
||
1595 | $query |
||
1596 | ->join('dates', static function (JoinClause $join): void { |
||
1597 | $join |
||
1598 | ->on('d_file', '=', 'f_file') |
||
1599 | ->on('d_gid', '=', 'f_id'); |
||
1600 | }) |
||
1601 | ->where('d_fact', '=', 'MARR') |
||
1602 | ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@']) |
||
1603 | ->whereBetween('d_year', [$year1, $year2]); |
||
1604 | } |
||
1605 | |||
1606 | return $query->get() |
||
1607 | ->map(static fn (object $row): object => (object) [ |
||
1608 | 'f_numchil' => (int) $row->f_numchil, |
||
1609 | 'total' => (int) $row->total, |
||
1610 | ]) |
||
1611 | ->all(); |
||
1612 | } |
||
1613 | |||
1614 | /** |
||
1615 | * @return array<array{family:Family,count:int}> |
||
1616 | */ |
||
1617 | private function topTenFamilyQuery(int $limit): array |
||
1618 | { |
||
1619 | return DB::table('families') |
||
1620 | ->where('f_file', '=', $this->tree->id()) |
||
1621 | ->orderBy('f_numchil', 'DESC') |
||
1622 | ->limit($limit) |
||
1623 | ->get() |
||
1624 | ->map(Registry::familyFactory()->mapper($this->tree)) |
||
1625 | ->filter(GedcomRecord::accessFilter()) |
||
1626 | ->map(static fn (Family $family): array => [ |
||
1627 | 'family' => $family, |
||
1628 | 'count' => $family->numberOfChildren(), |
||
1629 | ]) |
||
1630 | ->all(); |
||
1631 | } |
||
1632 | |||
1633 | public function topTenLargestFamily(int $limit = 10): string |
||
1634 | { |
||
1635 | $records = $this->topTenFamilyQuery($limit); |
||
1636 | |||
1637 | return view('statistics/families/top10-nolist', [ |
||
1638 | 'records' => $records, |
||
1639 | ]); |
||
1640 | } |
||
1641 | |||
1642 | public function topTenLargestFamilyList(int $limit = 10): string |
||
1643 | { |
||
1644 | $records = $this->topTenFamilyQuery($limit); |
||
1645 | |||
1646 | return view('statistics/families/top10-list', [ |
||
1647 | 'records' => $records, |
||
1648 | ]); |
||
1649 | } |
||
1650 | |||
1651 | public function parentsQuery(string $type, string $age_dir, string $sex, bool $show_years): string |
||
1652 | { |
||
1653 | if ($sex === 'F') { |
||
1654 | $sex_field = 'WIFE'; |
||
1655 | } else { |
||
1656 | $sex_field = 'HUSB'; |
||
1657 | } |
||
1658 | |||
1659 | if ($age_dir !== 'ASC') { |
||
1660 | $age_dir = 'DESC'; |
||
1661 | } |
||
1662 | |||
1663 | $row = DB::table('link AS parentfamily') |
||
1664 | ->join('link AS childfamily', static function (JoinClause $join): void { |
||
1665 | $join |
||
1666 | ->on('childfamily.l_file', '=', 'parentfamily.l_file') |
||
1667 | ->on('childfamily.l_from', '=', 'parentfamily.l_from') |
||
1668 | ->where('childfamily.l_type', '=', 'CHIL'); |
||
1669 | }) |
||
1670 | ->join('dates AS birth', static function (JoinClause $join): void { |
||
1671 | $join |
||
1672 | ->on('birth.d_file', '=', 'parentfamily.l_file') |
||
1673 | ->on('birth.d_gid', '=', 'parentfamily.l_to') |
||
1674 | ->where('birth.d_fact', '=', 'BIRT') |
||
1675 | ->where('birth.d_julianday1', '<>', 0); |
||
1676 | }) |
||
1677 | ->join('dates AS childbirth', static function (JoinClause $join): void { |
||
1678 | $join |
||
1679 | ->on('childbirth.d_file', '=', 'parentfamily.l_file') |
||
1680 | ->on('childbirth.d_gid', '=', 'childfamily.l_to') |
||
1681 | ->where('childbirth.d_fact', '=', 'BIRT'); |
||
1682 | }) |
||
1683 | ->where('childfamily.l_file', '=', $this->tree->id()) |
||
1684 | ->where('parentfamily.l_type', '=', $sex_field) |
||
1685 | ->where('childbirth.d_julianday2', '>', new Expression(DB::prefix('birth.d_julianday1'))) |
||
1686 | ->select(['parentfamily.l_to AS id', new Expression(DB::prefix('childbirth.d_julianday2') . ' - ' . DB::prefix('birth.d_julianday1') . ' AS age')]) |
||
1687 | ->take(1) |
||
1688 | ->orderBy('age', $age_dir) |
||
1689 | ->get() |
||
1690 | ->first(); |
||
1691 | |||
1692 | if ($row === null) { |
||
1693 | return I18N::translate('This information is not available.'); |
||
1694 | } |
||
1695 | |||
1696 | $person = Registry::individualFactory()->make($row->id, $this->tree); |
||
1697 | |||
1698 | switch ($type) { |
||
1699 | default: |
||
1700 | case 'full': |
||
1701 | if ($person !== null && $person->canShow()) { |
||
1702 | $result = $person->formatList(); |
||
1703 | } else { |
||
1704 | $result = I18N::translate('This information is private and cannot be shown.'); |
||
1705 | } |
||
1706 | break; |
||
1707 | |||
1708 | case 'name': |
||
1709 | $result = '<a href="' . e($person->url()) . '">' . $person->fullName() . '</a>'; |
||
1710 | break; |
||
1711 | |||
1712 | case 'age': |
||
1713 | $age = $row->age; |
||
1714 | |||
1715 | if ($show_years) { |
||
1716 | $result = $this->calculateAge((int) $row->age); |
||
1717 | } else { |
||
1718 | $result = (string) floor($age / 365.25); |
||
1719 | } |
||
1720 | |||
1721 | break; |
||
1722 | } |
||
1723 | |||
1724 | return $result; |
||
1725 | } |
||
1726 | |||
1727 | /** |
||
1728 | * General query on age at marriage. |
||
1729 | * |
||
1730 | * @param string $type |
||
1731 | * @param string $age_dir "ASC" or "DESC" |
||
1732 | * @param int $limit |
||
1733 | */ |
||
1734 | public function ageOfMarriageQuery(string $type, string $age_dir, int $limit): string |
||
1735 | { |
||
1736 | $hrows = DB::table('families') |
||
1737 | ->where('f_file', '=', $this->tree->id()) |
||
1738 | ->join('dates AS married', static function (JoinClause $join): void { |
||
1739 | $join |
||
1740 | ->on('married.d_file', '=', 'f_file') |
||
1741 | ->on('married.d_gid', '=', 'f_id') |
||
1742 | ->where('married.d_fact', '=', 'MARR') |
||
1743 | ->where('married.d_julianday1', '<>', 0); |
||
1744 | }) |
||
1745 | ->join('dates AS husbdeath', static function (JoinClause $join): void { |
||
1746 | $join |
||
1747 | ->on('husbdeath.d_gid', '=', 'f_husb') |
||
1748 | ->on('husbdeath.d_file', '=', 'f_file') |
||
1749 | ->where('husbdeath.d_fact', '=', 'DEAT'); |
||
1750 | }) |
||
1751 | ->whereColumn('married.d_julianday1', '<', 'husbdeath.d_julianday2') |
||
1752 | ->groupBy(['f_id']) |
||
1753 | ->select(['f_id AS family', new Expression('MIN(' . DB::prefix('husbdeath.d_julianday2') . ' - ' . DB::prefix('married.d_julianday1') . ') AS age')]) |
||
1754 | ->get() |
||
1755 | ->all(); |
||
1756 | |||
1757 | $wrows = DB::table('families') |
||
1758 | ->where('f_file', '=', $this->tree->id()) |
||
1759 | ->join('dates AS married', static function (JoinClause $join): void { |
||
1760 | $join |
||
1761 | ->on('married.d_file', '=', 'f_file') |
||
1762 | ->on('married.d_gid', '=', 'f_id') |
||
1763 | ->where('married.d_fact', '=', 'MARR') |
||
1764 | ->where('married.d_julianday1', '<>', 0); |
||
1765 | }) |
||
1766 | ->join('dates AS wifedeath', static function (JoinClause $join): void { |
||
1767 | $join |
||
1768 | ->on('wifedeath.d_gid', '=', 'f_wife') |
||
1769 | ->on('wifedeath.d_file', '=', 'f_file') |
||
1770 | ->where('wifedeath.d_fact', '=', 'DEAT'); |
||
1771 | }) |
||
1772 | ->whereColumn('married.d_julianday1', '<', 'wifedeath.d_julianday2') |
||
1773 | ->groupBy(['f_id']) |
||
1774 | ->select(['f_id AS family', new Expression('MIN(' . DB::prefix('wifedeath.d_julianday2') . ' - ' . DB::prefix('married.d_julianday1') . ') AS age')]) |
||
1775 | ->get() |
||
1776 | ->all(); |
||
1777 | |||
1778 | $drows = DB::table('families') |
||
1779 | ->where('f_file', '=', $this->tree->id()) |
||
1780 | ->join('dates AS married', static function (JoinClause $join): void { |
||
1781 | $join |
||
1782 | ->on('married.d_file', '=', 'f_file') |
||
1783 | ->on('married.d_gid', '=', 'f_id') |
||
1784 | ->where('married.d_fact', '=', 'MARR') |
||
1785 | ->where('married.d_julianday1', '<>', 0); |
||
1786 | }) |
||
1787 | ->join('dates AS divorced', static function (JoinClause $join): void { |
||
1788 | $join |
||
1789 | ->on('divorced.d_gid', '=', 'f_id') |
||
1790 | ->on('divorced.d_file', '=', 'f_file') |
||
1791 | ->whereIn('divorced.d_fact', ['DIV', 'ANUL', '_SEPR']); |
||
1792 | }) |
||
1793 | ->whereColumn('married.d_julianday1', '<', 'divorced.d_julianday2') |
||
1794 | ->groupBy(['f_id']) |
||
1795 | ->select(['f_id AS family', new Expression('MIN(' . DB::prefix('divorced.d_julianday2') . ' - ' . DB::prefix('married.d_julianday1') . ') AS age')]) |
||
1796 | ->get() |
||
1797 | ->all(); |
||
1798 | |||
1799 | $rows = []; |
||
1800 | foreach ($drows as $family) { |
||
1801 | $rows[$family->family] = $family->age; |
||
1802 | } |
||
1803 | |||
1804 | foreach ($hrows as $family) { |
||
1805 | if (!isset($rows[$family->family])) { |
||
1806 | $rows[$family->family] = $family->age; |
||
1807 | } |
||
1808 | } |
||
1809 | |||
1810 | foreach ($wrows as $family) { |
||
1811 | if (!isset($rows[$family->family])) { |
||
1812 | $rows[$family->family] = $family->age; |
||
1813 | } elseif ($rows[$family->family] > $family->age) { |
||
1814 | $rows[$family->family] = $family->age; |
||
1815 | } |
||
1816 | } |
||
1817 | |||
1818 | if ($age_dir === 'DESC') { |
||
1819 | arsort($rows); |
||
1820 | } else { |
||
1821 | asort($rows); |
||
1822 | } |
||
1823 | |||
1824 | $top10 = []; |
||
1825 | $i = 0; |
||
1826 | foreach ($rows as $xref => $age) { |
||
1827 | $family = Registry::familyFactory()->make((string) $xref, $this->tree); |
||
1828 | if ($type === 'name') { |
||
1829 | return $family->formatList(); |
||
1830 | } |
||
1831 | |||
1832 | $age = $this->calculateAge((int) $age); |
||
1833 | |||
1834 | if ($type === 'age') { |
||
1835 | return $age; |
||
1836 | } |
||
1837 | |||
1838 | $husb = $family->husband(); |
||
1839 | $wife = $family->wife(); |
||
1840 | |||
1841 | if ( |
||
1842 | $husb instanceof Individual && |
||
1843 | $wife instanceof Individual && |
||
1844 | ($husb->getAllDeathDates() || !$husb->isDead()) && |
||
1845 | ($wife->getAllDeathDates() || !$wife->isDead()) |
||
1846 | ) { |
||
1847 | if ($family->canShow()) { |
||
1848 | if ($type === 'list') { |
||
1849 | $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->fullName() . '</a> (' . $age . ')' . '</li>'; |
||
1850 | } else { |
||
1851 | $top10[] = '<a href="' . e($family->url()) . '">' . $family->fullName() . '</a> (' . $age . ')'; |
||
1852 | } |
||
1853 | } |
||
1854 | if (++$i === $limit) { |
||
1855 | break; |
||
1856 | } |
||
1857 | } |
||
1858 | } |
||
1859 | |||
1860 | if ($type === 'list') { |
||
1861 | $top10 = implode('', $top10); |
||
1862 | } else { |
||
1863 | $top10 = implode('; ', $top10); |
||
1864 | } |
||
1865 | |||
1866 | if (I18N::direction() === 'rtl') { |
||
1867 | $top10 = str_replace([ |
||
1868 | '[', |
||
1869 | ']', |
||
1870 | '(', |
||
1871 | ')', |
||
1872 | '+', |
||
1873 | ], [ |
||
1874 | '‏[', |
||
1875 | '‏]', |
||
1876 | '‏(', |
||
1877 | '‏)', |
||
1878 | '‏+', |
||
1879 | ], $top10); |
||
1880 | } |
||
1881 | |||
1882 | if ($type === 'list') { |
||
1883 | return '<ul>' . $top10 . '</ul>'; |
||
1884 | } |
||
1885 | |||
1886 | return $top10; |
||
1887 | } |
||
1888 | |||
1889 | /** |
||
1890 | * @return array<array{family:Family,age:string}> |
||
1891 | */ |
||
1892 | private function ageBetweenSpousesQuery(string $age_dir, int $limit): array |
||
1893 | { |
||
1894 | $query = DB::table('families') |
||
1895 | ->where('f_file', '=', $this->tree->id()) |
||
1896 | ->join('dates AS wife', static function (JoinClause $join): void { |
||
1897 | $join |
||
1898 | ->on('wife.d_gid', '=', 'f_wife') |
||
1899 | ->on('wife.d_file', '=', 'f_file') |
||
1900 | ->where('wife.d_fact', '=', 'BIRT') |
||
1901 | ->where('wife.d_julianday1', '<>', 0); |
||
1902 | }) |
||
1903 | ->join('dates AS husb', static function (JoinClause $join): void { |
||
1904 | $join |
||
1905 | ->on('husb.d_gid', '=', 'f_husb') |
||
1906 | ->on('husb.d_file', '=', 'f_file') |
||
1907 | ->where('husb.d_fact', '=', 'BIRT') |
||
1908 | ->where('husb.d_julianday1', '<>', 0); |
||
1909 | }); |
||
1910 | |||
1911 | if ($age_dir === 'DESC') { |
||
1912 | $query |
||
1913 | ->whereColumn('wife.d_julianday1', '>=', 'husb.d_julianday1') |
||
1914 | ->orderBy(new Expression('MIN(' . DB::prefix('wife.d_julianday1') . ') - MIN(' . DB::prefix('husb.d_julianday1') . ')'), 'DESC'); |
||
1915 | } else { |
||
1916 | $query |
||
1917 | ->whereColumn('husb.d_julianday1', '>=', 'wife.d_julianday1') |
||
1918 | ->orderBy(new Expression('MIN(' . DB::prefix('husb.d_julianday1') . ') - MIN(' . DB::prefix('wife.d_julianday1') . ')'), 'DESC'); |
||
1919 | } |
||
1920 | |||
1921 | return $query |
||
1922 | ->groupBy(['f_id', 'f_file']) |
||
1923 | ->select(['families.*']) |
||
1924 | ->take($limit) |
||
1925 | ->get() |
||
1926 | ->map(Registry::familyFactory()->mapper($this->tree)) |
||
1927 | ->filter(GedcomRecord::accessFilter()) |
||
1928 | ->map(function (Family $family) use ($age_dir): array { |
||
1929 | $husb_birt_jd = $family->husband()->getBirthDate()->minimumJulianDay(); |
||
1930 | $wife_birt_jd = $family->wife()->getBirthDate()->minimumJulianDay(); |
||
1931 | |||
1932 | if ($age_dir === 'DESC') { |
||
1933 | $diff = $wife_birt_jd - $husb_birt_jd; |
||
1934 | } else { |
||
1935 | $diff = $husb_birt_jd - $wife_birt_jd; |
||
1936 | } |
||
1937 | |||
1938 | return [ |
||
1939 | 'family' => $family, |
||
1940 | 'age' => $this->calculateAge($diff), |
||
1941 | ]; |
||
1942 | }) |
||
1943 | ->all(); |
||
1944 | } |
||
1945 | |||
1946 | public function ageBetweenSpousesMF(int $limit = 10): string |
||
1947 | { |
||
1948 | $records = $this->ageBetweenSpousesQuery('DESC', $limit); |
||
1949 | |||
1950 | return view('statistics/families/top10-nolist-spouses', [ |
||
1951 | 'records' => $records, |
||
1952 | ]); |
||
1953 | } |
||
1954 | |||
1955 | public function ageBetweenSpousesMFList(int $limit = 10): string |
||
1956 | { |
||
1957 | $records = $this->ageBetweenSpousesQuery('DESC', $limit); |
||
1958 | |||
1959 | return view('statistics/families/top10-list-spouses', [ |
||
1960 | 'records' => $records, |
||
1961 | ]); |
||
1962 | } |
||
1963 | |||
1964 | public function ageBetweenSpousesFM(int $limit = 10): string |
||
1965 | { |
||
1966 | return view('statistics/families/top10-nolist-spouses', [ |
||
1967 | 'records' => $this->ageBetweenSpousesQuery('ASC', $limit), |
||
1968 | ]); |
||
1969 | } |
||
1970 | |||
1971 | public function ageBetweenSpousesFMList(int $limit = 10): string |
||
1972 | { |
||
1973 | return view('statistics/families/top10-list-spouses', [ |
||
1974 | 'records' => $this->ageBetweenSpousesQuery('ASC', $limit), |
||
1975 | ]); |
||
1976 | } |
||
1977 | |||
1978 | /** |
||
1979 | * @return array<object{f_id:string,d_gid:string,age:int}> |
||
0 ignored issues
–
show
|
|||
1980 | */ |
||
1981 | public function statsMarrAgeQuery(string $sex, int $year1, int $year2): array |
||
1982 | { |
||
1983 | $query = DB::table('dates AS married') |
||
1984 | ->join('families', static function (JoinClause $join): void { |
||
1985 | $join |
||
1986 | ->on('f_file', '=', 'married.d_file') |
||
1987 | ->on('f_id', '=', 'married.d_gid'); |
||
1988 | }) |
||
1989 | ->join('dates AS birth', static function (JoinClause $join) use ($sex): void { |
||
1990 | $join |
||
1991 | ->on('birth.d_file', '=', 'married.d_file') |
||
1992 | ->on('birth.d_gid', '=', $sex === 'M' ? 'f_husb' : 'f_wife') |
||
1993 | ->where('birth.d_julianday1', '<>', 0) |
||
1994 | ->where('birth.d_fact', '=', 'BIRT') |
||
1995 | ->whereIn('birth.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']); |
||
1996 | }) |
||
1997 | ->where('married.d_file', '=', $this->tree->id()) |
||
1998 | ->where('married.d_fact', '=', 'MARR') |
||
1999 | ->whereIn('married.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']) |
||
2000 | ->whereColumn('married.d_julianday1', '>', 'birth.d_julianday1') |
||
2001 | ->select(['f_id', 'birth.d_gid', new Expression(DB::prefix('married.d_julianday2') . ' - ' . DB::prefix('birth.d_julianday1') . ' AS age')]); |
||
2002 | |||
2003 | if ($year1 !== 0 && $year2 !== 0) { |
||
2004 | $query->whereBetween('married.d_year', [$year1, $year2]); |
||
2005 | } |
||
2006 | |||
2007 | return $query |
||
2008 | ->get() |
||
2009 | ->map(static fn (object $row): object => (object) [ |
||
2010 | 'f_id' => $row->f_id, |
||
2011 | 'd_gid' => $row->d_gid, |
||
2012 | 'age' => (int) $row->age, |
||
2013 | ]) |
||
2014 | ->all(); |
||
2015 | } |
||
2016 | |||
2017 | /** |
||
2018 | * Query the database for marriage tags. |
||
2019 | * |
||
2020 | * @param string $show "full", "name" or "age" |
||
2021 | * @param string $age_dir "ASC" or "DESC" |
||
2022 | * @param string $sex "F" or "M" |
||
2023 | * @param bool $show_years |
||
2024 | */ |
||
2025 | public function marriageQuery(string $show, string $age_dir, string $sex, bool $show_years): string |
||
2026 | { |
||
2027 | if ($sex === 'F') { |
||
2028 | $sex_field = 'f_wife'; |
||
2029 | } else { |
||
2030 | $sex_field = 'f_husb'; |
||
2031 | } |
||
2032 | |||
2033 | if ($age_dir !== 'ASC') { |
||
2034 | $age_dir = 'DESC'; |
||
2035 | } |
||
2036 | |||
2037 | $row = DB::table('families') |
||
2038 | ->join('dates AS married', static function (JoinClause $join): void { |
||
2039 | $join |
||
2040 | ->on('married.d_file', '=', 'f_file') |
||
2041 | ->on('married.d_gid', '=', 'f_id') |
||
2042 | ->where('married.d_fact', '=', 'MARR'); |
||
2043 | }) |
||
2044 | ->join('individuals', static function (JoinClause $join) use ($sex, $sex_field): void { |
||
2045 | $join |
||
2046 | ->on('i_file', '=', 'f_file') |
||
2047 | ->on('i_id', '=', $sex_field) |
||
2048 | ->where('i_sex', '=', $sex); |
||
2049 | }) |
||
2050 | ->join('dates AS birth', static function (JoinClause $join): void { |
||
2051 | $join |
||
2052 | ->on('birth.d_file', '=', 'i_file') |
||
2053 | ->on('birth.d_gid', '=', 'i_id') |
||
2054 | ->where('birth.d_fact', '=', 'BIRT') |
||
2055 | ->where('birth.d_julianday1', '<>', 0); |
||
2056 | }) |
||
2057 | ->where('f_file', '=', $this->tree->id()) |
||
2058 | ->where('married.d_julianday2', '>', new Expression(DB::prefix('birth.d_julianday1'))) |
||
2059 | ->orderBy(new Expression(DB::prefix('married.d_julianday2') . ' - ' . DB::prefix('birth.d_julianday1')), $age_dir) |
||
2060 | ->select(['f_id AS famid', $sex_field, new Expression(DB::prefix('married.d_julianday2') . ' - ' . DB::prefix('birth.d_julianday1') . ' AS age'), 'i_id']) |
||
2061 | ->take(1) |
||
2062 | ->get() |
||
2063 | ->first(); |
||
2064 | |||
2065 | if ($row === null) { |
||
2066 | return I18N::translate('This information is not available.'); |
||
2067 | } |
||
2068 | |||
2069 | $family = Registry::familyFactory()->make($row->famid, $this->tree); |
||
2070 | $person = Registry::individualFactory()->make($row->i_id, $this->tree); |
||
2071 | |||
2072 | switch ($show) { |
||
2073 | default: |
||
2074 | case 'full': |
||
2075 | if ($family !== null && $family->canShow()) { |
||
2076 | $result = $family->formatList(); |
||
2077 | } else { |
||
2078 | $result = I18N::translate('This information is private and cannot be shown.'); |
||
2079 | } |
||
2080 | break; |
||
2081 | |||
2082 | case 'name': |
||
2083 | $result = '<a href="' . e($family->url()) . '">' . $person->fullName() . '</a>'; |
||
2084 | break; |
||
2085 | |||
2086 | case 'age': |
||
2087 | $age = $row->age; |
||
2088 | |||
2089 | if ($show_years) { |
||
2090 | $result = $this->calculateAge((int) $row->age); |
||
2091 | } else { |
||
2092 | $result = I18N::number((int) ($age / 365.25)); |
||
2093 | } |
||
2094 | |||
2095 | break; |
||
2096 | } |
||
2097 | |||
2098 | return $result; |
||
2099 | } |
||
2100 | |||
2101 | /** |
||
2102 | * Who is currently logged in? |
||
2103 | * |
||
2104 | * @param string $type "list" or "nolist" |
||
2105 | */ |
||
2106 | private function usersLoggedInQuery(string $type): string |
||
2107 | { |
||
2108 | $content = ''; |
||
2109 | $anonymous = 0; |
||
2110 | $logged_in = []; |
||
2111 | |||
2112 | foreach ($this->user_service->allLoggedIn() as $user) { |
||
2113 | if (Auth::isAdmin() || $user->getPreference(UserInterface::PREF_IS_VISIBLE_ONLINE) === '1') { |
||
2114 | $logged_in[] = $user; |
||
2115 | } else { |
||
2116 | $anonymous++; |
||
2117 | } |
||
2118 | } |
||
2119 | |||
2120 | $count_logged_in = count($logged_in); |
||
2121 | |||
2122 | if ($count_logged_in === 0 && $anonymous === 0) { |
||
2123 | $content .= I18N::translate('No signed-in and no anonymous users'); |
||
2124 | } |
||
2125 | |||
2126 | if ($anonymous > 0) { |
||
2127 | $content .= '<b>' . I18N::plural('%s anonymous signed-in user', '%s anonymous signed-in users', $anonymous, I18N::number($anonymous)) . '</b>'; |
||
2128 | } |
||
2129 | |||
2130 | if ($count_logged_in > 0) { |
||
2131 | if ($anonymous !== 0) { |
||
2132 | if ($type === 'list') { |
||
2133 | $content .= '<br><br>'; |
||
2134 | } else { |
||
2135 | $content .= ' ' . I18N::translate('and') . ' '; |
||
2136 | } |
||
2137 | } |
||
2138 | $content .= '<b>' . I18N::plural('%s signed-in user', '%s signed-in users', $count_logged_in, I18N::number($count_logged_in)) . '</b>'; |
||
2139 | if ($type === 'list') { |
||
2140 | $content .= '<ul>'; |
||
2141 | } else { |
||
2142 | $content .= ': '; |
||
2143 | } |
||
2144 | } |
||
2145 | |||
2146 | if (Auth::check()) { |
||
2147 | foreach ($logged_in as $user) { |
||
2148 | if ($type === 'list') { |
||
2149 | $content .= '<li>'; |
||
2150 | } |
||
2151 | |||
2152 | $individual = Registry::individualFactory()->make($this->tree->getUserPreference($user, UserInterface::PREF_TREE_ACCOUNT_XREF), $this->tree); |
||
2153 | |||
2154 | if ($individual instanceof Individual && $individual->canShow()) { |
||
2155 | $content .= '<a href="' . e($individual->url()) . '">' . e($user->realName()) . '</a>'; |
||
2156 | } else { |
||
2157 | $content .= e($user->realName()); |
||
2158 | } |
||
2159 | |||
2160 | $content .= ' - ' . e($user->userName()); |
||
2161 | |||
2162 | if ($user->getPreference(UserInterface::PREF_CONTACT_METHOD) !== MessageService::CONTACT_METHOD_NONE && Auth::id() !== $user->id()) { |
||
2163 | $content .= '<a href="' . e(route(MessagePage::class, ['to' => $user->userName(), 'tree' => $this->tree->name()])) . '" class="btn btn-link" title="' . I18N::translate('Send a message') . '">' . view('icons/email') . '</a>'; |
||
2164 | } |
||
2165 | |||
2166 | if ($type === 'list') { |
||
2167 | $content .= '</li>'; |
||
2168 | } |
||
2169 | } |
||
2170 | } |
||
2171 | |||
2172 | if ($type === 'list') { |
||
2173 | $content .= '</ul>'; |
||
2174 | } |
||
2175 | |||
2176 | return $content; |
||
2177 | } |
||
2178 | |||
2179 | public function usersLoggedIn(): string |
||
2180 | { |
||
2181 | return $this->usersLoggedInQuery('nolist'); |
||
2182 | } |
||
2183 | |||
2184 | public function usersLoggedInList(): string |
||
2185 | { |
||
2186 | return $this->usersLoggedInQuery('list'); |
||
2187 | } |
||
2188 | |||
2189 | /** |
||
2190 | * Century name, English => 21st, Polish => XXI, etc. |
||
2191 | */ |
||
2192 | private function centuryName(int $century): string |
||
2193 | { |
||
2194 | if ($century < 0) { |
||
2195 | return I18N::translate('%s BCE', $this->centuryName(-$century)); |
||
2196 | } |
||
2197 | |||
2198 | // The current chart engine (Google charts) can't handle <sup></sup> markup |
||
2199 | return match ($century) { |
||
2200 | 21 => strip_tags(I18N::translateContext('CENTURY', '21st')), |
||
2201 | 20 => strip_tags(I18N::translateContext('CENTURY', '20th')), |
||
2202 | 19 => strip_tags(I18N::translateContext('CENTURY', '19th')), |
||
2203 | 18 => strip_tags(I18N::translateContext('CENTURY', '18th')), |
||
2204 | 17 => strip_tags(I18N::translateContext('CENTURY', '17th')), |
||
2205 | 16 => strip_tags(I18N::translateContext('CENTURY', '16th')), |
||
2206 | 15 => strip_tags(I18N::translateContext('CENTURY', '15th')), |
||
2207 | 14 => strip_tags(I18N::translateContext('CENTURY', '14th')), |
||
2208 | 13 => strip_tags(I18N::translateContext('CENTURY', '13th')), |
||
2209 | 12 => strip_tags(I18N::translateContext('CENTURY', '12th')), |
||
2210 | 11 => strip_tags(I18N::translateContext('CENTURY', '11th')), |
||
2211 | 10 => strip_tags(I18N::translateContext('CENTURY', '10th')), |
||
2212 | 9 => strip_tags(I18N::translateContext('CENTURY', '9th')), |
||
2213 | 8 => strip_tags(I18N::translateContext('CENTURY', '8th')), |
||
2214 | 7 => strip_tags(I18N::translateContext('CENTURY', '7th')), |
||
2215 | 6 => strip_tags(I18N::translateContext('CENTURY', '6th')), |
||
2216 | 5 => strip_tags(I18N::translateContext('CENTURY', '5th')), |
||
2217 | 4 => strip_tags(I18N::translateContext('CENTURY', '4th')), |
||
2218 | 3 => strip_tags(I18N::translateContext('CENTURY', '3rd')), |
||
2219 | 2 => strip_tags(I18N::translateContext('CENTURY', '2nd')), |
||
2220 | 1 => strip_tags(I18N::translateContext('CENTURY', '1st')), |
||
2221 | default => ($century - 1) . '01-' . $century . '00', |
||
2222 | }; |
||
2223 | } |
||
2224 | |||
2225 | /** |
||
2226 | * @return array<string> |
||
2227 | */ |
||
2228 | private function getAllCountries(): array |
||
2229 | { |
||
2230 | return [ |
||
2231 | /* I18N: Name of a country or state */ |
||
2232 | '???' => I18N::translate('Unknown'), |
||
2233 | /* I18N: Name of a country or state */ |
||
2234 | 'ABW' => I18N::translate('Aruba'), |
||
2235 | /* I18N: Name of a country or state */ |
||
2236 | 'AFG' => I18N::translate('Afghanistan'), |
||
2237 | /* I18N: Name of a country or state */ |
||
2238 | 'AGO' => I18N::translate('Angola'), |
||
2239 | /* I18N: Name of a country or state */ |
||
2240 | 'AIA' => I18N::translate('Anguilla'), |
||
2241 | /* I18N: Name of a country or state */ |
||
2242 | 'ALA' => I18N::translate('Åland Islands'), |
||
2243 | /* I18N: Name of a country or state */ |
||
2244 | 'ALB' => I18N::translate('Albania'), |
||
2245 | /* I18N: Name of a country or state */ |
||
2246 | 'AND' => I18N::translate('Andorra'), |
||
2247 | /* I18N: Name of a country or state */ |
||
2248 | 'ARE' => I18N::translate('United Arab Emirates'), |
||
2249 | /* I18N: Name of a country or state */ |
||
2250 | 'ARG' => I18N::translate('Argentina'), |
||
2251 | /* I18N: Name of a country or state */ |
||
2252 | 'ARM' => I18N::translate('Armenia'), |
||
2253 | /* I18N: Name of a country or state */ |
||
2254 | 'ASM' => I18N::translate('American Samoa'), |
||
2255 | /* I18N: Name of a country or state */ |
||
2256 | 'ATA' => I18N::translate('Antarctica'), |
||
2257 | /* I18N: Name of a country or state */ |
||
2258 | 'ATF' => I18N::translate('French Southern Territories'), |
||
2259 | /* I18N: Name of a country or state */ |
||
2260 | 'ATG' => I18N::translate('Antigua and Barbuda'), |
||
2261 | /* I18N: Name of a country or state */ |
||
2262 | 'AUS' => I18N::translate('Australia'), |
||
2263 | /* I18N: Name of a country or state */ |
||
2264 | 'AUT' => I18N::translate('Austria'), |
||
2265 | /* I18N: Name of a country or state */ |
||
2266 | 'AZE' => I18N::translate('Azerbaijan'), |
||
2267 | /* I18N: Name of a country or state */ |
||
2268 | 'AZR' => I18N::translate('Azores'), |
||
2269 | /* I18N: Name of a country or state */ |
||
2270 | 'BDI' => I18N::translate('Burundi'), |
||
2271 | /* I18N: Name of a country or state */ |
||
2272 | 'BEL' => I18N::translate('Belgium'), |
||
2273 | /* I18N: Name of a country or state */ |
||
2274 | 'BEN' => I18N::translate('Benin'), |
||
2275 | // BES => Bonaire, Sint Eustatius and Saba |
||
2276 | /* I18N: Name of a country or state */ |
||
2277 | 'BFA' => I18N::translate('Burkina Faso'), |
||
2278 | /* I18N: Name of a country or state */ |
||
2279 | 'BGD' => I18N::translate('Bangladesh'), |
||
2280 | /* I18N: Name of a country or state */ |
||
2281 | 'BGR' => I18N::translate('Bulgaria'), |
||
2282 | /* I18N: Name of a country or state */ |
||
2283 | 'BHR' => I18N::translate('Bahrain'), |
||
2284 | /* I18N: Name of a country or state */ |
||
2285 | 'BHS' => I18N::translate('Bahamas'), |
||
2286 | /* I18N: Name of a country or state */ |
||
2287 | 'BIH' => I18N::translate('Bosnia and Herzegovina'), |
||
2288 | // BLM => Saint Barthélemy |
||
2289 | 'BLM' => I18N::translate('Saint Barthélemy'), |
||
2290 | /* I18N: Name of a country or state */ |
||
2291 | 'BLR' => I18N::translate('Belarus'), |
||
2292 | /* I18N: Name of a country or state */ |
||
2293 | 'BLZ' => I18N::translate('Belize'), |
||
2294 | /* I18N: Name of a country or state */ |
||
2295 | 'BMU' => I18N::translate('Bermuda'), |
||
2296 | /* I18N: Name of a country or state */ |
||
2297 | 'BOL' => I18N::translate('Bolivia'), |
||
2298 | /* I18N: Name of a country or state */ |
||
2299 | 'BRA' => I18N::translate('Brazil'), |
||
2300 | /* I18N: Name of a country or state */ |
||
2301 | 'BRB' => I18N::translate('Barbados'), |
||
2302 | /* I18N: Name of a country or state */ |
||
2303 | 'BRN' => I18N::translate('Brunei Darussalam'), |
||
2304 | /* I18N: Name of a country or state */ |
||
2305 | 'BTN' => I18N::translate('Bhutan'), |
||
2306 | /* I18N: Name of a country or state */ |
||
2307 | 'BVT' => I18N::translate('Bouvet Island'), |
||
2308 | /* I18N: Name of a country or state */ |
||
2309 | 'BWA' => I18N::translate('Botswana'), |
||
2310 | /* I18N: Name of a country or state */ |
||
2311 | 'CAF' => I18N::translate('Central African Republic'), |
||
2312 | /* I18N: Name of a country or state */ |
||
2313 | 'CAN' => I18N::translate('Canada'), |
||
2314 | /* I18N: Name of a country or state */ |
||
2315 | 'CCK' => I18N::translate('Cocos (Keeling) Islands'), |
||
2316 | /* I18N: Name of a country or state */ |
||
2317 | 'CHE' => I18N::translate('Switzerland'), |
||
2318 | /* I18N: Name of a country or state */ |
||
2319 | 'CHL' => I18N::translate('Chile'), |
||
2320 | /* I18N: Name of a country or state */ |
||
2321 | 'CHN' => I18N::translate('China'), |
||
2322 | /* I18N: Name of a country or state */ |
||
2323 | 'CIV' => I18N::translate('Côte d’Ivoire'), |
||
2324 | /* I18N: Name of a country or state */ |
||
2325 | 'CMR' => I18N::translate('Cameroon'), |
||
2326 | /* I18N: Name of a country or state */ |
||
2327 | 'COD' => I18N::translate('Democratic Republic of the Congo'), |
||
2328 | /* I18N: Name of a country or state */ |
||
2329 | 'COG' => I18N::translate('Republic of the Congo'), |
||
2330 | /* I18N: Name of a country or state */ |
||
2331 | 'COK' => I18N::translate('Cook Islands'), |
||
2332 | /* I18N: Name of a country or state */ |
||
2333 | 'COL' => I18N::translate('Colombia'), |
||
2334 | /* I18N: Name of a country or state */ |
||
2335 | 'COM' => I18N::translate('Comoros'), |
||
2336 | /* I18N: Name of a country or state */ |
||
2337 | 'CPV' => I18N::translate('Cape Verde'), |
||
2338 | /* I18N: Name of a country or state */ |
||
2339 | 'CRI' => I18N::translate('Costa Rica'), |
||
2340 | /* I18N: Name of a country or state */ |
||
2341 | 'CUB' => I18N::translate('Cuba'), |
||
2342 | /* I18N: Name of a country or state */ |
||
2343 | 'CUW' => I18N::translate('Curaçao'), |
||
2344 | /* I18N: Name of a country or state */ |
||
2345 | 'CXR' => I18N::translate('Christmas Island'), |
||
2346 | /* I18N: Name of a country or state */ |
||
2347 | 'CYM' => I18N::translate('Cayman Islands'), |
||
2348 | /* I18N: Name of a country or state */ |
||
2349 | 'CYP' => I18N::translate('Cyprus'), |
||
2350 | /* I18N: Name of a country or state */ |
||
2351 | 'CZE' => I18N::translate('Czech Republic'), |
||
2352 | /* I18N: Name of a country or state */ |
||
2353 | 'DEU' => I18N::translate('Germany'), |
||
2354 | /* I18N: Name of a country or state */ |
||
2355 | 'DJI' => I18N::translate('Djibouti'), |
||
2356 | /* I18N: Name of a country or state */ |
||
2357 | 'DMA' => I18N::translate('Dominica'), |
||
2358 | /* I18N: Name of a country or state */ |
||
2359 | 'DNK' => I18N::translate('Denmark'), |
||
2360 | /* I18N: Name of a country or state */ |
||
2361 | 'DOM' => I18N::translate('Dominican Republic'), |
||
2362 | /* I18N: Name of a country or state */ |
||
2363 | 'DZA' => I18N::translate('Algeria'), |
||
2364 | /* I18N: Name of a country or state */ |
||
2365 | 'ECU' => I18N::translate('Ecuador'), |
||
2366 | /* I18N: Name of a country or state */ |
||
2367 | 'EGY' => I18N::translate('Egypt'), |
||
2368 | /* I18N: Name of a country or state */ |
||
2369 | 'ENG' => I18N::translate('England'), |
||
2370 | /* I18N: Name of a country or state */ |
||
2371 | 'ERI' => I18N::translate('Eritrea'), |
||
2372 | /* I18N: Name of a country or state */ |
||
2373 | 'ESH' => I18N::translate('Western Sahara'), |
||
2374 | /* I18N: Name of a country or state */ |
||
2375 | 'ESP' => I18N::translate('Spain'), |
||
2376 | /* I18N: Name of a country or state */ |
||
2377 | 'EST' => I18N::translate('Estonia'), |
||
2378 | /* I18N: Name of a country or state */ |
||
2379 | 'ETH' => I18N::translate('Ethiopia'), |
||
2380 | /* I18N: Name of a country or state */ |
||
2381 | 'FIN' => I18N::translate('Finland'), |
||
2382 | /* I18N: Name of a country or state */ |
||
2383 | 'FJI' => I18N::translate('Fiji'), |
||
2384 | /* I18N: Name of a country or state */ |
||
2385 | 'FLD' => I18N::translate('Flanders'), |
||
2386 | /* I18N: Name of a country or state */ |
||
2387 | 'FLK' => I18N::translate('Falkland Islands'), |
||
2388 | /* I18N: Name of a country or state */ |
||
2389 | 'FRA' => I18N::translate('France'), |
||
2390 | /* I18N: Name of a country or state */ |
||
2391 | 'FRO' => I18N::translate('Faroe Islands'), |
||
2392 | /* I18N: Name of a country or state */ |
||
2393 | 'FSM' => I18N::translate('Micronesia'), |
||
2394 | /* I18N: Name of a country or state */ |
||
2395 | 'GAB' => I18N::translate('Gabon'), |
||
2396 | /* I18N: Name of a country or state */ |
||
2397 | 'GBR' => I18N::translate('United Kingdom'), |
||
2398 | /* I18N: Name of a country or state */ |
||
2399 | 'GEO' => I18N::translate('Georgia'), |
||
2400 | /* I18N: Name of a country or state */ |
||
2401 | 'GGY' => I18N::translate('Guernsey'), |
||
2402 | /* I18N: Name of a country or state */ |
||
2403 | 'GHA' => I18N::translate('Ghana'), |
||
2404 | /* I18N: Name of a country or state */ |
||
2405 | 'GIB' => I18N::translate('Gibraltar'), |
||
2406 | /* I18N: Name of a country or state */ |
||
2407 | 'GIN' => I18N::translate('Guinea'), |
||
2408 | /* I18N: Name of a country or state */ |
||
2409 | 'GLP' => I18N::translate('Guadeloupe'), |
||
2410 | /* I18N: Name of a country or state */ |
||
2411 | 'GMB' => I18N::translate('Gambia'), |
||
2412 | /* I18N: Name of a country or state */ |
||
2413 | 'GNB' => I18N::translate('Guinea-Bissau'), |
||
2414 | /* I18N: Name of a country or state */ |
||
2415 | 'GNQ' => I18N::translate('Equatorial Guinea'), |
||
2416 | /* I18N: Name of a country or state */ |
||
2417 | 'GRC' => I18N::translate('Greece'), |
||
2418 | /* I18N: Name of a country or state */ |
||
2419 | 'GRD' => I18N::translate('Grenada'), |
||
2420 | /* I18N: Name of a country or state */ |
||
2421 | 'GRL' => I18N::translate('Greenland'), |
||
2422 | /* I18N: Name of a country or state */ |
||
2423 | 'GTM' => I18N::translate('Guatemala'), |
||
2424 | /* I18N: Name of a country or state */ |
||
2425 | 'GUF' => I18N::translate('French Guiana'), |
||
2426 | /* I18N: Name of a country or state */ |
||
2427 | 'GUM' => I18N::translate('Guam'), |
||
2428 | /* I18N: Name of a country or state */ |
||
2429 | 'GUY' => I18N::translate('Guyana'), |
||
2430 | /* I18N: Name of a country or state */ |
||
2431 | 'HKG' => I18N::translate('Hong Kong'), |
||
2432 | /* I18N: Name of a country or state */ |
||
2433 | 'HMD' => I18N::translate('Heard Island and McDonald Islands'), |
||
2434 | /* I18N: Name of a country or state */ |
||
2435 | 'HND' => I18N::translate('Honduras'), |
||
2436 | /* I18N: Name of a country or state */ |
||
2437 | 'HRV' => I18N::translate('Croatia'), |
||
2438 | /* I18N: Name of a country or state */ |
||
2439 | 'HTI' => I18N::translate('Haiti'), |
||
2440 | /* I18N: Name of a country or state */ |
||
2441 | 'HUN' => I18N::translate('Hungary'), |
||
2442 | /* I18N: Name of a country or state */ |
||
2443 | 'IDN' => I18N::translate('Indonesia'), |
||
2444 | /* I18N: Name of a country or state */ |
||
2445 | 'IND' => I18N::translate('India'), |
||
2446 | /* I18N: Name of a country or state */ |
||
2447 | 'IOM' => I18N::translate('Isle of Man'), |
||
2448 | /* I18N: Name of a country or state */ |
||
2449 | 'IOT' => I18N::translate('British Indian Ocean Territory'), |
||
2450 | /* I18N: Name of a country or state */ |
||
2451 | 'IRL' => I18N::translate('Ireland'), |
||
2452 | /* I18N: Name of a country or state */ |
||
2453 | 'IRN' => I18N::translate('Iran'), |
||
2454 | /* I18N: Name of a country or state */ |
||
2455 | 'IRQ' => I18N::translate('Iraq'), |
||
2456 | /* I18N: Name of a country or state */ |
||
2457 | 'ISL' => I18N::translate('Iceland'), |
||
2458 | /* I18N: Name of a country or state */ |
||
2459 | 'ISR' => I18N::translate('Israel'), |
||
2460 | /* I18N: Name of a country or state */ |
||
2461 | 'ITA' => I18N::translate('Italy'), |
||
2462 | /* I18N: Name of a country or state */ |
||
2463 | 'JAM' => I18N::translate('Jamaica'), |
||
2464 | //'JEY' => Jersey |
||
2465 | /* I18N: Name of a country or state */ |
||
2466 | 'JOR' => I18N::translate('Jordan'), |
||
2467 | /* I18N: Name of a country or state */ |
||
2468 | 'JPN' => I18N::translate('Japan'), |
||
2469 | /* I18N: Name of a country or state */ |
||
2470 | 'KAZ' => I18N::translate('Kazakhstan'), |
||
2471 | /* I18N: Name of a country or state */ |
||
2472 | 'KEN' => I18N::translate('Kenya'), |
||
2473 | /* I18N: Name of a country or state */ |
||
2474 | 'KGZ' => I18N::translate('Kyrgyzstan'), |
||
2475 | /* I18N: Name of a country or state */ |
||
2476 | 'KHM' => I18N::translate('Cambodia'), |
||
2477 | /* I18N: Name of a country or state */ |
||
2478 | 'KIR' => I18N::translate('Kiribati'), |
||
2479 | /* I18N: Name of a country or state */ |
||
2480 | 'KNA' => I18N::translate('Saint Kitts and Nevis'), |
||
2481 | /* I18N: Name of a country or state */ |
||
2482 | 'KOR' => I18N::translate('Korea'), |
||
2483 | /* I18N: Name of a country or state */ |
||
2484 | 'KWT' => I18N::translate('Kuwait'), |
||
2485 | /* I18N: Name of a country or state */ |
||
2486 | 'LAO' => I18N::translate('Laos'), |
||
2487 | /* I18N: Name of a country or state */ |
||
2488 | 'LBN' => I18N::translate('Lebanon'), |
||
2489 | /* I18N: Name of a country or state */ |
||
2490 | 'LBR' => I18N::translate('Liberia'), |
||
2491 | /* I18N: Name of a country or state */ |
||
2492 | 'LBY' => I18N::translate('Libya'), |
||
2493 | /* I18N: Name of a country or state */ |
||
2494 | 'LCA' => I18N::translate('Saint Lucia'), |
||
2495 | /* I18N: Name of a country or state */ |
||
2496 | 'LIE' => I18N::translate('Liechtenstein'), |
||
2497 | /* I18N: Name of a country or state */ |
||
2498 | 'LKA' => I18N::translate('Sri Lanka'), |
||
2499 | /* I18N: Name of a country or state */ |
||
2500 | 'LSO' => I18N::translate('Lesotho'), |
||
2501 | /* I18N: Name of a country or state */ |
||
2502 | 'LTU' => I18N::translate('Lithuania'), |
||
2503 | /* I18N: Name of a country or state */ |
||
2504 | 'LUX' => I18N::translate('Luxembourg'), |
||
2505 | /* I18N: Name of a country or state */ |
||
2506 | 'LVA' => I18N::translate('Latvia'), |
||
2507 | /* I18N: Name of a country or state */ |
||
2508 | 'MAC' => I18N::translate('Macau'), |
||
2509 | // MAF => Saint Martin |
||
2510 | /* I18N: Name of a country or state */ |
||
2511 | 'MAR' => I18N::translate('Morocco'), |
||
2512 | /* I18N: Name of a country or state */ |
||
2513 | 'MCO' => I18N::translate('Monaco'), |
||
2514 | /* I18N: Name of a country or state */ |
||
2515 | 'MDA' => I18N::translate('Moldova'), |
||
2516 | /* I18N: Name of a country or state */ |
||
2517 | 'MDG' => I18N::translate('Madagascar'), |
||
2518 | /* I18N: Name of a country or state */ |
||
2519 | 'MDV' => I18N::translate('Maldives'), |
||
2520 | /* I18N: Name of a country or state */ |
||
2521 | 'MEX' => I18N::translate('Mexico'), |
||
2522 | /* I18N: Name of a country or state */ |
||
2523 | 'MHL' => I18N::translate('Marshall Islands'), |
||
2524 | /* I18N: Name of a country or state */ |
||
2525 | 'MKD' => I18N::translate('Macedonia'), |
||
2526 | /* I18N: Name of a country or state */ |
||
2527 | 'MLI' => I18N::translate('Mali'), |
||
2528 | /* I18N: Name of a country or state */ |
||
2529 | 'MLT' => I18N::translate('Malta'), |
||
2530 | /* I18N: Name of a country or state */ |
||
2531 | 'MMR' => I18N::translate('Myanmar'), |
||
2532 | /* I18N: Name of a country or state */ |
||
2533 | 'MNG' => I18N::translate('Mongolia'), |
||
2534 | /* I18N: Name of a country or state */ |
||
2535 | 'MNP' => I18N::translate('Northern Mariana Islands'), |
||
2536 | /* I18N: Name of a country or state */ |
||
2537 | 'MNT' => I18N::translate('Montenegro'), |
||
2538 | /* I18N: Name of a country or state */ |
||
2539 | 'MOZ' => I18N::translate('Mozambique'), |
||
2540 | /* I18N: Name of a country or state */ |
||
2541 | 'MRT' => I18N::translate('Mauritania'), |
||
2542 | /* I18N: Name of a country or state */ |
||
2543 | 'MSR' => I18N::translate('Montserrat'), |
||
2544 | /* I18N: Name of a country or state */ |
||
2545 | 'MTQ' => I18N::translate('Martinique'), |
||
2546 | /* I18N: Name of a country or state */ |
||
2547 | 'MUS' => I18N::translate('Mauritius'), |
||
2548 | /* I18N: Name of a country or state */ |
||
2549 | 'MWI' => I18N::translate('Malawi'), |
||
2550 | /* I18N: Name of a country or state */ |
||
2551 | 'MYS' => I18N::translate('Malaysia'), |
||
2552 | /* I18N: Name of a country or state */ |
||
2553 | 'MYT' => I18N::translate('Mayotte'), |
||
2554 | /* I18N: Name of a country or state */ |
||
2555 | 'NAM' => I18N::translate('Namibia'), |
||
2556 | /* I18N: Name of a country or state */ |
||
2557 | 'NCL' => I18N::translate('New Caledonia'), |
||
2558 | /* I18N: Name of a country or state */ |
||
2559 | 'NER' => I18N::translate('Niger'), |
||
2560 | /* I18N: Name of a country or state */ |
||
2561 | 'NFK' => I18N::translate('Norfolk Island'), |
||
2562 | /* I18N: Name of a country or state */ |
||
2563 | 'NGA' => I18N::translate('Nigeria'), |
||
2564 | /* I18N: Name of a country or state */ |
||
2565 | 'NIC' => I18N::translate('Nicaragua'), |
||
2566 | /* I18N: Name of a country or state */ |
||
2567 | 'NIR' => I18N::translate('Northern Ireland'), |
||
2568 | /* I18N: Name of a country or state */ |
||
2569 | 'NIU' => I18N::translate('Niue'), |
||
2570 | /* I18N: Name of a country or state */ |
||
2571 | 'NLD' => I18N::translate('Netherlands'), |
||
2572 | /* I18N: Name of a country or state */ |
||
2573 | 'NOR' => I18N::translate('Norway'), |
||
2574 | /* I18N: Name of a country or state */ |
||
2575 | 'NPL' => I18N::translate('Nepal'), |
||
2576 | /* I18N: Name of a country or state */ |
||
2577 | 'NRU' => I18N::translate('Nauru'), |
||
2578 | /* I18N: Name of a country or state */ |
||
2579 | 'NZL' => I18N::translate('New Zealand'), |
||
2580 | /* I18N: Name of a country or state */ |
||
2581 | 'OMN' => I18N::translate('Oman'), |
||
2582 | /* I18N: Name of a country or state */ |
||
2583 | 'PAK' => I18N::translate('Pakistan'), |
||
2584 | /* I18N: Name of a country or state */ |
||
2585 | 'PAN' => I18N::translate('Panama'), |
||
2586 | /* I18N: Name of a country or state */ |
||
2587 | 'PCN' => I18N::translate('Pitcairn'), |
||
2588 | /* I18N: Name of a country or state */ |
||
2589 | 'PER' => I18N::translate('Peru'), |
||
2590 | /* I18N: Name of a country or state */ |
||
2591 | 'PHL' => I18N::translate('Philippines'), |
||
2592 | /* I18N: Name of a country or state */ |
||
2593 | 'PLW' => I18N::translate('Palau'), |
||
2594 | /* I18N: Name of a country or state */ |
||
2595 | 'PNG' => I18N::translate('Papua New Guinea'), |
||
2596 | /* I18N: Name of a country or state */ |
||
2597 | 'POL' => I18N::translate('Poland'), |
||
2598 | /* I18N: Name of a country or state */ |
||
2599 | 'PRI' => I18N::translate('Puerto Rico'), |
||
2600 | /* I18N: Name of a country or state */ |
||
2601 | 'PRK' => I18N::translate('North Korea'), |
||
2602 | /* I18N: Name of a country or state */ |
||
2603 | 'PRT' => I18N::translate('Portugal'), |
||
2604 | /* I18N: Name of a country or state */ |
||
2605 | 'PRY' => I18N::translate('Paraguay'), |
||
2606 | /* I18N: Name of a country or state */ |
||
2607 | 'PSE' => I18N::translate('Occupied Palestinian Territory'), |
||
2608 | /* I18N: Name of a country or state */ |
||
2609 | 'PYF' => I18N::translate('French Polynesia'), |
||
2610 | /* I18N: Name of a country or state */ |
||
2611 | 'QAT' => I18N::translate('Qatar'), |
||
2612 | /* I18N: Name of a country or state */ |
||
2613 | 'REU' => I18N::translate('Réunion'), |
||
2614 | /* I18N: Name of a country or state */ |
||
2615 | 'ROM' => I18N::translate('Romania'), |
||
2616 | /* I18N: Name of a country or state */ |
||
2617 | 'RUS' => I18N::translate('Russia'), |
||
2618 | /* I18N: Name of a country or state */ |
||
2619 | 'RWA' => I18N::translate('Rwanda'), |
||
2620 | /* I18N: Name of a country or state */ |
||
2621 | 'SAU' => I18N::translate('Saudi Arabia'), |
||
2622 | /* I18N: Name of a country or state */ |
||
2623 | 'SCT' => I18N::translate('Scotland'), |
||
2624 | /* I18N: Name of a country or state */ |
||
2625 | 'SDN' => I18N::translate('Sudan'), |
||
2626 | /* I18N: Name of a country or state */ |
||
2627 | 'SEA' => I18N::translate('At sea'), |
||
2628 | /* I18N: Name of a country or state */ |
||
2629 | 'SEN' => I18N::translate('Senegal'), |
||
2630 | /* I18N: Name of a country or state */ |
||
2631 | 'SER' => I18N::translate('Serbia'), |
||
2632 | /* I18N: Name of a country or state */ |
||
2633 | 'SGP' => I18N::translate('Singapore'), |
||
2634 | /* I18N: Name of a country or state */ |
||
2635 | 'SGS' => I18N::translate('South Georgia and the South Sandwich Islands'), |
||
2636 | /* I18N: Name of a country or state */ |
||
2637 | 'SHN' => I18N::translate('Saint Helena'), |
||
2638 | /* I18N: Name of a country or state */ |
||
2639 | 'SJM' => I18N::translate('Svalbard and Jan Mayen'), |
||
2640 | /* I18N: Name of a country or state */ |
||
2641 | 'SLB' => I18N::translate('Solomon Islands'), |
||
2642 | /* I18N: Name of a country or state */ |
||
2643 | 'SLE' => I18N::translate('Sierra Leone'), |
||
2644 | /* I18N: Name of a country or state */ |
||
2645 | 'SLV' => I18N::translate('El Salvador'), |
||
2646 | /* I18N: Name of a country or state */ |
||
2647 | 'SMR' => I18N::translate('San Marino'), |
||
2648 | /* I18N: Name of a country or state */ |
||
2649 | 'SOM' => I18N::translate('Somalia'), |
||
2650 | /* I18N: Name of a country or state */ |
||
2651 | 'SPM' => I18N::translate('Saint Pierre and Miquelon'), |
||
2652 | /* I18N: Name of a country or state */ |
||
2653 | 'SSD' => I18N::translate('South Sudan'), |
||
2654 | /* I18N: Name of a country or state */ |
||
2655 | 'STP' => I18N::translate('Sao Tome and Principe'), |
||
2656 | /* I18N: Name of a country or state */ |
||
2657 | 'SUR' => I18N::translate('Suriname'), |
||
2658 | /* I18N: Name of a country or state */ |
||
2659 | 'SVK' => I18N::translate('Slovakia'), |
||
2660 | /* I18N: Name of a country or state */ |
||
2661 | 'SVN' => I18N::translate('Slovenia'), |
||
2662 | /* I18N: Name of a country or state */ |
||
2663 | 'SWE' => I18N::translate('Sweden'), |
||
2664 | /* I18N: Name of a country or state */ |
||
2665 | 'SWZ' => I18N::translate('Swaziland'), |
||
2666 | // SXM => Sint Maarten |
||
2667 | /* I18N: Name of a country or state */ |
||
2668 | 'SYC' => I18N::translate('Seychelles'), |
||
2669 | /* I18N: Name of a country or state */ |
||
2670 | 'SYR' => I18N::translate('Syria'), |
||
2671 | /* I18N: Name of a country or state */ |
||
2672 | 'TCA' => I18N::translate('Turks and Caicos Islands'), |
||
2673 | /* I18N: Name of a country or state */ |
||
2674 | 'TCD' => I18N::translate('Chad'), |
||
2675 | /* I18N: Name of a country or state */ |
||
2676 | 'TGO' => I18N::translate('Togo'), |
||
2677 | /* I18N: Name of a country or state */ |
||
2678 | 'THA' => I18N::translate('Thailand'), |
||
2679 | /* I18N: Name of a country or state */ |
||
2680 | 'TJK' => I18N::translate('Tajikistan'), |
||
2681 | /* I18N: Name of a country or state */ |
||
2682 | 'TKL' => I18N::translate('Tokelau'), |
||
2683 | /* I18N: Name of a country or state */ |
||
2684 | 'TKM' => I18N::translate('Turkmenistan'), |
||
2685 | /* I18N: Name of a country or state */ |
||
2686 | 'TLS' => I18N::translate('Timor-Leste'), |
||
2687 | /* I18N: Name of a country or state */ |
||
2688 | 'TON' => I18N::translate('Tonga'), |
||
2689 | /* I18N: Name of a country or state */ |
||
2690 | 'TTO' => I18N::translate('Trinidad and Tobago'), |
||
2691 | /* I18N: Name of a country or state */ |
||
2692 | 'TUN' => I18N::translate('Tunisia'), |
||
2693 | /* I18N: Name of a country or state */ |
||
2694 | 'TUR' => I18N::translate('Turkey'), |
||
2695 | /* I18N: Name of a country or state */ |
||
2696 | 'TUV' => I18N::translate('Tuvalu'), |
||
2697 | /* I18N: Name of a country or state */ |
||
2698 | 'TWN' => I18N::translate('Taiwan'), |
||
2699 | /* I18N: Name of a country or state */ |
||
2700 | 'TZA' => I18N::translate('Tanzania'), |
||
2701 | /* I18N: Name of a country or state */ |
||
2702 | 'UGA' => I18N::translate('Uganda'), |
||
2703 | /* I18N: Name of a country or state */ |
||
2704 | 'UKR' => I18N::translate('Ukraine'), |
||
2705 | /* I18N: Name of a country or state */ |
||
2706 | 'UMI' => I18N::translate('US Minor Outlying Islands'), |
||
2707 | /* I18N: Name of a country or state */ |
||
2708 | 'URY' => I18N::translate('Uruguay'), |
||
2709 | /* I18N: Name of a country or state */ |
||
2710 | 'USA' => I18N::translate('United States'), |
||
2711 | /* I18N: Name of a country or state */ |
||
2712 | 'UZB' => I18N::translate('Uzbekistan'), |
||
2713 | /* I18N: Name of a country or state */ |
||
2714 | 'VAT' => I18N::translate('Vatican City'), |
||
2715 | /* I18N: Name of a country or state */ |
||
2716 | 'VCT' => I18N::translate('Saint Vincent and the Grenadines'), |
||
2717 | /* I18N: Name of a country or state */ |
||
2718 | 'VEN' => I18N::translate('Venezuela'), |
||
2719 | /* I18N: Name of a country or state */ |
||
2720 | 'VGB' => I18N::translate('British Virgin Islands'), |
||
2721 | /* I18N: Name of a country or state */ |
||
2722 | 'VIR' => I18N::translate('US Virgin Islands'), |
||
2723 | /* I18N: Name of a country or state */ |
||
2724 | 'VNM' => I18N::translate('Vietnam'), |
||
2725 | /* I18N: Name of a country or state */ |
||
2726 | 'VUT' => I18N::translate('Vanuatu'), |
||
2727 | /* I18N: Name of a country or state */ |
||
2728 | 'WLF' => I18N::translate('Wallis and Futuna'), |
||
2729 | /* I18N: Name of a country or state */ |
||
2730 | 'WLS' => I18N::translate('Wales'), |
||
2731 | /* I18N: Name of a country or state */ |
||
2732 | 'WSM' => I18N::translate('Samoa'), |
||
2733 | /* I18N: Name of a country or state */ |
||
2734 | 'YEM' => I18N::translate('Yemen'), |
||
2735 | /* I18N: Name of a country or state */ |
||
2736 | 'ZAF' => I18N::translate('South Africa'), |
||
2737 | /* I18N: Name of a country or state */ |
||
2738 | 'ZMB' => I18N::translate('Zambia'), |
||
2739 | /* I18N: Name of a country or state */ |
||
2740 | 'ZWE' => I18N::translate('Zimbabwe'), |
||
2741 | ]; |
||
2742 | } |
||
2743 | |||
2744 | /** |
||
2745 | * ISO3166 3 letter codes, with their 2 letter equivalent. |
||
2746 | * NOTE: this is not 1:1. ENG/SCO/WAL/NIR => GB |
||
2747 | * NOTE: this also includes chapman codes and others. Should it? |
||
2748 | * |
||
2749 | * @return array<string> |
||
2750 | */ |
||
2751 | private function iso3166(): array |
||
2752 | { |
||
2753 | return [ |
||
2754 | 'GBR' => 'GB', // Must come before ENG, NIR, SCT and WLS |
||
2755 | 'ABW' => 'AW', |
||
2756 | 'AFG' => 'AF', |
||
2757 | 'AGO' => 'AO', |
||
2758 | 'AIA' => 'AI', |
||
2759 | 'ALA' => 'AX', |
||
2760 | 'ALB' => 'AL', |
||
2761 | 'AND' => 'AD', |
||
2762 | 'ARE' => 'AE', |
||
2763 | 'ARG' => 'AR', |
||
2764 | 'ARM' => 'AM', |
||
2765 | 'ASM' => 'AS', |
||
2766 | 'ATA' => 'AQ', |
||
2767 | 'ATF' => 'TF', |
||
2768 | 'ATG' => 'AG', |
||
2769 | 'AUS' => 'AU', |
||
2770 | 'AUT' => 'AT', |
||
2771 | 'AZE' => 'AZ', |
||
2772 | 'BDI' => 'BI', |
||
2773 | 'BEL' => 'BE', |
||
2774 | 'BEN' => 'BJ', |
||
2775 | 'BFA' => 'BF', |
||
2776 | 'BGD' => 'BD', |
||
2777 | 'BGR' => 'BG', |
||
2778 | 'BHR' => 'BH', |
||
2779 | 'BHS' => 'BS', |
||
2780 | 'BIH' => 'BA', |
||
2781 | 'BLR' => 'BY', |
||
2782 | 'BLZ' => 'BZ', |
||
2783 | 'BMU' => 'BM', |
||
2784 | 'BOL' => 'BO', |
||
2785 | 'BRA' => 'BR', |
||
2786 | 'BRB' => 'BB', |
||
2787 | 'BRN' => 'BN', |
||
2788 | 'BTN' => 'BT', |
||
2789 | 'BVT' => 'BV', |
||
2790 | 'BWA' => 'BW', |
||
2791 | 'CAF' => 'CF', |
||
2792 | 'CAN' => 'CA', |
||
2793 | 'CCK' => 'CC', |
||
2794 | 'CHE' => 'CH', |
||
2795 | 'CHL' => 'CL', |
||
2796 | 'CHN' => 'CN', |
||
2797 | 'CIV' => 'CI', |
||
2798 | 'CMR' => 'CM', |
||
2799 | 'COD' => 'CD', |
||
2800 | 'COG' => 'CG', |
||
2801 | 'COK' => 'CK', |
||
2802 | 'COL' => 'CO', |
||
2803 | 'COM' => 'KM', |
||
2804 | 'CPV' => 'CV', |
||
2805 | 'CRI' => 'CR', |
||
2806 | 'CUB' => 'CU', |
||
2807 | 'CXR' => 'CX', |
||
2808 | 'CYM' => 'KY', |
||
2809 | 'CYP' => 'CY', |
||
2810 | 'CZE' => 'CZ', |
||
2811 | 'DEU' => 'DE', |
||
2812 | 'DJI' => 'DJ', |
||
2813 | 'DMA' => 'DM', |
||
2814 | 'DNK' => 'DK', |
||
2815 | 'DOM' => 'DO', |
||
2816 | 'DZA' => 'DZ', |
||
2817 | 'ECU' => 'EC', |
||
2818 | 'EGY' => 'EG', |
||
2819 | 'ENG' => 'GB', |
||
2820 | 'ERI' => 'ER', |
||
2821 | 'ESH' => 'EH', |
||
2822 | 'ESP' => 'ES', |
||
2823 | 'EST' => 'EE', |
||
2824 | 'ETH' => 'ET', |
||
2825 | 'FIN' => 'FI', |
||
2826 | 'FJI' => 'FJ', |
||
2827 | 'FLK' => 'FK', |
||
2828 | 'FRA' => 'FR', |
||
2829 | 'FRO' => 'FO', |
||
2830 | 'FSM' => 'FM', |
||
2831 | 'GAB' => 'GA', |
||
2832 | 'GEO' => 'GE', |
||
2833 | 'GHA' => 'GH', |
||
2834 | 'GIB' => 'GI', |
||
2835 | 'GIN' => 'GN', |
||
2836 | 'GLP' => 'GP', |
||
2837 | 'GMB' => 'GM', |
||
2838 | 'GNB' => 'GW', |
||
2839 | 'GNQ' => 'GQ', |
||
2840 | 'GRC' => 'GR', |
||
2841 | 'GRD' => 'GD', |
||
2842 | 'GRL' => 'GL', |
||
2843 | 'GTM' => 'GT', |
||
2844 | 'GUF' => 'GF', |
||
2845 | 'GUM' => 'GU', |
||
2846 | 'GUY' => 'GY', |
||
2847 | 'HKG' => 'HK', |
||
2848 | 'HMD' => 'HM', |
||
2849 | 'HND' => 'HN', |
||
2850 | 'HRV' => 'HR', |
||
2851 | 'HTI' => 'HT', |
||
2852 | 'HUN' => 'HU', |
||
2853 | 'IDN' => 'ID', |
||
2854 | 'IND' => 'IN', |
||
2855 | 'IOT' => 'IO', |
||
2856 | 'IRL' => 'IE', |
||
2857 | 'IRN' => 'IR', |
||
2858 | 'IRQ' => 'IQ', |
||
2859 | 'ISL' => 'IS', |
||
2860 | 'ISR' => 'IL', |
||
2861 | 'ITA' => 'IT', |
||
2862 | 'JAM' => 'JM', |
||
2863 | 'JOR' => 'JO', |
||
2864 | 'JPN' => 'JP', |
||
2865 | 'KAZ' => 'KZ', |
||
2866 | 'KEN' => 'KE', |
||
2867 | 'KGZ' => 'KG', |
||
2868 | 'KHM' => 'KH', |
||
2869 | 'KIR' => 'KI', |
||
2870 | 'KNA' => 'KN', |
||
2871 | 'KOR' => 'KO', |
||
2872 | 'KWT' => 'KW', |
||
2873 | 'LAO' => 'LA', |
||
2874 | 'LBN' => 'LB', |
||
2875 | 'LBR' => 'LR', |
||
2876 | 'LBY' => 'LY', |
||
2877 | 'LCA' => 'LC', |
||
2878 | 'LIE' => 'LI', |
||
2879 | 'LKA' => 'LK', |
||
2880 | 'LSO' => 'LS', |
||
2881 | 'LTU' => 'LT', |
||
2882 | 'LUX' => 'LU', |
||
2883 | 'LVA' => 'LV', |
||
2884 | 'MAC' => 'MO', |
||
2885 | 'MAR' => 'MA', |
||
2886 | 'MCO' => 'MC', |
||
2887 | 'MDA' => 'MD', |
||
2888 | 'MDG' => 'MG', |
||
2889 | 'MDV' => 'MV', |
||
2890 | 'MEX' => 'MX', |
||
2891 | 'MHL' => 'MH', |
||
2892 | 'MKD' => 'MK', |
||
2893 | 'MLI' => 'ML', |
||
2894 | 'MLT' => 'MT', |
||
2895 | 'MMR' => 'MM', |
||
2896 | 'MNG' => 'MN', |
||
2897 | 'MNP' => 'MP', |
||
2898 | 'MNT' => 'ME', |
||
2899 | 'MOZ' => 'MZ', |
||
2900 | 'MRT' => 'MR', |
||
2901 | 'MSR' => 'MS', |
||
2902 | 'MTQ' => 'MQ', |
||
2903 | 'MUS' => 'MU', |
||
2904 | 'MWI' => 'MW', |
||
2905 | 'MYS' => 'MY', |
||
2906 | 'MYT' => 'YT', |
||
2907 | 'NAM' => 'NA', |
||
2908 | 'NCL' => 'NC', |
||
2909 | 'NER' => 'NE', |
||
2910 | 'NFK' => 'NF', |
||
2911 | 'NGA' => 'NG', |
||
2912 | 'NIC' => 'NI', |
||
2913 | 'NIR' => 'GB', |
||
2914 | 'NIU' => 'NU', |
||
2915 | 'NLD' => 'NL', |
||
2916 | 'NOR' => 'NO', |
||
2917 | 'NPL' => 'NP', |
||
2918 | 'NRU' => 'NR', |
||
2919 | 'NZL' => 'NZ', |
||
2920 | 'OMN' => 'OM', |
||
2921 | 'PAK' => 'PK', |
||
2922 | 'PAN' => 'PA', |
||
2923 | 'PCN' => 'PN', |
||
2924 | 'PER' => 'PE', |
||
2925 | 'PHL' => 'PH', |
||
2926 | 'PLW' => 'PW', |
||
2927 | 'PNG' => 'PG', |
||
2928 | 'POL' => 'PL', |
||
2929 | 'PRI' => 'PR', |
||
2930 | 'PRK' => 'KP', |
||
2931 | 'PRT' => 'PT', |
||
2932 | 'PRY' => 'PY', |
||
2933 | 'PSE' => 'PS', |
||
2934 | 'PYF' => 'PF', |
||
2935 | 'QAT' => 'QA', |
||
2936 | 'REU' => 'RE', |
||
2937 | 'ROM' => 'RO', |
||
2938 | 'RUS' => 'RU', |
||
2939 | 'RWA' => 'RW', |
||
2940 | 'SAU' => 'SA', |
||
2941 | 'SCT' => 'GB', |
||
2942 | 'SDN' => 'SD', |
||
2943 | 'SEN' => 'SN', |
||
2944 | 'SER' => 'RS', |
||
2945 | 'SGP' => 'SG', |
||
2946 | 'SGS' => 'GS', |
||
2947 | 'SHN' => 'SH', |
||
2948 | 'SJM' => 'SJ', |
||
2949 | 'SLB' => 'SB', |
||
2950 | 'SLE' => 'SL', |
||
2951 | 'SLV' => 'SV', |
||
2952 | 'SMR' => 'SM', |
||
2953 | 'SOM' => 'SO', |
||
2954 | 'SPM' => 'PM', |
||
2955 | 'STP' => 'ST', |
||
2956 | 'SUR' => 'SR', |
||
2957 | 'SVK' => 'SK', |
||
2958 | 'SVN' => 'SI', |
||
2959 | 'SWE' => 'SE', |
||
2960 | 'SWZ' => 'SZ', |
||
2961 | 'SYC' => 'SC', |
||
2962 | 'SYR' => 'SY', |
||
2963 | 'TCA' => 'TC', |
||
2964 | 'TCD' => 'TD', |
||
2965 | 'TGO' => 'TG', |
||
2966 | 'THA' => 'TH', |
||
2967 | 'TJK' => 'TJ', |
||
2968 | 'TKL' => 'TK', |
||
2969 | 'TKM' => 'TM', |
||
2970 | 'TLS' => 'TL', |
||
2971 | 'TON' => 'TO', |
||
2972 | 'TTO' => 'TT', |
||
2973 | 'TUN' => 'TN', |
||
2974 | 'TUR' => 'TR', |
||
2975 | 'TUV' => 'TV', |
||
2976 | 'TWN' => 'TW', |
||
2977 | 'TZA' => 'TZ', |
||
2978 | 'UGA' => 'UG', |
||
2979 | 'UKR' => 'UA', |
||
2980 | 'UMI' => 'UM', |
||
2981 | 'URY' => 'UY', |
||
2982 | 'USA' => 'US', |
||
2983 | 'UZB' => 'UZ', |
||
2984 | 'VAT' => 'VA', |
||
2985 | 'VCT' => 'VC', |
||
2986 | 'VEN' => 'VE', |
||
2987 | 'VGB' => 'VG', |
||
2988 | 'VIR' => 'VI', |
||
2989 | 'VNM' => 'VN', |
||
2990 | 'VUT' => 'VU', |
||
2991 | 'WLF' => 'WF', |
||
2992 | 'WLS' => 'GB', |
||
2993 | 'WSM' => 'WS', |
||
2994 | 'YEM' => 'YE', |
||
2995 | 'ZAF' => 'ZA', |
||
2996 | 'ZMB' => 'ZM', |
||
2997 | 'ZWE' => 'ZW', |
||
2998 | ]; |
||
2999 | } |
||
3000 | |||
3001 | /** |
||
3002 | * Returns the translated country name based on the given two letter country code. |
||
3003 | */ |
||
3004 | private function mapTwoLetterToName(string $twoLetterCode): string |
||
3005 | { |
||
3006 | $threeLetterCode = array_search($twoLetterCode, $this->iso3166(), true); |
||
3007 | $threeLetterCode = $threeLetterCode ?: '???'; |
||
3008 | |||
3009 | return $this->getAllCountries()[$threeLetterCode]; |
||
3010 | } |
||
3011 | } |
||
3012 |