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