18add1155SRico Sonntag<?php 23976b470SGreg Roach 38add1155SRico Sonntag/** 48add1155SRico Sonntag * webtrees: online genealogy 589f7189bSGreg Roach * Copyright (C) 2021 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 22*c8db8a43SGreg Roachuse Fisharebest\Webtrees\Family; 238add1155SRico Sonntaguse Fisharebest\Webtrees\Gedcom; 248add1155SRico Sonntaguse Fisharebest\Webtrees\I18N; 25*c8db8a43SGreg Roachuse Fisharebest\Webtrees\Individual; 26*c8db8a43SGreg Roachuse Fisharebest\Webtrees\Location; 278add1155SRico Sonntaguse Fisharebest\Webtrees\Place; 288add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartDistribution; 298add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\Interfaces\PlaceRepositoryInterface; 3093ccd686SRico Sonntaguse Fisharebest\Webtrees\Statistics\Service\CountryService; 318add1155SRico Sonntaguse Fisharebest\Webtrees\Tree; 328add1155SRico Sonntaguse Illuminate\Database\Capsule\Manager as DB; 338add1155SRico Sonntaguse Illuminate\Database\Query\JoinClause; 346ccdf4f0SGreg Roachuse stdClass; 358add1155SRico Sonntag 3671378461SGreg Roachuse function array_key_exists; 3771378461SGreg Roach 388add1155SRico Sonntag/** 398add1155SRico Sonntag * A repository providing methods for place related statistics. 408add1155SRico Sonntag */ 418add1155SRico Sonntagclass PlaceRepository implements PlaceRepositoryInterface 428add1155SRico Sonntag{ 438add1155SRico Sonntag /** 448add1155SRico Sonntag * @var Tree 458add1155SRico Sonntag */ 468add1155SRico Sonntag private $tree; 478add1155SRico Sonntag 488add1155SRico Sonntag /** 4993ccd686SRico Sonntag * @var CountryService 508add1155SRico Sonntag */ 5193ccd686SRico Sonntag private $country_service; 528add1155SRico Sonntag 538add1155SRico Sonntag /** 548add1155SRico Sonntag * BirthPlaces constructor. 558add1155SRico Sonntag * 568add1155SRico Sonntag * @param Tree $tree 578add1155SRico Sonntag */ 588add1155SRico Sonntag public function __construct(Tree $tree) 598add1155SRico Sonntag { 608add1155SRico Sonntag $this->tree = $tree; 6193ccd686SRico Sonntag $this->country_service = new CountryService(); 628add1155SRico Sonntag } 638add1155SRico Sonntag 648add1155SRico Sonntag /** 658add1155SRico Sonntag * Places 668add1155SRico Sonntag * 678add1155SRico Sonntag * @param string $fact 688add1155SRico Sonntag * @param string $what 698add1155SRico Sonntag * @param bool $country 708add1155SRico Sonntag * 718add1155SRico Sonntag * @return int[] 728add1155SRico Sonntag */ 738add1155SRico Sonntag private function queryFactPlaces(string $fact, string $what = 'ALL', bool $country = false): array 748add1155SRico Sonntag { 758add1155SRico Sonntag $rows = []; 768add1155SRico Sonntag 778add1155SRico Sonntag if ($what === 'INDI') { 78d72b284aSGreg Roach $rows = DB::table('individuals')->select(['i_gedcom as tree'])->where( 798add1155SRico Sonntag 'i_file', 808add1155SRico Sonntag '=', 818add1155SRico Sonntag $this->tree->id() 828add1155SRico Sonntag )->where( 838add1155SRico Sonntag 'i_gedcom', 848add1155SRico Sonntag 'LIKE', 858add1155SRico Sonntag "%\n2 PLAC %" 868add1155SRico Sonntag )->get()->all(); 878add1155SRico Sonntag } elseif ($what === 'FAM') { 88d72b284aSGreg Roach $rows = DB::table('families')->select(['f_gedcom as tree'])->where( 898add1155SRico Sonntag 'f_file', 908add1155SRico Sonntag '=', 918add1155SRico Sonntag $this->tree->id() 928add1155SRico Sonntag )->where( 938add1155SRico Sonntag 'f_gedcom', 948add1155SRico Sonntag 'LIKE', 958add1155SRico Sonntag "%\n2 PLAC %" 968add1155SRico Sonntag )->get()->all(); 978add1155SRico Sonntag } 988add1155SRico Sonntag 998add1155SRico Sonntag $placelist = []; 1008add1155SRico Sonntag 1018add1155SRico Sonntag foreach ($rows as $row) { 102d72b284aSGreg Roach if (preg_match('/\n1 ' . $fact . '(?:\n[2-9].*)*\n2 PLAC (.+)/', $row->tree, $match)) { 1038add1155SRico Sonntag if ($country) { 1048add1155SRico Sonntag $tmp = explode(Gedcom::PLACE_SEPARATOR, $match[1]); 1058add1155SRico Sonntag $place = end($tmp); 1068add1155SRico Sonntag } else { 1078add1155SRico Sonntag $place = $match[1]; 1088add1155SRico Sonntag } 1098add1155SRico Sonntag 1108add1155SRico Sonntag if (isset($placelist[$place])) { 1118add1155SRico Sonntag ++$placelist[$place]; 1128add1155SRico Sonntag } else { 1138add1155SRico Sonntag $placelist[$place] = 1; 1148add1155SRico Sonntag } 1158add1155SRico Sonntag } 1168add1155SRico Sonntag } 1178add1155SRico Sonntag 1188add1155SRico Sonntag return $placelist; 1198add1155SRico Sonntag } 1208add1155SRico Sonntag 1218add1155SRico Sonntag /** 1228add1155SRico Sonntag * Query places. 1238add1155SRico Sonntag * 1248add1155SRico Sonntag * @param string $what 1258add1155SRico Sonntag * @param string $fact 1268add1155SRico Sonntag * @param int $parent 1278add1155SRico Sonntag * @param bool $country 1288add1155SRico Sonntag * 129870ec5a3SGreg Roach * @return array<int|stdClass> 1308add1155SRico Sonntag */ 1318add1155SRico Sonntag public function statsPlaces(string $what = 'ALL', string $fact = '', int $parent = 0, bool $country = false): array 1328add1155SRico Sonntag { 1338add1155SRico Sonntag if ($fact) { 1348add1155SRico Sonntag return $this->queryFactPlaces($fact, $what, $country); 1358add1155SRico Sonntag } 1368add1155SRico Sonntag 1378add1155SRico Sonntag $query = DB::table('places') 1380b5fd0a6SGreg Roach ->join('placelinks', static function (JoinClause $join): void { 1398add1155SRico Sonntag $join->on('pl_file', '=', 'p_file') 1408add1155SRico Sonntag ->on('pl_p_id', '=', 'p_id'); 1418add1155SRico Sonntag }) 1428add1155SRico Sonntag ->where('p_file', '=', $this->tree->id()); 1438add1155SRico Sonntag 1448add1155SRico Sonntag if ($parent > 0) { 1458add1155SRico Sonntag // Used by placehierarchy map modules 1468add1155SRico Sonntag $query->select(['p_place AS place']) 1478add1155SRico Sonntag ->selectRaw('COUNT(*) AS tot') 1488add1155SRico Sonntag ->where('p_id', '=', $parent) 1498add1155SRico Sonntag ->groupBy(['place']); 1508add1155SRico Sonntag } else { 1518add1155SRico Sonntag $query->select(['p_place AS country']) 1528add1155SRico Sonntag ->selectRaw('COUNT(*) AS tot') 1538add1155SRico Sonntag ->where('p_parent_id', '=', 0) 1548add1155SRico Sonntag ->groupBy(['country']) 1558add1155SRico Sonntag ->orderByDesc('tot') 1568add1155SRico Sonntag ->orderBy('country'); 1578add1155SRico Sonntag } 1588add1155SRico Sonntag 159*c8db8a43SGreg Roach if ($what === Individual::RECORD_TYPE) { 1600b5fd0a6SGreg Roach $query->join('individuals', static function (JoinClause $join): void { 1618add1155SRico Sonntag $join->on('pl_file', '=', 'i_file') 1628add1155SRico Sonntag ->on('pl_gid', '=', 'i_id'); 1638add1155SRico Sonntag }); 164*c8db8a43SGreg Roach } elseif ($what === Family::RECORD_TYPE) { 1650b5fd0a6SGreg Roach $query->join('families', static function (JoinClause $join): void { 1668add1155SRico Sonntag $join->on('pl_file', '=', 'f_file') 1678add1155SRico Sonntag ->on('pl_gid', '=', 'f_id'); 1688add1155SRico Sonntag }); 169*c8db8a43SGreg Roach } elseif ($what === Location::RECORD_TYPE) { 170*c8db8a43SGreg Roach $query->join('other', static function (JoinClause $join): void { 171*c8db8a43SGreg Roach $join->on('pl_file', '=', 'o_file') 172*c8db8a43SGreg Roach ->on('pl_gid', '=', 'o_id'); 173*c8db8a43SGreg Roach }) 174*c8db8a43SGreg Roach ->where('o_type', '=', Location::RECORD_TYPE); 1758add1155SRico Sonntag } 1768add1155SRico Sonntag 177936d28c8SRico Sonntag return $query 178936d28c8SRico Sonntag ->get() 179936d28c8SRico Sonntag ->map(static function (stdClass $entry) { 180936d28c8SRico Sonntag // Map total value to integer 181936d28c8SRico Sonntag $entry->tot = (int) $entry->tot; 182*c8db8a43SGreg Roach 183936d28c8SRico Sonntag return $entry; 184936d28c8SRico Sonntag }) 185936d28c8SRico Sonntag ->all(); 1868add1155SRico Sonntag } 1878add1155SRico Sonntag 1888add1155SRico Sonntag /** 1898add1155SRico Sonntag * Get the top 10 places list. 1908add1155SRico Sonntag * 19191c84b80SGreg Roach * @param array<string,int> $places 1928add1155SRico Sonntag * 19391c84b80SGreg Roach * @return array<array<string,mixed>> 1948add1155SRico Sonntag */ 1958add1155SRico Sonntag private function getTop10Places(array $places): array 1968add1155SRico Sonntag { 1978add1155SRico Sonntag $top10 = []; 1988add1155SRico Sonntag $i = 0; 1998add1155SRico Sonntag 2008add1155SRico Sonntag arsort($places); 2018add1155SRico Sonntag 2028add1155SRico Sonntag foreach ($places as $place => $count) { 2038add1155SRico Sonntag $tmp = new Place($place, $this->tree); 2048add1155SRico Sonntag $top10[] = [ 2058add1155SRico Sonntag 'place' => $tmp, 2068add1155SRico Sonntag 'count' => $count, 2078add1155SRico Sonntag ]; 2088add1155SRico Sonntag 2098add1155SRico Sonntag ++$i; 2108add1155SRico Sonntag 2118add1155SRico Sonntag if ($i === 10) { 2128add1155SRico Sonntag break; 2138add1155SRico Sonntag } 2148add1155SRico Sonntag } 2158add1155SRico Sonntag 2168add1155SRico Sonntag return $top10; 2178add1155SRico Sonntag } 2188add1155SRico Sonntag 2198add1155SRico Sonntag /** 2208add1155SRico Sonntag * Renders the top 10 places list. 2218add1155SRico Sonntag * 22291c84b80SGreg Roach * @param array<string,int> $places 2238add1155SRico Sonntag * 2248add1155SRico Sonntag * @return string 2258add1155SRico Sonntag */ 2268add1155SRico Sonntag private function renderTop10(array $places): string 2278add1155SRico Sonntag { 2288add1155SRico Sonntag $top10Records = $this->getTop10Places($places); 2298add1155SRico Sonntag 2308add1155SRico Sonntag return view( 2318add1155SRico Sonntag 'statistics/other/top10-list', 2328add1155SRico Sonntag [ 2338add1155SRico Sonntag 'records' => $top10Records, 2348add1155SRico Sonntag ] 2358add1155SRico Sonntag ); 2368add1155SRico Sonntag } 2378add1155SRico Sonntag 2388add1155SRico Sonntag /** 2398add1155SRico Sonntag * A list of common birth places. 2408add1155SRico Sonntag * 2418add1155SRico Sonntag * @return string 2428add1155SRico Sonntag */ 2438add1155SRico Sonntag public function commonBirthPlacesList(): string 2448add1155SRico Sonntag { 2458add1155SRico Sonntag $places = $this->queryFactPlaces('BIRT', 'INDI'); 2468add1155SRico Sonntag return $this->renderTop10($places); 2478add1155SRico Sonntag } 2488add1155SRico Sonntag 2498add1155SRico Sonntag /** 2508add1155SRico Sonntag * A list of common death places. 2518add1155SRico Sonntag * 2528add1155SRico Sonntag * @return string 2538add1155SRico Sonntag */ 2548add1155SRico Sonntag public function commonDeathPlacesList(): string 2558add1155SRico Sonntag { 2568add1155SRico Sonntag $places = $this->queryFactPlaces('DEAT', 'INDI'); 2578add1155SRico Sonntag return $this->renderTop10($places); 2588add1155SRico Sonntag } 2598add1155SRico Sonntag 2608add1155SRico Sonntag /** 2618add1155SRico Sonntag * A list of common marriage places. 2628add1155SRico Sonntag * 2638add1155SRico Sonntag * @return string 2648add1155SRico Sonntag */ 2658add1155SRico Sonntag public function commonMarriagePlacesList(): string 2668add1155SRico Sonntag { 2678add1155SRico Sonntag $places = $this->queryFactPlaces('MARR', 'FAM'); 2688add1155SRico Sonntag return $this->renderTop10($places); 2698add1155SRico Sonntag } 2708add1155SRico Sonntag 2718add1155SRico Sonntag /** 2728add1155SRico Sonntag * A list of common countries. 2738add1155SRico Sonntag * 2748add1155SRico Sonntag * @return string 2758add1155SRico Sonntag */ 2768add1155SRico Sonntag public function commonCountriesList(): string 2778add1155SRico Sonntag { 2788add1155SRico Sonntag $countries = $this->statsPlaces(); 2798add1155SRico Sonntag 280320f6a24SGreg Roach if ($countries === []) { 2818add1155SRico Sonntag return ''; 2828add1155SRico Sonntag } 2838add1155SRico Sonntag 2848add1155SRico Sonntag $top10 = []; 2858add1155SRico Sonntag $i = 1; 2868add1155SRico Sonntag 2878add1155SRico Sonntag // Get the country names for each language 2888add1155SRico Sonntag $country_names = []; 28990a2f718SGreg Roach $old_language = I18N::languageTag(); 29090a2f718SGreg Roach 2918add1155SRico Sonntag foreach (I18N::activeLocales() as $locale) { 2928add1155SRico Sonntag I18N::init($locale->languageTag()); 29393ccd686SRico Sonntag $all_countries = $this->country_service->getAllCountries(); 2948add1155SRico Sonntag foreach ($all_countries as $country_code => $country_name) { 2958add1155SRico Sonntag $country_names[$country_name] = $country_code; 2968add1155SRico Sonntag } 2978add1155SRico Sonntag } 2988add1155SRico Sonntag 29990a2f718SGreg Roach I18N::init($old_language); 3008add1155SRico Sonntag 3018add1155SRico Sonntag $all_db_countries = []; 3028add1155SRico Sonntag foreach ($countries as $place) { 3038add1155SRico Sonntag $country = trim($place->country); 3046ccdf4f0SGreg Roach if (array_key_exists($country, $country_names)) { 3058add1155SRico Sonntag if (isset($all_db_countries[$country_names[$country]][$country])) { 3068add1155SRico Sonntag $all_db_countries[$country_names[$country]][$country] += (int) $place->tot; 3078add1155SRico Sonntag } else { 3088add1155SRico Sonntag $all_db_countries[$country_names[$country]][$country] = (int) $place->tot; 3098add1155SRico Sonntag } 3108add1155SRico Sonntag } 3118add1155SRico Sonntag } 3128add1155SRico Sonntag 3138add1155SRico Sonntag // get all the user’s countries names 31493ccd686SRico Sonntag $all_countries = $this->country_service->getAllCountries(); 3158add1155SRico Sonntag 3168add1155SRico Sonntag foreach ($all_db_countries as $country_code => $country) { 3178add1155SRico Sonntag foreach ($country as $country_name => $tot) { 3188add1155SRico Sonntag $tmp = new Place($country_name, $this->tree); 3198add1155SRico Sonntag 3208add1155SRico Sonntag $top10[] = [ 3218add1155SRico Sonntag 'place' => $tmp, 3228add1155SRico Sonntag 'count' => $tot, 3238add1155SRico Sonntag 'name' => $all_countries[$country_code], 3248add1155SRico Sonntag ]; 3258add1155SRico Sonntag } 3268add1155SRico Sonntag 3278add1155SRico Sonntag if ($i++ === 10) { 3288add1155SRico Sonntag break; 3298add1155SRico Sonntag } 3308add1155SRico Sonntag } 3318add1155SRico Sonntag 3328add1155SRico Sonntag return view( 3338add1155SRico Sonntag 'statistics/other/top10-list', 3348add1155SRico Sonntag [ 3358add1155SRico Sonntag 'records' => $top10, 3368add1155SRico Sonntag ] 3378add1155SRico Sonntag ); 3388add1155SRico Sonntag } 3398add1155SRico Sonntag 3408add1155SRico Sonntag /** 3418add1155SRico Sonntag * Count total places. 3428add1155SRico Sonntag * 3438add1155SRico Sonntag * @return int 3448add1155SRico Sonntag */ 3458add1155SRico Sonntag private function totalPlacesQuery(): int 3468add1155SRico Sonntag { 3478add1155SRico Sonntag return DB::table('places') 3488add1155SRico Sonntag ->where('p_file', '=', $this->tree->id()) 3498add1155SRico Sonntag ->count(); 3508add1155SRico Sonntag } 3518add1155SRico Sonntag 3528add1155SRico Sonntag /** 3538add1155SRico Sonntag * Count total places. 3548add1155SRico Sonntag * 3558add1155SRico Sonntag * @return string 3568add1155SRico Sonntag */ 3578add1155SRico Sonntag public function totalPlaces(): string 3588add1155SRico Sonntag { 3598add1155SRico Sonntag return I18N::number($this->totalPlacesQuery()); 3608add1155SRico Sonntag } 3618add1155SRico Sonntag 3628add1155SRico Sonntag /** 3638add1155SRico Sonntag * Create a chart showing where events occurred. 3648add1155SRico Sonntag * 3658add1155SRico Sonntag * @param string $chart_shows 3668add1155SRico Sonntag * @param string $chart_type 3678add1155SRico Sonntag * @param string $surname 3688add1155SRico Sonntag * 3698add1155SRico Sonntag * @return string 3708add1155SRico Sonntag */ 3718add1155SRico Sonntag public function chartDistribution( 3728add1155SRico Sonntag string $chart_shows = 'world', 3738add1155SRico Sonntag string $chart_type = '', 3748add1155SRico Sonntag string $surname = '' 3758add1155SRico Sonntag ): string { 3768add1155SRico Sonntag return (new ChartDistribution($this->tree)) 37788de55fdSRico Sonntag ->chartDistribution($chart_shows, $chart_type, $surname); 3788add1155SRico Sonntag } 3798add1155SRico Sonntag} 380