18add1155SRico Sonntag<?php 23976b470SGreg Roach 38add1155SRico Sonntag/** 48add1155SRico Sonntag * webtrees: online genealogy 5d11be702SGreg Roach * Copyright (C) 2023 webtrees development team 68add1155SRico Sonntag * This program is free software: you can redistribute it and/or modify 78add1155SRico Sonntag * it under the terms of the GNU General Public License as published by 88add1155SRico Sonntag * the Free Software Foundation, either version 3 of the License, or 98add1155SRico Sonntag * (at your option) any later version. 108add1155SRico Sonntag * This program is distributed in the hope that it will be useful, 118add1155SRico Sonntag * but WITHOUT ANY WARRANTY; without even the implied warranty of 128add1155SRico Sonntag * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 138add1155SRico Sonntag * GNU General Public License for more details. 148add1155SRico Sonntag * You should have received a copy of the GNU General Public License 1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 168add1155SRico Sonntag */ 17fcfa147eSGreg Roach 188add1155SRico Sonntagdeclare(strict_types=1); 198add1155SRico Sonntag 208add1155SRico Sonntagnamespace Fisharebest\Webtrees\Statistics\Repository; 218add1155SRico Sonntag 228add1155SRico Sonntaguse Fisharebest\Webtrees\Date; 236f4ec3caSGreg Roachuse Fisharebest\Webtrees\DB; 2497def6bcSGreg Roachuse Fisharebest\Webtrees\Elements\UnknownElement; 25820b62dfSGreg Roachuse Fisharebest\Webtrees\Fact; 2697def6bcSGreg Roachuse Fisharebest\Webtrees\Family; 278add1155SRico Sonntaguse Fisharebest\Webtrees\Gedcom; 28ef475b14SGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 291635452cSGreg Roachuse Fisharebest\Webtrees\Header; 308add1155SRico Sonntaguse Fisharebest\Webtrees\I18N; 31f0c88a96SGreg Roachuse Fisharebest\Webtrees\Individual; 32f0c88a96SGreg Roachuse Fisharebest\Webtrees\Registry; 338add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\Interfaces\EventRepositoryInterface; 348add1155SRico Sonntaguse Fisharebest\Webtrees\Tree; 358add1155SRico Sonntag 362cd72788SGreg Roachuse function abs; 374c78e066SGreg Roachuse function array_map; 384c78e066SGreg Roachuse function array_merge; 394c78e066SGreg Roachuse function e; 404c78e066SGreg Roachuse function strncmp; 414c78e066SGreg Roachuse function substr; 424c78e066SGreg Roach 438add1155SRico Sonntag/** 448add1155SRico Sonntag * A repository providing methods for event related statistics. 458add1155SRico Sonntag */ 468add1155SRico Sonntagclass EventRepository implements EventRepositoryInterface 478add1155SRico Sonntag{ 488add1155SRico Sonntag /** 498add1155SRico Sonntag * Sorting directions. 508add1155SRico Sonntag */ 518add1155SRico Sonntag private const SORT_ASC = 'ASC'; 528add1155SRico Sonntag private const SORT_DESC = 'DESC'; 538add1155SRico Sonntag 548add1155SRico Sonntag /** 558add1155SRico Sonntag * Event facts. 568add1155SRico Sonntag */ 578add1155SRico Sonntag private const EVENT_BIRTH = 'BIRT'; 588add1155SRico Sonntag private const EVENT_DEATH = 'DEAT'; 598add1155SRico Sonntag private const EVENT_MARRIAGE = 'MARR'; 608add1155SRico Sonntag private const EVENT_DIVORCE = 'DIV'; 618add1155SRico Sonntag 624c78e066SGreg Roach private Tree $tree; 638add1155SRico Sonntag 648add1155SRico Sonntag /** 658add1155SRico Sonntag * @param Tree $tree 668add1155SRico Sonntag */ 678add1155SRico Sonntag public function __construct(Tree $tree) 688add1155SRico Sonntag { 698add1155SRico Sonntag $this->tree = $tree; 708add1155SRico Sonntag } 718add1155SRico Sonntag 728add1155SRico Sonntag /** 738add1155SRico Sonntag * Returns the total number of a given list of events (with dates). 748add1155SRico Sonntag * 759123453fSGreg Roach * @param array<string> $events The list of events to count (e.g. BIRT, DEAT, ...) 768add1155SRico Sonntag * 778add1155SRico Sonntag * @return int 788add1155SRico Sonntag */ 79c0112ce8SGreg Roach private function getEventCount(array $events): int 808add1155SRico Sonntag { 818add1155SRico Sonntag $query = DB::table('dates') 828add1155SRico Sonntag ->where('d_file', '=', $this->tree->id()); 838add1155SRico Sonntag 848add1155SRico Sonntag $no_types = [ 858add1155SRico Sonntag 'HEAD', 868add1155SRico Sonntag 'CHAN', 878add1155SRico Sonntag ]; 888add1155SRico Sonntag 899123453fSGreg Roach if ($events !== []) { 908add1155SRico Sonntag $types = []; 918add1155SRico Sonntag 928add1155SRico Sonntag foreach ($events as $type) { 938add1155SRico Sonntag if (strncmp($type, '!', 1) === 0) { 948add1155SRico Sonntag $no_types[] = substr($type, 1); 958add1155SRico Sonntag } else { 968add1155SRico Sonntag $types[] = $type; 978add1155SRico Sonntag } 988add1155SRico Sonntag } 998add1155SRico Sonntag 1009123453fSGreg Roach if ($types !== []) { 1018add1155SRico Sonntag $query->whereIn('d_fact', $types); 1028add1155SRico Sonntag } 1038add1155SRico Sonntag } 1048add1155SRico Sonntag 1058add1155SRico Sonntag return $query->whereNotIn('d_fact', $no_types) 1068add1155SRico Sonntag ->count(); 1078add1155SRico Sonntag } 1088add1155SRico Sonntag 1098add1155SRico Sonntag /** 11009482a55SGreg Roach * @param array<string> $events 1110dcd9387SGreg Roach * 1120dcd9387SGreg Roach * @return string 1138add1155SRico Sonntag */ 1148add1155SRico Sonntag public function totalEvents(array $events = []): string 1158add1155SRico Sonntag { 1168add1155SRico Sonntag return I18N::number( 1178add1155SRico Sonntag $this->getEventCount($events) 1188add1155SRico Sonntag ); 1198add1155SRico Sonntag } 1208add1155SRico Sonntag 1218add1155SRico Sonntag /** 1220dcd9387SGreg Roach * @return string 1238add1155SRico Sonntag */ 1248add1155SRico Sonntag public function totalEventsBirth(): string 1258add1155SRico Sonntag { 1268add1155SRico Sonntag return $this->totalEvents(Gedcom::BIRTH_EVENTS); 1278add1155SRico Sonntag } 1288add1155SRico Sonntag 1298add1155SRico Sonntag /** 1300dcd9387SGreg Roach * @return string 1318add1155SRico Sonntag */ 1328add1155SRico Sonntag public function totalBirths(): string 1338add1155SRico Sonntag { 1348add1155SRico Sonntag return $this->totalEvents([self::EVENT_BIRTH]); 1358add1155SRico Sonntag } 1368add1155SRico Sonntag 1378add1155SRico Sonntag /** 1380dcd9387SGreg Roach * @return string 1398add1155SRico Sonntag */ 1408add1155SRico Sonntag public function totalEventsDeath(): string 1418add1155SRico Sonntag { 1428add1155SRico Sonntag return $this->totalEvents(Gedcom::DEATH_EVENTS); 1438add1155SRico Sonntag } 1448add1155SRico Sonntag 1458add1155SRico Sonntag /** 1460dcd9387SGreg Roach * @return string 1478add1155SRico Sonntag */ 1488add1155SRico Sonntag public function totalDeaths(): string 1498add1155SRico Sonntag { 1508add1155SRico Sonntag return $this->totalEvents([self::EVENT_DEATH]); 1518add1155SRico Sonntag } 1528add1155SRico Sonntag 1538add1155SRico Sonntag /** 1540dcd9387SGreg Roach * @return string 1558add1155SRico Sonntag */ 1568add1155SRico Sonntag public function totalEventsMarriage(): string 1578add1155SRico Sonntag { 1588add1155SRico Sonntag return $this->totalEvents(Gedcom::MARRIAGE_EVENTS); 1598add1155SRico Sonntag } 1608add1155SRico Sonntag 1618add1155SRico Sonntag /** 1620dcd9387SGreg Roach * @return string 1638add1155SRico Sonntag */ 1648add1155SRico Sonntag public function totalMarriages(): string 1658add1155SRico Sonntag { 1668add1155SRico Sonntag return $this->totalEvents([self::EVENT_MARRIAGE]); 1678add1155SRico Sonntag } 1688add1155SRico Sonntag 1698add1155SRico Sonntag /** 1700dcd9387SGreg Roach * @return string 1718add1155SRico Sonntag */ 1728add1155SRico Sonntag public function totalEventsDivorce(): string 1738add1155SRico Sonntag { 1748add1155SRico Sonntag return $this->totalEvents(Gedcom::DIVORCE_EVENTS); 1758add1155SRico Sonntag } 1768add1155SRico Sonntag 1778add1155SRico Sonntag /** 1780dcd9387SGreg Roach * @return string 1798add1155SRico Sonntag */ 1808add1155SRico Sonntag public function totalDivorces(): string 1818add1155SRico Sonntag { 1828add1155SRico Sonntag return $this->totalEvents([self::EVENT_DIVORCE]); 1838add1155SRico Sonntag } 1848add1155SRico Sonntag 1858add1155SRico Sonntag /** 18652f124b0SAlejandro Criado-Pérez * Returns the list of common facts used query the data. 1878add1155SRico Sonntag * 1889123453fSGreg Roach * @return array<string> 1898add1155SRico Sonntag */ 1908add1155SRico Sonntag private function getCommonFacts(): array 1918add1155SRico Sonntag { 1928add1155SRico Sonntag // The list of facts used to limit the query result 1938add1155SRico Sonntag return array_merge( 1948add1155SRico Sonntag Gedcom::BIRTH_EVENTS, 1958add1155SRico Sonntag Gedcom::MARRIAGE_EVENTS, 1968add1155SRico Sonntag Gedcom::DIVORCE_EVENTS, 1978add1155SRico Sonntag Gedcom::DEATH_EVENTS 1988add1155SRico Sonntag ); 1998add1155SRico Sonntag } 2008add1155SRico Sonntag 2018add1155SRico Sonntag /** 2020dcd9387SGreg Roach * @return string 2038add1155SRico Sonntag */ 2048add1155SRico Sonntag public function totalEventsOther(): string 2058add1155SRico Sonntag { 2068add1155SRico Sonntag $no_facts = array_map( 207*f25fc0f9SGreg Roach static fn(string $fact): string => '!' . $fact, 2088add1155SRico Sonntag $this->getCommonFacts() 2098add1155SRico Sonntag ); 2108add1155SRico Sonntag 2118add1155SRico Sonntag return $this->totalEvents($no_facts); 2128add1155SRico Sonntag } 2138add1155SRico Sonntag 2148add1155SRico Sonntag /** 2158add1155SRico Sonntag * Returns the first/last event record from the given list of event facts. 2168add1155SRico Sonntag * 2178add1155SRico Sonntag * @param string $direction The sorting direction of the query (To return first or last record) 2188add1155SRico Sonntag * 2192cd72788SGreg Roach * @return object{id:string,year:int,fact:string,type:string}|null 2208add1155SRico Sonntag */ 221ac701fbdSGreg Roach private function eventQuery(string $direction): ?object 2228add1155SRico Sonntag { 2238add1155SRico Sonntag return DB::table('dates') 2248add1155SRico Sonntag ->select(['d_gid as id', 'd_year as year', 'd_fact AS fact', 'd_type AS type']) 2258add1155SRico Sonntag ->where('d_file', '=', $this->tree->id()) 2261635452cSGreg Roach ->where('d_gid', '<>', Header::RECORD_TYPE) 2278add1155SRico Sonntag ->whereIn('d_fact', $this->getCommonFacts()) 2288add1155SRico Sonntag ->where('d_julianday1', '<>', 0) 2298add1155SRico Sonntag ->orderBy('d_julianday1', $direction) 2308add1155SRico Sonntag ->orderBy('d_type') 2312cd72788SGreg Roach ->limit(1) 2322cd72788SGreg Roach ->get() 2332cd72788SGreg Roach ->map(static fn (object $row): object => (object) [ 2342cd72788SGreg Roach 'id' => $row->id, 2352cd72788SGreg Roach 'year' => (int) $row->year, 2362cd72788SGreg Roach 'fact' => $row->fact, 2372cd72788SGreg Roach 'type' => $row->type, 2382cd72788SGreg Roach ]) 2398add1155SRico Sonntag ->first(); 2408add1155SRico Sonntag } 2418add1155SRico Sonntag 2428add1155SRico Sonntag /** 24352f124b0SAlejandro Criado-Pérez * Returns the formatted first/last occurring event. 2448add1155SRico Sonntag * 2458add1155SRico Sonntag * @param string $direction The sorting direction 2468add1155SRico Sonntag * 2478add1155SRico Sonntag * @return string 2488add1155SRico Sonntag */ 2498add1155SRico Sonntag private function getFirstLastEvent(string $direction): string 2508add1155SRico Sonntag { 2518add1155SRico Sonntag $row = $this->eventQuery($direction); 252dd7dd2a1SRico Sonntag $result = I18N::translate('This information is not available.'); 2538add1155SRico Sonntag 254ef475b14SGreg Roach if ($row !== null) { 2556b9cb339SGreg Roach $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree); 2568add1155SRico Sonntag 257ef475b14SGreg Roach if ($record instanceof GedcomRecord && $record->canShow()) { 2588add1155SRico Sonntag $result = $record->formatList(); 2598add1155SRico Sonntag } else { 2608add1155SRico Sonntag $result = I18N::translate('This information is private and cannot be shown.'); 2618add1155SRico Sonntag } 2628add1155SRico Sonntag } 2638add1155SRico Sonntag 2648add1155SRico Sonntag return $result; 2658add1155SRico Sonntag } 2668add1155SRico Sonntag 2678add1155SRico Sonntag /** 2680dcd9387SGreg Roach * @return string 2698add1155SRico Sonntag */ 2708add1155SRico Sonntag public function firstEvent(): string 2718add1155SRico Sonntag { 2728add1155SRico Sonntag return $this->getFirstLastEvent(self::SORT_ASC); 2738add1155SRico Sonntag } 2748add1155SRico Sonntag 2758add1155SRico Sonntag /** 2760dcd9387SGreg Roach * @return string 2778add1155SRico Sonntag */ 2788add1155SRico Sonntag public function lastEvent(): string 2798add1155SRico Sonntag { 2808add1155SRico Sonntag return $this->getFirstLastEvent(self::SORT_DESC); 2818add1155SRico Sonntag } 2828add1155SRico Sonntag 2838add1155SRico Sonntag /** 28452f124b0SAlejandro Criado-Pérez * Returns the formatted year of the first/last occurring event. 2858add1155SRico Sonntag * 2868add1155SRico Sonntag * @param string $direction The sorting direction 2878add1155SRico Sonntag * 2888add1155SRico Sonntag * @return string 2898add1155SRico Sonntag */ 2908add1155SRico Sonntag private function getFirstLastEventYear(string $direction): string 2918add1155SRico Sonntag { 2928add1155SRico Sonntag $row = $this->eventQuery($direction); 2938add1155SRico Sonntag 294ef475b14SGreg Roach if ($row === null) { 2958add1155SRico Sonntag return ''; 2968add1155SRico Sonntag } 2978add1155SRico Sonntag 2982cd72788SGreg Roach if ($row->year < 0) { 2992cd72788SGreg Roach $row->year = abs($row->year) . ' B.C.'; 3002cd72788SGreg Roach } 3012cd72788SGreg Roach 3028add1155SRico Sonntag return (new Date($row->type . ' ' . $row->year)) 3038add1155SRico Sonntag ->display(); 3048add1155SRico Sonntag } 3058add1155SRico Sonntag 3068add1155SRico Sonntag /** 3070dcd9387SGreg Roach * @return string 3088add1155SRico Sonntag */ 3098add1155SRico Sonntag public function firstEventYear(): string 3108add1155SRico Sonntag { 3118add1155SRico Sonntag return $this->getFirstLastEventYear(self::SORT_ASC); 3128add1155SRico Sonntag } 3138add1155SRico Sonntag 3148add1155SRico Sonntag /** 3150dcd9387SGreg Roach * @return string 3168add1155SRico Sonntag */ 3178add1155SRico Sonntag public function lastEventYear(): string 3188add1155SRico Sonntag { 3198add1155SRico Sonntag return $this->getFirstLastEventYear(self::SORT_DESC); 3208add1155SRico Sonntag } 3218add1155SRico Sonntag 3228add1155SRico Sonntag /** 323c2ed51d1SGreg Roach * Returns the formatted type of the first/last occurring event. 3248add1155SRico Sonntag * 3258add1155SRico Sonntag * @param string $direction The sorting direction 3268add1155SRico Sonntag * 3278add1155SRico Sonntag * @return string 3288add1155SRico Sonntag */ 3298add1155SRico Sonntag private function getFirstLastEventType(string $direction): string 3308add1155SRico Sonntag { 3318add1155SRico Sonntag $row = $this->eventQuery($direction); 3328add1155SRico Sonntag 33397def6bcSGreg Roach if ($row === null) { 33497def6bcSGreg Roach return ''; 3358add1155SRico Sonntag } 3368add1155SRico Sonntag 33797def6bcSGreg Roach foreach ([Individual::RECORD_TYPE, Family::RECORD_TYPE] as $record_type) { 33897def6bcSGreg Roach $element = Registry::elementFactory()->make($record_type . ':' . $row->fact); 33997def6bcSGreg Roach 34097def6bcSGreg Roach if (!$element instanceof UnknownElement) { 34197def6bcSGreg Roach return $element->label(); 34297def6bcSGreg Roach } 34397def6bcSGreg Roach } 34497def6bcSGreg Roach 34597def6bcSGreg Roach return $row->fact; 3468add1155SRico Sonntag } 3478add1155SRico Sonntag 3488add1155SRico Sonntag /** 3490dcd9387SGreg Roach * @return string 3508add1155SRico Sonntag */ 3518add1155SRico Sonntag public function firstEventType(): string 3528add1155SRico Sonntag { 3538add1155SRico Sonntag return $this->getFirstLastEventType(self::SORT_ASC); 3548add1155SRico Sonntag } 3558add1155SRico Sonntag 3568add1155SRico Sonntag /** 3570dcd9387SGreg Roach * @return string 3588add1155SRico Sonntag */ 3598add1155SRico Sonntag public function lastEventType(): string 3608add1155SRico Sonntag { 3618add1155SRico Sonntag return $this->getFirstLastEventType(self::SORT_DESC); 3628add1155SRico Sonntag } 3638add1155SRico Sonntag 3648add1155SRico Sonntag /** 36552f124b0SAlejandro Criado-Pérez * Returns the formatted name of the first/last occurring event. 3668add1155SRico Sonntag * 3678add1155SRico Sonntag * @param string $direction The sorting direction 3688add1155SRico Sonntag * 3698add1155SRico Sonntag * @return string 3708add1155SRico Sonntag */ 3718add1155SRico Sonntag private function getFirstLastEventName(string $direction): string 3728add1155SRico Sonntag { 3738add1155SRico Sonntag $row = $this->eventQuery($direction); 3748add1155SRico Sonntag 375ef475b14SGreg Roach if ($row !== null) { 3766b9cb339SGreg Roach $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree); 3778add1155SRico Sonntag 378ef475b14SGreg Roach if ($record instanceof GedcomRecord) { 37939ca88baSGreg Roach return '<a href="' . e($record->url()) . '">' . $record->fullName() . '</a>'; 3808add1155SRico Sonntag } 3818add1155SRico Sonntag } 3828add1155SRico Sonntag 3838add1155SRico Sonntag return ''; 3848add1155SRico Sonntag } 3858add1155SRico Sonntag 3868add1155SRico Sonntag /** 3870dcd9387SGreg Roach * @return string 3888add1155SRico Sonntag */ 3898add1155SRico Sonntag public function firstEventName(): string 3908add1155SRico Sonntag { 3918add1155SRico Sonntag return $this->getFirstLastEventName(self::SORT_ASC); 3928add1155SRico Sonntag } 3938add1155SRico Sonntag 3948add1155SRico Sonntag /** 3950dcd9387SGreg Roach * @return string 3968add1155SRico Sonntag */ 3978add1155SRico Sonntag public function lastEventName(): string 3988add1155SRico Sonntag { 3998add1155SRico Sonntag return $this->getFirstLastEventName(self::SORT_DESC); 4008add1155SRico Sonntag } 4018add1155SRico Sonntag 4028add1155SRico Sonntag /** 40352f124b0SAlejandro Criado-Pérez * Returns the formatted place of the first/last occurring event. 4048add1155SRico Sonntag * 4058add1155SRico Sonntag * @param string $direction The sorting direction 4068add1155SRico Sonntag * 4078add1155SRico Sonntag * @return string 4088add1155SRico Sonntag */ 4098add1155SRico Sonntag private function getFirstLastEventPlace(string $direction): string 4108add1155SRico Sonntag { 4118add1155SRico Sonntag $row = $this->eventQuery($direction); 4128add1155SRico Sonntag 413ef475b14SGreg Roach if ($row !== null) { 4146b9cb339SGreg Roach $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree); 4158add1155SRico Sonntag $fact = null; 4168add1155SRico Sonntag 417ef475b14SGreg Roach if ($record instanceof GedcomRecord) { 418820b62dfSGreg Roach $fact = $record->facts([$row->fact])->first(); 4198add1155SRico Sonntag } 4208add1155SRico Sonntag 421820b62dfSGreg Roach if ($fact instanceof Fact) { 422b315f3e1SGreg Roach return $fact->place()->shortName(); 4238add1155SRico Sonntag } 4248add1155SRico Sonntag } 4258add1155SRico Sonntag 4268add1155SRico Sonntag return I18N::translate('Private'); 4278add1155SRico Sonntag } 4288add1155SRico Sonntag 4298add1155SRico Sonntag /** 4300dcd9387SGreg Roach * @return string 4318add1155SRico Sonntag */ 4328add1155SRico Sonntag public function firstEventPlace(): string 4338add1155SRico Sonntag { 4348add1155SRico Sonntag return $this->getFirstLastEventPlace(self::SORT_ASC); 4358add1155SRico Sonntag } 4368add1155SRico Sonntag 4378add1155SRico Sonntag /** 4380dcd9387SGreg Roach * @return string 4398add1155SRico Sonntag */ 4408add1155SRico Sonntag public function lastEventPlace(): string 4418add1155SRico Sonntag { 4428add1155SRico Sonntag return $this->getFirstLastEventPlace(self::SORT_DESC); 4438add1155SRico Sonntag } 4448add1155SRico Sonntag} 445