1a25f0a04SGreg Roach<?php 23976b470SGreg Roach 3a25f0a04SGreg Roach/** 4a25f0a04SGreg Roach * webtrees: online genealogy 58fcd0d32SGreg Roach * Copyright (C) 2019 webtrees development team 6a25f0a04SGreg Roach * This program is free software: you can redistribute it and/or modify 7a25f0a04SGreg Roach * it under the terms of the GNU General Public License as published by 8a25f0a04SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9a25f0a04SGreg Roach * (at your option) any later version. 10a25f0a04SGreg Roach * This program is distributed in the hope that it will be useful, 11a25f0a04SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12a25f0a04SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13a25f0a04SGreg Roach * GNU General Public License for more details. 14a25f0a04SGreg Roach * You should have received a copy of the GNU General Public License 15a25f0a04SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 16a25f0a04SGreg Roach */ 17fcfa147eSGreg Roach 18e7f56f2aSGreg Roachdeclare(strict_types=1); 19e7f56f2aSGreg Roach 2076692c8bSGreg Roachnamespace Fisharebest\Webtrees; 21a25f0a04SGreg Roach 2267992b6aSRichard Cisseeuse Fisharebest\Webtrees\Module\ModuleInterface; 2387cca37cSGreg Roachuse Fisharebest\Webtrees\Module\ModuleListInterface; 2467992b6aSRichard Cisseeuse Fisharebest\Webtrees\Module\PlaceHierarchyListModule; 2567992b6aSRichard Cisseeuse Fisharebest\Webtrees\Services\ModuleService; 26b68caec6SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 27a69f5655SGreg Roachuse Illuminate\Database\Query\Expression; 28392561bbSGreg Roachuse Illuminate\Support\Collection; 29b68caec6SGreg Roach 30a25f0a04SGreg Roach/** 3176692c8bSGreg Roach * A GEDCOM place (PLAC) object. 32a25f0a04SGreg Roach */ 33c1010edaSGreg Roachclass Place 34c1010edaSGreg Roach{ 35392561bbSGreg Roach /** @var string e.g. "Westminster, London, England" */ 36392561bbSGreg Roach private $place_name; 37392561bbSGreg Roach 3825d7fe95SGreg Roach /** @var Collection The parts of a place name, e.g. ["Westminster", "London", "England"] */ 39392561bbSGreg Roach private $parts; 4084caa210SGreg Roach 4176692c8bSGreg Roach /** @var Tree We may have the same place name in different trees. */ 4284caa210SGreg Roach private $tree; 43a25f0a04SGreg Roach 44a25f0a04SGreg Roach /** 4576692c8bSGreg Roach * Create a place. 4676692c8bSGreg Roach * 47392561bbSGreg Roach * @param string $place_name 4884caa210SGreg Roach * @param Tree $tree 49a25f0a04SGreg Roach */ 50392561bbSGreg Roach public function __construct(string $place_name, Tree $tree) 51c1010edaSGreg Roach { 52392561bbSGreg Roach // Ignore any empty parts in place names such as "Village, , , Country". 53321a89daSGreg Roach $this->parts = Collection::make(preg_split(Gedcom::PLACE_SEPARATOR_REGEX, $place_name)) 54392561bbSGreg Roach ->filter(); 55392561bbSGreg Roach 56392561bbSGreg Roach // Rebuild the placename in the correct format. 57392561bbSGreg Roach $this->place_name = $this->parts->implode(Gedcom::PLACE_SEPARATOR); 58392561bbSGreg Roach 5964b16745SGreg Roach $this->tree = $tree; 60a25f0a04SGreg Roach } 61a25f0a04SGreg Roach 62a25f0a04SGreg Roach /** 6376692c8bSGreg Roach * Get the higher level place. 6476692c8bSGreg Roach * 65a25f0a04SGreg Roach * @return Place 66a25f0a04SGreg Roach */ 67392561bbSGreg Roach public function parent(): Place 68c1010edaSGreg Roach { 698af6bbf8SGreg Roach return new self($this->parts->slice(1)->implode(Gedcom::PLACE_SEPARATOR), $this->tree); 70392561bbSGreg Roach } 71392561bbSGreg Roach 72392561bbSGreg Roach /** 73392561bbSGreg Roach * The database row that contains this place. 74392561bbSGreg Roach * Note that due to database collation, both "Quebec" and "Québec" will share the same row. 75392561bbSGreg Roach * 76392561bbSGreg Roach * @return int 77392561bbSGreg Roach */ 78392561bbSGreg Roach public function id(): int 79392561bbSGreg Roach { 80*7476e8a5SGreg Roach return app('cache.array')->remember('place-' . $this->place_name, function (): int { 81392561bbSGreg Roach // The "top-level" place won't exist in the database. 82392561bbSGreg Roach if ($this->parts->isEmpty()) { 83392561bbSGreg Roach return 0; 84392561bbSGreg Roach } 85392561bbSGreg Roach 868af6bbf8SGreg Roach $parent_place_id = $this->parent()->id(); 87392561bbSGreg Roach 88392561bbSGreg Roach $place_id = (int) DB::table('places') 89392561bbSGreg Roach ->where('p_file', '=', $this->tree->id()) 90392561bbSGreg Roach ->where('p_place', '=', $this->parts->first()) 918af6bbf8SGreg Roach ->where('p_parent_id', '=', $parent_place_id) 92392561bbSGreg Roach ->value('p_id'); 93392561bbSGreg Roach 94392561bbSGreg Roach if ($place_id === 0) { 95392561bbSGreg Roach $place = $this->parts->first(); 96392561bbSGreg Roach 97392561bbSGreg Roach DB::table('places')->insert([ 98392561bbSGreg Roach 'p_file' => $this->tree->id(), 99392561bbSGreg Roach 'p_place' => $place, 1008af6bbf8SGreg Roach 'p_parent_id' => $parent_place_id, 101392561bbSGreg Roach 'p_std_soundex' => Soundex::russell($place), 102392561bbSGreg Roach 'p_dm_soundex' => Soundex::daitchMokotoff($place), 103392561bbSGreg Roach ]); 104392561bbSGreg Roach 105392561bbSGreg Roach $place_id = (int) DB::connection()->getPdo()->lastInsertId(); 106392561bbSGreg Roach } 107392561bbSGreg Roach 108392561bbSGreg Roach return $place_id; 109392561bbSGreg Roach }); 110392561bbSGreg Roach } 111392561bbSGreg Roach 112392561bbSGreg Roach /** 113392561bbSGreg Roach * Extract the locality (first parts) of a place name. 114392561bbSGreg Roach * 115392561bbSGreg Roach * @param int $n 116392561bbSGreg Roach * 117392561bbSGreg Roach * @return Collection 118392561bbSGreg Roach */ 119392561bbSGreg Roach public function firstParts(int $n): Collection 120392561bbSGreg Roach { 121392561bbSGreg Roach return $this->parts->slice(0, $n); 122392561bbSGreg Roach } 123392561bbSGreg Roach 124392561bbSGreg Roach /** 125392561bbSGreg Roach * Extract the country (last parts) of a place name. 126392561bbSGreg Roach * 127392561bbSGreg Roach * @param int $n 128392561bbSGreg Roach * 129392561bbSGreg Roach * @return Collection 130392561bbSGreg Roach */ 131392561bbSGreg Roach public function lastParts(int $n): Collection 132392561bbSGreg Roach { 133392561bbSGreg Roach return $this->parts->slice(-$n); 134a25f0a04SGreg Roach } 135a25f0a04SGreg Roach 136a25f0a04SGreg Roach /** 13776692c8bSGreg Roach * Get the lower level places. 13876692c8bSGreg Roach * 139a25f0a04SGreg Roach * @return Place[] 140a25f0a04SGreg Roach */ 1418f53f488SRico Sonntag public function getChildPlaces(): array 142c1010edaSGreg Roach { 143392561bbSGreg Roach if ($this->place_name !== '') { 144392561bbSGreg Roach $parent_text = Gedcom::PLACE_SEPARATOR . $this->place_name; 145a25f0a04SGreg Roach } else { 146a25f0a04SGreg Roach $parent_text = ''; 147a25f0a04SGreg Roach } 148a25f0a04SGreg Roach 149b68caec6SGreg Roach return DB::table('places') 150b68caec6SGreg Roach ->where('p_file', '=', $this->tree->id()) 151392561bbSGreg Roach ->where('p_parent_id', '=', $this->id()) 152a69f5655SGreg Roach ->orderBy(new Expression('p_place /*! COLLATE ' . I18N::collation() . ' */')) 153b68caec6SGreg Roach ->pluck('p_place') 154b68caec6SGreg Roach ->map(function (string $place) use ($parent_text): Place { 155b68caec6SGreg Roach return new self($place . $parent_text, $this->tree); 156b68caec6SGreg Roach }) 157b68caec6SGreg Roach ->all(); 158a25f0a04SGreg Roach } 159a25f0a04SGreg Roach 160a25f0a04SGreg Roach /** 16176692c8bSGreg Roach * Create a URL to the place-hierarchy page. 16276692c8bSGreg Roach * 163a25f0a04SGreg Roach * @return string 164a25f0a04SGreg Roach */ 165e8777cb5SGreg Roach public function url(): string 166c1010edaSGreg Roach { 16767992b6aSRichard Cissee //find a module providing the place hierarchy 1680797053bSGreg Roach $module = app(ModuleService::class) 1690797053bSGreg Roach ->findByComponent(ModuleListInterface::class, $this->tree, Auth::user()) 1700797053bSGreg Roach ->first(static function (ModuleInterface $module): bool { 17167992b6aSRichard Cissee return $module instanceof PlaceHierarchyListModule; 17267992b6aSRichard Cissee }); 17367992b6aSRichard Cissee 17467992b6aSRichard Cissee if ($module instanceof PlaceHierarchyListModule) { 17567992b6aSRichard Cissee return $module->listUrl($this->tree, [ 176392561bbSGreg Roach 'parent' => $this->parts->reverse()->all(), 1779022ab66SGreg Roach 'tree' => $this->tree->name(), 178e8777cb5SGreg Roach ]); 179e364afe4SGreg Roach } 180e364afe4SGreg Roach 181f7721877SGreg Roach // The place-list module is disabled... 182f7721877SGreg Roach return '#'; 18367992b6aSRichard Cissee } 184a25f0a04SGreg Roach 185a25f0a04SGreg Roach /** 186392561bbSGreg Roach * Format this place for GEDCOM data. 18776692c8bSGreg Roach * 188a25f0a04SGreg Roach * @return string 189a25f0a04SGreg Roach */ 190392561bbSGreg Roach public function gedcomName(): string 191c1010edaSGreg Roach { 192392561bbSGreg Roach return $this->place_name; 193a25f0a04SGreg Roach } 194a25f0a04SGreg Roach 195a25f0a04SGreg Roach /** 19676692c8bSGreg Roach * Format this place for display on screen. 19776692c8bSGreg Roach * 198a25f0a04SGreg Roach * @return string 199a25f0a04SGreg Roach */ 200392561bbSGreg Roach public function placeName(): string 201c1010edaSGreg Roach { 202392561bbSGreg Roach $place_name = $this->parts->first() ?? I18N::translate('unknown'); 203a25f0a04SGreg Roach 204392561bbSGreg Roach return '<span dir="auto">' . e($place_name) . '</span>'; 205a25f0a04SGreg Roach } 206a25f0a04SGreg Roach 207a25f0a04SGreg Roach /** 20876692c8bSGreg Roach * Generate the place name for display, including the full hierarchy. 20976692c8bSGreg Roach * 210392561bbSGreg Roach * @param bool $link 211392561bbSGreg Roach * 212a25f0a04SGreg Roach * @return string 213a25f0a04SGreg Roach */ 214e364afe4SGreg Roach public function fullName(bool $link = false): string 215c1010edaSGreg Roach { 216392561bbSGreg Roach if ($this->parts->isEmpty()) { 217392561bbSGreg Roach return ''; 218b2ce94c6SRico Sonntag } 219b2ce94c6SRico Sonntag 220392561bbSGreg Roach $full_name = $this->parts->implode(I18N::$list_separator); 221392561bbSGreg Roach 222392561bbSGreg Roach if ($link) { 223392561bbSGreg Roach return '<a dir="auto" href="' . e($this->url()) . '">' . e($full_name) . '</a>'; 224a25f0a04SGreg Roach } 225a25f0a04SGreg Roach 226392561bbSGreg Roach return '<span dir="auto">' . e($full_name) . '</span>'; 227a25f0a04SGreg Roach } 228a25f0a04SGreg Roach 229a25f0a04SGreg Roach /** 230a25f0a04SGreg Roach * For lists and charts, where the full name won’t fit. 231a25f0a04SGreg Roach * 232392561bbSGreg Roach * @param bool $link 233a25f0a04SGreg Roach * 234a25f0a04SGreg Roach * @return string 235a25f0a04SGreg Roach */ 236e364afe4SGreg Roach public function shortName(bool $link = false): string 237c1010edaSGreg Roach { 238392561bbSGreg Roach $SHOW_PEDIGREE_PLACES = (int) $this->tree->getPreference('SHOW_PEDIGREE_PLACES'); 239392561bbSGreg Roach 240392561bbSGreg Roach // Abbreviate the place name, for lists 241392561bbSGreg Roach if ($this->tree->getPreference('SHOW_PEDIGREE_PLACES_SUFFIX')) { 242392561bbSGreg Roach $parts = $this->lastParts($SHOW_PEDIGREE_PLACES); 243392561bbSGreg Roach } else { 244392561bbSGreg Roach $parts = $this->firstParts($SHOW_PEDIGREE_PLACES); 245a25f0a04SGreg Roach } 246a25f0a04SGreg Roach 247392561bbSGreg Roach $short_name = $parts->implode(I18N::$list_separator); 248392561bbSGreg Roach 249392561bbSGreg Roach // Add a tool-tip showing the full name 250392561bbSGreg Roach $title = strip_tags($this->fullName()); 251392561bbSGreg Roach 252392561bbSGreg Roach if ($link) { 25340f99dd2SGreg Roach return '<a dir="auto" href="' . e($this->url()) . '" title="' . $title . '">' . e($short_name) . '</a>'; 254392561bbSGreg Roach } 255392561bbSGreg Roach 256392561bbSGreg Roach return '<span dir="auto">' . e($short_name) . '</span>'; 257a25f0a04SGreg Roach } 258a25f0a04SGreg Roach} 259