1a25f0a04SGreg Roach<?php 2a25f0a04SGreg Roach/** 3a25f0a04SGreg Roach * webtrees: online genealogy 48fcd0d32SGreg Roach * Copyright (C) 2019 webtrees development team 5a25f0a04SGreg Roach * This program is free software: you can redistribute it and/or modify 6a25f0a04SGreg Roach * it under the terms of the GNU General Public License as published by 7a25f0a04SGreg Roach * the Free Software Foundation, either version 3 of the License, or 8a25f0a04SGreg Roach * (at your option) any later version. 9a25f0a04SGreg Roach * This program is distributed in the hope that it will be useful, 10a25f0a04SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 11a25f0a04SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12a25f0a04SGreg Roach * GNU General Public License for more details. 13a25f0a04SGreg Roach * You should have received a copy of the GNU General Public License 14a25f0a04SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 15a25f0a04SGreg Roach */ 16e7f56f2aSGreg Roachdeclare(strict_types=1); 17e7f56f2aSGreg Roach 1876692c8bSGreg Roachnamespace Fisharebest\Webtrees; 19a25f0a04SGreg Roach 20b68caec6SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 21*392561bbSGreg Roachuse Illuminate\Support\Collection; 22b68caec6SGreg Roach 23a25f0a04SGreg Roach/** 2476692c8bSGreg Roach * A GEDCOM place (PLAC) object. 25a25f0a04SGreg Roach */ 26c1010edaSGreg Roachclass Place 27c1010edaSGreg Roach{ 28*392561bbSGreg Roach /** @var string e.g. "Westminster, London, England" */ 29*392561bbSGreg Roach private $place_name; 30*392561bbSGreg Roach 31*392561bbSGreg Roach /** @var Collection|string[] The parts of a place name, e.g. ["Westminster", "London", "England"] */ 32*392561bbSGreg Roach private $parts; 3384caa210SGreg Roach 3476692c8bSGreg Roach /** @var Tree We may have the same place name in different trees. */ 3584caa210SGreg Roach private $tree; 36a25f0a04SGreg Roach 37a25f0a04SGreg Roach /** 3876692c8bSGreg Roach * Create a place. 3976692c8bSGreg Roach * 40*392561bbSGreg Roach * @param string $place_name 4184caa210SGreg Roach * @param Tree $tree 42a25f0a04SGreg Roach */ 43*392561bbSGreg Roach public function __construct(string $place_name, Tree $tree) 44c1010edaSGreg Roach { 45*392561bbSGreg Roach // Ignore any empty parts in place names such as "Village, , , Country". 46*392561bbSGreg Roach $this->parts = (new Collection(preg_split(Gedcom::PLACE_SEPARATOR_REGEX, $place_name))) 47*392561bbSGreg Roach ->filter(); 48*392561bbSGreg Roach 49*392561bbSGreg Roach // Rebuild the placename in the correct format. 50*392561bbSGreg Roach $this->place_name = $this->parts->implode(Gedcom::PLACE_SEPARATOR); 51*392561bbSGreg Roach 5264b16745SGreg Roach $this->tree = $tree; 53a25f0a04SGreg Roach } 54a25f0a04SGreg Roach 55a25f0a04SGreg Roach /** 5676692c8bSGreg Roach * Get the higher level place. 5776692c8bSGreg Roach * 58a25f0a04SGreg Roach * @return Place 59a25f0a04SGreg Roach */ 60*392561bbSGreg Roach public function parent(): Place 61c1010edaSGreg Roach { 62*392561bbSGreg Roach return new static($this->parts->slice(1)->implode(Gedcom::PLACE_SEPARATOR), $this->tree); 63*392561bbSGreg Roach } 64*392561bbSGreg Roach 65*392561bbSGreg Roach /** 66*392561bbSGreg Roach * The database row that contains this place. 67*392561bbSGreg Roach * Note that due to database collation, both "Quebec" and "Québec" will share the same row. 68*392561bbSGreg Roach * 69*392561bbSGreg Roach * @return int 70*392561bbSGreg Roach */ 71*392561bbSGreg Roach public function id(): int 72*392561bbSGreg Roach { 73*392561bbSGreg Roach return app()->make('cache.array')->rememberForever('place:' . $this->place_name, function () { 74*392561bbSGreg Roach // The "top-level" place won't exist in the database. 75*392561bbSGreg Roach if ($this->parts->isEmpty()) { 76*392561bbSGreg Roach return 0; 77*392561bbSGreg Roach } 78*392561bbSGreg Roach 79*392561bbSGreg Roach $parent_place = $this->parent(); 80*392561bbSGreg Roach 81*392561bbSGreg Roach $place_id = (int) DB::table('places') 82*392561bbSGreg Roach ->where('p_file', '=', $this->tree->id()) 83*392561bbSGreg Roach ->where('p_place', '=', $this->parts->first()) 84*392561bbSGreg Roach ->where('p_parent_id', '=', $parent_place->id()) 85*392561bbSGreg Roach ->value('p_id'); 86*392561bbSGreg Roach 87*392561bbSGreg Roach if ($place_id === 0) { 88*392561bbSGreg Roach $place = $this->parts->first(); 89*392561bbSGreg Roach 90*392561bbSGreg Roach DB::table('places')->insert([ 91*392561bbSGreg Roach 'p_file' => $this->tree->id(), 92*392561bbSGreg Roach 'p_place' => $place, 93*392561bbSGreg Roach 'p_parent_id' => $parent_place->id(), 94*392561bbSGreg Roach 'p_std_soundex' => Soundex::russell($place), 95*392561bbSGreg Roach 'p_dm_soundex' => Soundex::daitchMokotoff($place), 96*392561bbSGreg Roach ]); 97*392561bbSGreg Roach 98*392561bbSGreg Roach $place_id = (int) DB::connection()->getPdo()->lastInsertId(); 99*392561bbSGreg Roach } 100*392561bbSGreg Roach 101*392561bbSGreg Roach return $place_id; 102*392561bbSGreg Roach }); 103*392561bbSGreg Roach } 104*392561bbSGreg Roach 105*392561bbSGreg Roach /** 106*392561bbSGreg Roach * Extract the locality (first parts) of a place name. 107*392561bbSGreg Roach * 108*392561bbSGreg Roach * @param int $n 109*392561bbSGreg Roach * 110*392561bbSGreg Roach * @return Collection 111*392561bbSGreg Roach */ 112*392561bbSGreg Roach public function firstParts(int $n): Collection 113*392561bbSGreg Roach { 114*392561bbSGreg Roach return $this->parts->slice(0, $n); 115*392561bbSGreg Roach } 116*392561bbSGreg Roach 117*392561bbSGreg Roach /** 118*392561bbSGreg Roach * Extract the country (last parts) of a place name. 119*392561bbSGreg Roach * 120*392561bbSGreg Roach * @param int $n 121*392561bbSGreg Roach * 122*392561bbSGreg Roach * @return Collection 123*392561bbSGreg Roach */ 124*392561bbSGreg Roach public function lastParts(int $n): Collection 125*392561bbSGreg Roach { 126*392561bbSGreg Roach return $this->parts->slice(-$n); 127a25f0a04SGreg Roach } 128a25f0a04SGreg Roach 129a25f0a04SGreg Roach /** 13076692c8bSGreg Roach * Get the lower level places. 13176692c8bSGreg Roach * 132a25f0a04SGreg Roach * @return Place[] 133a25f0a04SGreg Roach */ 1348f53f488SRico Sonntag public function getChildPlaces(): array 135c1010edaSGreg Roach { 136*392561bbSGreg Roach if ($this->place_name !== '') { 137*392561bbSGreg Roach $parent_text = Gedcom::PLACE_SEPARATOR . $this->place_name; 138a25f0a04SGreg Roach } else { 139a25f0a04SGreg Roach $parent_text = ''; 140a25f0a04SGreg Roach } 141a25f0a04SGreg Roach 142b68caec6SGreg Roach return DB::table('places') 143b68caec6SGreg Roach ->where('p_file', '=', $this->tree->id()) 144*392561bbSGreg Roach ->where('p_parent_id', '=', $this->id()) 145b68caec6SGreg Roach ->orderBy(DB::raw('p_place /*! COLLATE ' . I18N::collation() . ' */')) 146b68caec6SGreg Roach ->pluck('p_place') 147b68caec6SGreg Roach ->map(function (string $place) use ($parent_text): Place { 148b68caec6SGreg Roach return new self($place . $parent_text, $this->tree); 149b68caec6SGreg Roach }) 150b68caec6SGreg Roach ->all(); 151a25f0a04SGreg Roach } 152a25f0a04SGreg Roach 153a25f0a04SGreg Roach /** 15476692c8bSGreg Roach * Create a URL to the place-hierarchy page. 15576692c8bSGreg Roach * 156a25f0a04SGreg Roach * @return string 157a25f0a04SGreg Roach */ 158e8777cb5SGreg Roach public function url(): string 159c1010edaSGreg Roach { 160e8777cb5SGreg Roach return route('place-hierarchy', [ 161*392561bbSGreg Roach 'parent' => $this->parts->reverse()->all(), 162aa6f03bbSGreg Roach 'ged' => $this->tree->name(), 163e8777cb5SGreg Roach ]); 164a25f0a04SGreg Roach } 165a25f0a04SGreg Roach 166a25f0a04SGreg Roach /** 167*392561bbSGreg Roach * Format this place for GEDCOM data. 16876692c8bSGreg Roach * 169a25f0a04SGreg Roach * @return string 170a25f0a04SGreg Roach */ 171*392561bbSGreg Roach public function gedcomName(): string 172c1010edaSGreg Roach { 173*392561bbSGreg Roach return $this->place_name; 174a25f0a04SGreg Roach } 175a25f0a04SGreg Roach 176a25f0a04SGreg Roach /** 17776692c8bSGreg Roach * Format this place for display on screen. 17876692c8bSGreg Roach * 179a25f0a04SGreg Roach * @return string 180a25f0a04SGreg Roach */ 181*392561bbSGreg Roach public function placeName(): string 182c1010edaSGreg Roach { 183*392561bbSGreg Roach $place_name = $this->parts->first() ?? I18N::translate('unknown'); 184a25f0a04SGreg Roach 185*392561bbSGreg Roach return '<span dir="auto">' . e($place_name) . '</span>'; 186a25f0a04SGreg Roach } 187a25f0a04SGreg Roach 188a25f0a04SGreg Roach /** 18976692c8bSGreg Roach * Generate the place name for display, including the full hierarchy. 19076692c8bSGreg Roach * 191*392561bbSGreg Roach * @param bool $link 192*392561bbSGreg Roach * 193a25f0a04SGreg Roach * @return string 194a25f0a04SGreg Roach */ 195*392561bbSGreg Roach public function fullName(bool $link = false) 196c1010edaSGreg Roach { 197*392561bbSGreg Roach if ($this->parts->isEmpty()) { 198*392561bbSGreg Roach return ''; 199b2ce94c6SRico Sonntag } 200b2ce94c6SRico Sonntag 201*392561bbSGreg Roach $full_name = $this->parts->implode(I18N::$list_separator); 202*392561bbSGreg Roach 203*392561bbSGreg Roach if ($link) { 204*392561bbSGreg Roach return '<a dir="auto" href="' . e($this->url()) . '">' . e($full_name) . '</a>'; 205a25f0a04SGreg Roach } 206a25f0a04SGreg Roach 207*392561bbSGreg Roach return '<span dir="auto">' . e($full_name) . '</span>'; 208a25f0a04SGreg Roach } 209a25f0a04SGreg Roach 210a25f0a04SGreg Roach /** 211a25f0a04SGreg Roach * For lists and charts, where the full name won’t fit. 212a25f0a04SGreg Roach * 213*392561bbSGreg Roach * @param bool $link 214a25f0a04SGreg Roach * 215a25f0a04SGreg Roach * @return string 216a25f0a04SGreg Roach */ 217*392561bbSGreg Roach public function shortName(bool $link = false) 218c1010edaSGreg Roach { 219*392561bbSGreg Roach $SHOW_PEDIGREE_PLACES = (int) $this->tree->getPreference('SHOW_PEDIGREE_PLACES'); 220*392561bbSGreg Roach 221*392561bbSGreg Roach // Abbreviate the place name, for lists 222*392561bbSGreg Roach if ($this->tree->getPreference('SHOW_PEDIGREE_PLACES_SUFFIX')) { 223*392561bbSGreg Roach $parts = $this->lastParts($SHOW_PEDIGREE_PLACES); 224*392561bbSGreg Roach } else { 225*392561bbSGreg Roach $parts = $this->firstParts($SHOW_PEDIGREE_PLACES); 226a25f0a04SGreg Roach } 227a25f0a04SGreg Roach 228*392561bbSGreg Roach $short_name = $parts->implode(I18N::$list_separator); 229*392561bbSGreg Roach 230*392561bbSGreg Roach // Add a tool-tip showing the full name 231*392561bbSGreg Roach $title = strip_tags($this->fullName()); 232*392561bbSGreg Roach 233*392561bbSGreg Roach if ($link) { 234*392561bbSGreg Roach return '<a dir="auto" href="' . e($this->url()) . '" title="' . $title . '"">' . e($short_name) . '</a>'; 235*392561bbSGreg Roach } 236*392561bbSGreg Roach 237*392561bbSGreg Roach return '<span dir="auto">' . e($short_name) . '</span>'; 238a25f0a04SGreg Roach } 239a25f0a04SGreg Roach} 240