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 226f4ec3caSGreg Roachuse Fisharebest\Webtrees\DB; 238add1155SRico Sonntaguse Fisharebest\Webtrees\I18N; 248add1155SRico Sonntaguse Fisharebest\Webtrees\Place; 258add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartDistribution; 26f78da678SGreg Roachuse Fisharebest\Webtrees\Statistics\Repository\Interfaces\IndividualRepositoryInterface; 278add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\Interfaces\PlaceRepositoryInterface; 2893ccd686SRico Sonntaguse Fisharebest\Webtrees\Statistics\Service\CountryService; 298add1155SRico Sonntaguse Fisharebest\Webtrees\Tree; 30a020b8bdSGreg Roachuse Illuminate\Database\Query\Expression; 318add1155SRico Sonntaguse Illuminate\Database\Query\JoinClause; 328add1155SRico Sonntag 3371378461SGreg Roachuse function array_key_exists; 344c78e066SGreg Roachuse function arsort; 354c78e066SGreg Roachuse function preg_match; 364c78e066SGreg Roachuse function view; 3771378461SGreg Roach 388add1155SRico Sonntag/** 398add1155SRico Sonntag * A repository providing methods for place related statistics. 408add1155SRico Sonntag */ 418add1155SRico Sonntagclass PlaceRepository implements PlaceRepositoryInterface 428add1155SRico Sonntag{ 434c78e066SGreg Roach private Tree $tree; 444c78e066SGreg Roach 454c78e066SGreg Roach private CountryService $country_service; 468add1155SRico Sonntag 47f78da678SGreg Roach private IndividualRepositoryInterface $individual_repository; 48f78da678SGreg Roach 498add1155SRico Sonntag /** 508add1155SRico Sonntag * @param Tree $tree 514c78e066SGreg Roach * @param CountryService $country_service 52f78da678SGreg Roach * @param IndividualRepositoryInterface $individual_repository 538add1155SRico Sonntag */ 54f78da678SGreg Roach public function __construct( 55f78da678SGreg Roach Tree $tree, 56f78da678SGreg Roach CountryService $country_service, 57f78da678SGreg Roach IndividualRepositoryInterface $individual_repository 58f78da678SGreg Roach ) { 598add1155SRico Sonntag $this->tree = $tree; 604c78e066SGreg Roach $this->country_service = $country_service; 61f78da678SGreg Roach $this->individual_repository = $individual_repository; 628add1155SRico Sonntag } 638add1155SRico Sonntag 648add1155SRico Sonntag /** 658add1155SRico Sonntag * Places 668add1155SRico Sonntag * 678add1155SRico Sonntag * @param string $fact 688add1155SRico Sonntag * @param string $what 698add1155SRico Sonntag * 707edfe083SGreg Roach * @return array<int> 718add1155SRico Sonntag */ 727edfe083SGreg Roach private function queryFactPlaces(string $fact, string $what): array 738add1155SRico Sonntag { 748add1155SRico Sonntag $rows = []; 758add1155SRico Sonntag 768add1155SRico Sonntag if ($what === 'INDI') { 777aab89b4SGreg Roach $rows = DB::table('individuals') 787aab89b4SGreg Roach ->select(['i_gedcom as tree']) 797aab89b4SGreg Roach ->where('i_file', '=', $this->tree->id()) 807aab89b4SGreg Roach ->where('i_gedcom', 'LIKE', "%\n2 PLAC %") 817aab89b4SGreg Roach ->get() 827aab89b4SGreg Roach ->all(); 838add1155SRico Sonntag } elseif ($what === 'FAM') { 847aab89b4SGreg Roach $rows = DB::table('families')->select(['f_gedcom as tree']) 857aab89b4SGreg Roach ->where('f_file', '=', $this->tree->id()) 867aab89b4SGreg Roach ->where('f_gedcom', 'LIKE', "%\n2 PLAC %") 877aab89b4SGreg Roach ->get() 887aab89b4SGreg Roach ->all(); 898add1155SRico Sonntag } 908add1155SRico Sonntag 918add1155SRico Sonntag $placelist = []; 928add1155SRico Sonntag 938add1155SRico Sonntag foreach ($rows as $row) { 94ef475b14SGreg Roach if (preg_match('/\n1 ' . $fact . '(?:\n[2-9].*)*\n2 PLAC (.+)/', $row->tree, $match) === 1) { 958add1155SRico Sonntag $place = $match[1]; 968add1155SRico Sonntag 977aab89b4SGreg Roach $placelist[$place] = ($placelist[$place] ?? 0) + 1; 988add1155SRico Sonntag } 998add1155SRico Sonntag } 1008add1155SRico Sonntag 1018add1155SRico Sonntag return $placelist; 1028add1155SRico Sonntag } 1038add1155SRico Sonntag 1048add1155SRico Sonntag /** 1058add1155SRico Sonntag * Get the top 10 places list. 1068add1155SRico Sonntag * 107f78da678SGreg Roach * @param array<int> $places 1088add1155SRico Sonntag * 109f78da678SGreg Roach * @return array<array<string,int|Place>> 1108add1155SRico Sonntag */ 1118add1155SRico Sonntag private function getTop10Places(array $places): array 1128add1155SRico Sonntag { 1138add1155SRico Sonntag $top10 = []; 1148add1155SRico Sonntag $i = 0; 1158add1155SRico Sonntag 1168add1155SRico Sonntag arsort($places); 1178add1155SRico Sonntag 1188add1155SRico Sonntag foreach ($places as $place => $count) { 119ac701fbdSGreg Roach $tmp = new Place((string) $place, $this->tree); 1208add1155SRico Sonntag $top10[] = [ 1218add1155SRico Sonntag 'place' => $tmp, 1228add1155SRico Sonntag 'count' => $count, 1238add1155SRico Sonntag ]; 1248add1155SRico Sonntag 1258add1155SRico Sonntag ++$i; 1268add1155SRico Sonntag 1278add1155SRico Sonntag if ($i === 10) { 1288add1155SRico Sonntag break; 1298add1155SRico Sonntag } 1308add1155SRico Sonntag } 1318add1155SRico Sonntag 1328add1155SRico Sonntag return $top10; 1338add1155SRico Sonntag } 1348add1155SRico Sonntag 1358add1155SRico Sonntag /** 1368add1155SRico Sonntag * Renders the top 10 places list. 1378add1155SRico Sonntag * 138ac701fbdSGreg Roach * @param array<int|string,int> $places 1398add1155SRico Sonntag * 1408add1155SRico Sonntag * @return string 1418add1155SRico Sonntag */ 1428add1155SRico Sonntag private function renderTop10(array $places): string 1438add1155SRico Sonntag { 1448add1155SRico Sonntag $top10Records = $this->getTop10Places($places); 1458add1155SRico Sonntag 1468add1155SRico Sonntag return view( 1478add1155SRico Sonntag 'statistics/other/top10-list', 1488add1155SRico Sonntag [ 1498add1155SRico Sonntag 'records' => $top10Records, 1508add1155SRico Sonntag ] 1518add1155SRico Sonntag ); 1528add1155SRico Sonntag } 1538add1155SRico Sonntag 1548add1155SRico Sonntag /** 1558add1155SRico Sonntag * A list of common birth places. 1568add1155SRico Sonntag * 1578add1155SRico Sonntag * @return string 1588add1155SRico Sonntag */ 1598add1155SRico Sonntag public function commonBirthPlacesList(): string 1608add1155SRico Sonntag { 1618add1155SRico Sonntag $places = $this->queryFactPlaces('BIRT', 'INDI'); 1628add1155SRico Sonntag return $this->renderTop10($places); 1638add1155SRico Sonntag } 1648add1155SRico Sonntag 1658add1155SRico Sonntag /** 1668add1155SRico Sonntag * A list of common death places. 1678add1155SRico Sonntag * 1688add1155SRico Sonntag * @return string 1698add1155SRico Sonntag */ 1708add1155SRico Sonntag public function commonDeathPlacesList(): string 1718add1155SRico Sonntag { 1728add1155SRico Sonntag $places = $this->queryFactPlaces('DEAT', 'INDI'); 1738add1155SRico Sonntag return $this->renderTop10($places); 1748add1155SRico Sonntag } 1758add1155SRico Sonntag 1768add1155SRico Sonntag /** 1778add1155SRico Sonntag * A list of common marriage places. 1788add1155SRico Sonntag * 1798add1155SRico Sonntag * @return string 1808add1155SRico Sonntag */ 1818add1155SRico Sonntag public function commonMarriagePlacesList(): string 1828add1155SRico Sonntag { 1838add1155SRico Sonntag $places = $this->queryFactPlaces('MARR', 'FAM'); 1848add1155SRico Sonntag return $this->renderTop10($places); 1858add1155SRico Sonntag } 1868add1155SRico Sonntag 1878add1155SRico Sonntag /** 1888add1155SRico Sonntag * A list of common countries. 1898add1155SRico Sonntag * 1908add1155SRico Sonntag * @return string 1918add1155SRico Sonntag */ 1928add1155SRico Sonntag public function commonCountriesList(): string 1938add1155SRico Sonntag { 194a020b8bdSGreg Roach $countries = DB::table('places') 195a020b8bdSGreg Roach ->join('placelinks', static function (JoinClause $join): void { 196a020b8bdSGreg Roach $join 197a020b8bdSGreg Roach ->on('pl_file', '=', 'p_file') 198a020b8bdSGreg Roach ->on('pl_p_id', '=', 'p_id'); 199a020b8bdSGreg Roach }) 200a020b8bdSGreg Roach ->where('p_file', '=', $this->tree->id()) 201a020b8bdSGreg Roach ->where('p_parent_id', '=', 0) 202a020b8bdSGreg Roach ->groupBy(['p_place']) 203a020b8bdSGreg Roach ->orderByDesc(new Expression('COUNT(*)')) 204a020b8bdSGreg Roach ->orderBy('p_place') 205*90da5d67SGreg Roach ->pluck(new Expression('COUNT(*) AS total'), 'p_place') 206a020b8bdSGreg Roach ->map(static fn (string $col): int => (int) $col) 207a020b8bdSGreg Roach ->all(); 2088add1155SRico Sonntag 209320f6a24SGreg Roach if ($countries === []) { 210c908635bSGreg Roach return I18N::translate('This information is not available.'); 2118add1155SRico Sonntag } 2128add1155SRico Sonntag 2138add1155SRico Sonntag $top10 = []; 2148add1155SRico Sonntag $i = 1; 2158add1155SRico Sonntag 2168add1155SRico Sonntag // Get the country names for each language 2178add1155SRico Sonntag $country_names = []; 21890a2f718SGreg Roach $old_language = I18N::languageTag(); 21990a2f718SGreg Roach 2208add1155SRico Sonntag foreach (I18N::activeLocales() as $locale) { 2218add1155SRico Sonntag I18N::init($locale->languageTag()); 22293ccd686SRico Sonntag $all_countries = $this->country_service->getAllCountries(); 2238add1155SRico Sonntag foreach ($all_countries as $country_code => $country_name) { 2248add1155SRico Sonntag $country_names[$country_name] = $country_code; 2258add1155SRico Sonntag } 2268add1155SRico Sonntag } 2278add1155SRico Sonntag 22890a2f718SGreg Roach I18N::init($old_language); 2298add1155SRico Sonntag 2308add1155SRico Sonntag $all_db_countries = []; 231a020b8bdSGreg Roach 232a020b8bdSGreg Roach foreach ($countries as $country => $count) { 2336ccdf4f0SGreg Roach if (array_key_exists($country, $country_names)) { 2348add1155SRico Sonntag if (isset($all_db_countries[$country_names[$country]][$country])) { 235a020b8bdSGreg Roach $all_db_countries[$country_names[$country]][$country] += (int) $count; 2368add1155SRico Sonntag } else { 237a020b8bdSGreg Roach $all_db_countries[$country_names[$country]][$country] = (int) $count; 2388add1155SRico Sonntag } 2398add1155SRico Sonntag } 2408add1155SRico Sonntag } 2418add1155SRico Sonntag 2428add1155SRico Sonntag // get all the user’s countries names 24393ccd686SRico Sonntag $all_countries = $this->country_service->getAllCountries(); 2448add1155SRico Sonntag 2458add1155SRico Sonntag foreach ($all_db_countries as $country_code => $country) { 2468add1155SRico Sonntag foreach ($country as $country_name => $tot) { 2478add1155SRico Sonntag $tmp = new Place($country_name, $this->tree); 2488add1155SRico Sonntag 2498add1155SRico Sonntag $top10[] = [ 2508add1155SRico Sonntag 'place' => $tmp, 2518add1155SRico Sonntag 'count' => $tot, 2528add1155SRico Sonntag 'name' => $all_countries[$country_code], 2538add1155SRico Sonntag ]; 2548add1155SRico Sonntag } 2558add1155SRico Sonntag 2568add1155SRico Sonntag if ($i++ === 10) { 2578add1155SRico Sonntag break; 2588add1155SRico Sonntag } 2598add1155SRico Sonntag } 2608add1155SRico Sonntag 2618add1155SRico Sonntag return view( 2628add1155SRico Sonntag 'statistics/other/top10-list', 2638add1155SRico Sonntag [ 2648add1155SRico Sonntag 'records' => $top10, 2658add1155SRico Sonntag ] 2668add1155SRico Sonntag ); 2678add1155SRico Sonntag } 2688add1155SRico Sonntag 2698add1155SRico Sonntag /** 2708add1155SRico Sonntag * Count total places. 2718add1155SRico Sonntag * 2728add1155SRico Sonntag * @return int 2738add1155SRico Sonntag */ 2748add1155SRico Sonntag private function totalPlacesQuery(): int 2758add1155SRico Sonntag { 2768add1155SRico Sonntag return DB::table('places') 2778add1155SRico Sonntag ->where('p_file', '=', $this->tree->id()) 2788add1155SRico Sonntag ->count(); 2798add1155SRico Sonntag } 2808add1155SRico Sonntag 2818add1155SRico Sonntag /** 2828add1155SRico Sonntag * Count total places. 2838add1155SRico Sonntag * 2848add1155SRico Sonntag * @return string 2858add1155SRico Sonntag */ 2868add1155SRico Sonntag public function totalPlaces(): string 2878add1155SRico Sonntag { 2888add1155SRico Sonntag return I18N::number($this->totalPlacesQuery()); 2898add1155SRico Sonntag } 2908add1155SRico Sonntag 2918add1155SRico Sonntag /** 2928add1155SRico Sonntag * Create a chart showing where events occurred. 2938add1155SRico Sonntag * 2948add1155SRico Sonntag * @param string $chart_shows 2958add1155SRico Sonntag * @param string $chart_type 2968add1155SRico Sonntag * @param string $surname 2978add1155SRico Sonntag * 2988add1155SRico Sonntag * @return string 2998add1155SRico Sonntag */ 3008add1155SRico Sonntag public function chartDistribution( 3018add1155SRico Sonntag string $chart_shows = 'world', 3028add1155SRico Sonntag string $chart_type = '', 3038add1155SRico Sonntag string $surname = '' 3048add1155SRico Sonntag ): string { 305a020b8bdSGreg Roach return (new ChartDistribution($this->tree, $this->country_service, $this->individual_repository)) 30688de55fdSRico Sonntag ->chartDistribution($chart_shows, $chart_type, $surname); 3078add1155SRico Sonntag } 3088add1155SRico Sonntag} 309