1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2019 webtrees development team 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16declare(strict_types=1); 17 18namespace Fisharebest\Webtrees\Statistics\Repository; 19 20use Fisharebest\Webtrees\Gedcom; 21use Fisharebest\Webtrees\I18N; 22use Fisharebest\Webtrees\Place; 23use Fisharebest\Webtrees\Statistics\Google\ChartDistribution; 24use Fisharebest\Webtrees\Statistics\Repository\Interfaces\PlaceRepositoryInterface; 25use Fisharebest\Webtrees\Statistics\Service\CountryService; 26use Fisharebest\Webtrees\Tree; 27use Illuminate\Database\Capsule\Manager as DB; 28use Illuminate\Database\Query\JoinClause; 29 30/** 31 * A repository providing methods for place related statistics. 32 */ 33class PlaceRepository implements PlaceRepositoryInterface 34{ 35 /** 36 * @var Tree 37 */ 38 private $tree; 39 40 /** 41 * @var CountryService 42 */ 43 private $country_service; 44 45 /** 46 * BirthPlaces constructor. 47 * 48 * @param Tree $tree 49 */ 50 public function __construct(Tree $tree) 51 { 52 $this->tree = $tree; 53 $this->country_service = new CountryService(); 54 } 55 56 /** 57 * Places 58 * 59 * @param string $fact 60 * @param string $what 61 * @param bool $country 62 * 63 * @return int[] 64 */ 65 private function queryFactPlaces(string $fact, string $what = 'ALL', bool $country = false): array 66 { 67 $rows = []; 68 69 if ($what === 'INDI') { 70 $rows = DB::table('individuals')->select(['i_gedcom as ged'])->where( 71 'i_file', 72 '=', 73 $this->tree->id() 74 )->where( 75 'i_gedcom', 76 'LIKE', 77 "%\n2 PLAC %" 78 )->get()->all(); 79 } elseif ($what === 'FAM') { 80 $rows = DB::table('families')->select(['f_gedcom as ged'])->where( 81 'f_file', 82 '=', 83 $this->tree->id() 84 )->where( 85 'f_gedcom', 86 'LIKE', 87 "%\n2 PLAC %" 88 )->get()->all(); 89 } 90 91 $placelist = []; 92 93 foreach ($rows as $row) { 94 if (preg_match('/\n1 ' . $fact . '(?:\n[2-9].*)*\n2 PLAC (.+)/', $row->ged, $match)) { 95 if ($country) { 96 $tmp = explode(Gedcom::PLACE_SEPARATOR, $match[1]); 97 $place = end($tmp); 98 } else { 99 $place = $match[1]; 100 } 101 102 if (isset($placelist[$place])) { 103 ++$placelist[$place]; 104 } else { 105 $placelist[$place] = 1; 106 } 107 } 108 } 109 110 return $placelist; 111 } 112 113 /** 114 * Query places. 115 * 116 * @param string $what 117 * @param string $fact 118 * @param int $parent 119 * @param bool $country 120 * 121 * @return int[]|\stdClass[] 122 */ 123 public function statsPlaces(string $what = 'ALL', string $fact = '', int $parent = 0, bool $country = false): array 124 { 125 if ($fact) { 126 return $this->queryFactPlaces($fact, $what, $country); 127 } 128 129 $query = DB::table('places') 130 ->join('placelinks', static function (JoinClause $join): void { 131 $join->on('pl_file', '=', 'p_file') 132 ->on('pl_p_id', '=', 'p_id'); 133 }) 134 ->where('p_file', '=', $this->tree->id()); 135 136 if ($parent > 0) { 137 // Used by placehierarchy map modules 138 $query->select(['p_place AS place']) 139 ->selectRaw('COUNT(*) AS tot') 140 ->where('p_id', '=', $parent) 141 ->groupBy(['place']); 142 } else { 143 $query->select(['p_place AS country']) 144 ->selectRaw('COUNT(*) AS tot') 145 ->where('p_parent_id', '=', 0) 146 ->groupBy(['country']) 147 ->orderByDesc('tot') 148 ->orderBy('country'); 149 } 150 151 if ($what === 'INDI') { 152 $query->join('individuals', static function (JoinClause $join): void { 153 $join->on('pl_file', '=', 'i_file') 154 ->on('pl_gid', '=', 'i_id'); 155 }); 156 } elseif ($what === 'FAM') { 157 $query->join('families', static function (JoinClause $join): void { 158 $join->on('pl_file', '=', 'f_file') 159 ->on('pl_gid', '=', 'f_id'); 160 }); 161 } 162 163 return $query->get()->all(); 164 } 165 166 /** 167 * Get the top 10 places list. 168 * 169 * @param array $places 170 * 171 * @return array 172 */ 173 private function getTop10Places(array $places): array 174 { 175 $top10 = []; 176 $i = 0; 177 178 arsort($places); 179 180 foreach ($places as $place => $count) { 181 $tmp = new Place($place, $this->tree); 182 $top10[] = [ 183 'place' => $tmp, 184 'count' => $count, 185 ]; 186 187 ++$i; 188 189 if ($i === 10) { 190 break; 191 } 192 } 193 194 return $top10; 195 } 196 197 /** 198 * Renders the top 10 places list. 199 * 200 * @param array $places 201 * 202 * @return string 203 */ 204 private function renderTop10(array $places): string 205 { 206 $top10Records = $this->getTop10Places($places); 207 208 return view( 209 'statistics/other/top10-list', 210 [ 211 'records' => $top10Records, 212 ] 213 ); 214 } 215 216 /** 217 * A list of common birth places. 218 * 219 * @return string 220 */ 221 public function commonBirthPlacesList(): string 222 { 223 $places = $this->queryFactPlaces('BIRT', 'INDI'); 224 return $this->renderTop10($places); 225 } 226 227 /** 228 * A list of common death places. 229 * 230 * @return string 231 */ 232 public function commonDeathPlacesList(): string 233 { 234 $places = $this->queryFactPlaces('DEAT', 'INDI'); 235 return $this->renderTop10($places); 236 } 237 238 /** 239 * A list of common marriage places. 240 * 241 * @return string 242 */ 243 public function commonMarriagePlacesList(): string 244 { 245 $places = $this->queryFactPlaces('MARR', 'FAM'); 246 return $this->renderTop10($places); 247 } 248 249 /** 250 * A list of common countries. 251 * 252 * @return string 253 */ 254 public function commonCountriesList(): string 255 { 256 $countries = $this->statsPlaces(); 257 258 if (empty($countries)) { 259 return ''; 260 } 261 262 $top10 = []; 263 $i = 1; 264 265 // Get the country names for each language 266 $country_names = []; 267 foreach (I18N::activeLocales() as $locale) { 268 I18N::init($locale->languageTag()); 269 $all_countries = $this->country_service->getAllCountries(); 270 foreach ($all_countries as $country_code => $country_name) { 271 $country_names[$country_name] = $country_code; 272 } 273 } 274 275 I18N::init(WT_LOCALE); 276 277 $all_db_countries = []; 278 foreach ($countries as $place) { 279 $country = trim($place->country); 280 if (\array_key_exists($country, $country_names)) { 281 if (isset($all_db_countries[$country_names[$country]][$country])) { 282 $all_db_countries[$country_names[$country]][$country] += (int) $place->tot; 283 } else { 284 $all_db_countries[$country_names[$country]][$country] = (int) $place->tot; 285 } 286 } 287 } 288 289 // get all the user’s countries names 290 $all_countries = $this->country_service->getAllCountries(); 291 292 foreach ($all_db_countries as $country_code => $country) { 293 foreach ($country as $country_name => $tot) { 294 $tmp = new Place($country_name, $this->tree); 295 296 $top10[] = [ 297 'place' => $tmp, 298 'count' => $tot, 299 'name' => $all_countries[$country_code], 300 ]; 301 } 302 303 if ($i++ === 10) { 304 break; 305 } 306 } 307 308 return view( 309 'statistics/other/top10-list', 310 [ 311 'records' => $top10, 312 ] 313 ); 314 } 315 316 /** 317 * Count total places. 318 * 319 * @return int 320 */ 321 private function totalPlacesQuery(): int 322 { 323 return DB::table('places') 324 ->where('p_file', '=', $this->tree->id()) 325 ->count(); 326 } 327 328 /** 329 * Count total places. 330 * 331 * @return string 332 */ 333 public function totalPlaces(): string 334 { 335 return I18N::number($this->totalPlacesQuery()); 336 } 337 338 /** 339 * Create a chart showing where events occurred. 340 * 341 * @param string $chart_shows 342 * @param string $chart_type 343 * @param string $surname 344 * 345 * @return string 346 */ 347 public function chartDistribution( 348 string $chart_shows = 'world', 349 string $chart_type = '', 350 string $surname = '' 351 ): string { 352 return (new ChartDistribution($this->tree)) 353 ->chartDistribution($chart_shows, $chart_type, $surname); 354 } 355} 356