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