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 20*b68caec6SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 21*b68caec6SGreg Roach 22a25f0a04SGreg Roach/** 2376692c8bSGreg Roach * A GEDCOM place (PLAC) object. 24a25f0a04SGreg Roach */ 25c1010edaSGreg Roachclass Place 26c1010edaSGreg Roach{ 2716d6367aSGreg Roach public const GEDCOM_SEPARATOR = ', '; 2884caa210SGreg Roach 2976692c8bSGreg Roach /** @var string[] e.g. array('Westminster', 'London', 'England') */ 3084caa210SGreg Roach private $gedcom_place; 3184caa210SGreg Roach 3276692c8bSGreg Roach /** @var Tree We may have the same place name in different trees. */ 3384caa210SGreg Roach private $tree; 34a25f0a04SGreg Roach 35a25f0a04SGreg Roach /** 3676692c8bSGreg Roach * Create a place. 3776692c8bSGreg Roach * 38a25f0a04SGreg Roach * @param string $gedcom_place 3984caa210SGreg Roach * @param Tree $tree 40a25f0a04SGreg Roach */ 41c1010edaSGreg Roach public function __construct($gedcom_place, Tree $tree) 42c1010edaSGreg Roach { 43b20ddbf9SGreg Roach if ($gedcom_place === '') { 4413abd6f3SGreg Roach $this->gedcom_place = []; 45b20ddbf9SGreg Roach } else { 46b20ddbf9SGreg Roach $this->gedcom_place = explode(self::GEDCOM_SEPARATOR, $gedcom_place); 47a25f0a04SGreg Roach } 4864b16745SGreg Roach $this->tree = $tree; 49a25f0a04SGreg Roach } 50a25f0a04SGreg Roach 51a25f0a04SGreg Roach /** 5216d0b7f7SRico Sonntag * Extract the country (last part) of a place name. 5316d0b7f7SRico Sonntag * 5416d0b7f7SRico Sonntag * @return string - e.g. "England" 5516d0b7f7SRico Sonntag */ 568f53f488SRico Sonntag public function lastPart(): string 57c1010edaSGreg Roach { 5875fe6325SGreg Roach return $this->gedcom_place[count($this->gedcom_place) - 1] ?? ''; 5916d0b7f7SRico Sonntag } 6016d0b7f7SRico Sonntag 6116d0b7f7SRico Sonntag /** 6276692c8bSGreg Roach * Get the identifier for a place. 6376692c8bSGreg Roach * 64cbc1590aSGreg Roach * @return int 65a25f0a04SGreg Roach */ 668f53f488SRico Sonntag public function getPlaceId(): int 67c1010edaSGreg Roach { 68a25f0a04SGreg Roach $place_id = 0; 69db7bb364SGreg Roach 70a25f0a04SGreg Roach foreach (array_reverse($this->gedcom_place) as $place) { 71*b68caec6SGreg Roach $place_id = (int) DB::table('places') 72*b68caec6SGreg Roach ->where('p_file', '=', $this->tree->id()) 73*b68caec6SGreg Roach ->where('p_place', '=', $place) 74*b68caec6SGreg Roach ->where('p_parent_id', '=', $place_id) 75*b68caec6SGreg Roach ->value('p_id'); 76a25f0a04SGreg Roach } 77a25f0a04SGreg Roach 78a25f0a04SGreg Roach return $place_id; 79a25f0a04SGreg Roach } 80a25f0a04SGreg Roach 81a25f0a04SGreg Roach /** 8276692c8bSGreg Roach * Get the higher level place. 8376692c8bSGreg Roach * 84a25f0a04SGreg Roach * @return Place 85a25f0a04SGreg Roach */ 868f53f488SRico Sonntag public function getParentPlace(): Place 87c1010edaSGreg Roach { 8806ef8e02SGreg Roach return new self(implode(self::GEDCOM_SEPARATOR, array_slice($this->gedcom_place, 1)), $this->tree); 89a25f0a04SGreg Roach } 90a25f0a04SGreg Roach 91a25f0a04SGreg Roach /** 9276692c8bSGreg Roach * Get the lower level places. 9376692c8bSGreg Roach * 94a25f0a04SGreg Roach * @return Place[] 95a25f0a04SGreg Roach */ 968f53f488SRico Sonntag public function getChildPlaces(): array 97c1010edaSGreg Roach { 98a25f0a04SGreg Roach if ($this->getPlaceId()) { 99a25f0a04SGreg Roach $parent_text = self::GEDCOM_SEPARATOR . $this->getGedcomName(); 100a25f0a04SGreg Roach } else { 101a25f0a04SGreg Roach $parent_text = ''; 102a25f0a04SGreg Roach } 103a25f0a04SGreg Roach 104*b68caec6SGreg Roach return DB::table('places') 105*b68caec6SGreg Roach ->where('p_file', '=', $this->tree->id()) 106*b68caec6SGreg Roach ->where('p_parent_id', '=', $this->getPlaceId()) 107*b68caec6SGreg Roach ->orderBy(DB::raw('p_place /*! COLLATE ' . I18N::collation() . ' */')) 108*b68caec6SGreg Roach ->pluck('p_place') 109*b68caec6SGreg Roach ->map(function (string $place) use ($parent_text): Place { 110*b68caec6SGreg Roach return new self($place . $parent_text, $this->tree); 111*b68caec6SGreg Roach }) 112*b68caec6SGreg Roach ->all(); 113a25f0a04SGreg Roach } 114a25f0a04SGreg Roach 115a25f0a04SGreg Roach /** 11676692c8bSGreg Roach * Create a URL to the place-hierarchy page. 11776692c8bSGreg Roach * 118a25f0a04SGreg Roach * @return string 119a25f0a04SGreg Roach */ 120e8777cb5SGreg Roach public function url(): string 121c1010edaSGreg Roach { 122e8777cb5SGreg Roach return route('place-hierarchy', [ 123c1010edaSGreg Roach 'parent' => array_reverse($this->gedcom_place), 124aa6f03bbSGreg Roach 'ged' => $this->tree->name(), 125e8777cb5SGreg Roach ]); 126a25f0a04SGreg Roach } 127a25f0a04SGreg Roach 128a25f0a04SGreg Roach /** 12976692c8bSGreg Roach * Format this name for GEDCOM data. 13076692c8bSGreg Roach * 131a25f0a04SGreg Roach * @return string 132a25f0a04SGreg Roach */ 1338f53f488SRico Sonntag public function getGedcomName(): string 134c1010edaSGreg Roach { 135a25f0a04SGreg Roach return implode(self::GEDCOM_SEPARATOR, $this->gedcom_place); 136a25f0a04SGreg Roach } 137a25f0a04SGreg Roach 138a25f0a04SGreg Roach /** 13976692c8bSGreg Roach * Format this place for display on screen. 14076692c8bSGreg Roach * 141a25f0a04SGreg Roach * @return string 142a25f0a04SGreg Roach */ 1438f53f488SRico Sonntag public function getPlaceName(): string 144c1010edaSGreg Roach { 14575fe6325SGreg Roach if (empty($this->gedcom_place)) { 14675fe6325SGreg Roach return I18N::translate('unknown'); 14775fe6325SGreg Roach } 148a25f0a04SGreg Roach 14975fe6325SGreg Roach return '<span dir="auto">' . e($this->gedcom_place[0]) . '</span>'; 150a25f0a04SGreg Roach } 151a25f0a04SGreg Roach 152a25f0a04SGreg Roach /** 15376692c8bSGreg Roach * Is this a null/empty/missing/invalid place? 15476692c8bSGreg Roach * 155a25f0a04SGreg Roach * @return bool 156a25f0a04SGreg Roach */ 1578f53f488SRico Sonntag public function isEmpty(): bool 158c1010edaSGreg Roach { 159a25f0a04SGreg Roach return empty($this->gedcom_place); 160a25f0a04SGreg Roach } 161a25f0a04SGreg Roach 162a25f0a04SGreg Roach /** 16376692c8bSGreg Roach * Generate the place name for display, including the full hierarchy. 16476692c8bSGreg Roach * 165a25f0a04SGreg Roach * @return string 166a25f0a04SGreg Roach */ 167c1010edaSGreg Roach public function getFullName() 168c1010edaSGreg Roach { 169a25f0a04SGreg Roach if (true) { 170a25f0a04SGreg Roach // If a place hierarchy is a single entity 171d53324c9SGreg Roach return '<span dir="auto">' . e(implode(I18N::$list_separator, $this->gedcom_place)) . '</span>'; 172b2ce94c6SRico Sonntag } 173b2ce94c6SRico Sonntag 174a25f0a04SGreg Roach // If a place hierarchy is a list of distinct items 17513abd6f3SGreg Roach $tmp = []; 176a25f0a04SGreg Roach foreach ($this->gedcom_place as $place) { 177d53324c9SGreg Roach $tmp[] = '<span dir="auto">' . e($place) . '</span>'; 178a25f0a04SGreg Roach } 179a25f0a04SGreg Roach 180a25f0a04SGreg Roach return implode(I18N::$list_separator, $tmp); 181a25f0a04SGreg Roach } 182a25f0a04SGreg Roach 183a25f0a04SGreg Roach /** 184a25f0a04SGreg Roach * For lists and charts, where the full name won’t fit. 185a25f0a04SGreg Roach * 186a25f0a04SGreg Roach * @return string 187a25f0a04SGreg Roach */ 188c1010edaSGreg Roach public function getShortName() 189c1010edaSGreg Roach { 190a99c6938SGreg Roach $SHOW_PEDIGREE_PLACES = (int) $this->tree->getPreference('SHOW_PEDIGREE_PLACES'); 191a25f0a04SGreg Roach 192a25f0a04SGreg Roach if ($SHOW_PEDIGREE_PLACES >= count($this->gedcom_place)) { 193a25f0a04SGreg Roach // A short place name - no need to abbreviate 194a25f0a04SGreg Roach return $this->getFullName(); 195b2ce94c6SRico Sonntag } 196b2ce94c6SRico Sonntag 197a25f0a04SGreg Roach // Abbreviate the place name, for lists 19864b16745SGreg Roach if ($this->tree->getPreference('SHOW_PEDIGREE_PLACES_SUFFIX')) { 199a25f0a04SGreg Roach // The *last* $SHOW_PEDIGREE_PLACES components 200a25f0a04SGreg Roach $short_name = implode(self::GEDCOM_SEPARATOR, array_slice($this->gedcom_place, -$SHOW_PEDIGREE_PLACES)); 201a25f0a04SGreg Roach } else { 202a25f0a04SGreg Roach // The *first* $SHOW_PEDIGREE_PLACES components 203a25f0a04SGreg Roach $short_name = implode(self::GEDCOM_SEPARATOR, array_slice($this->gedcom_place, 0, $SHOW_PEDIGREE_PLACES)); 204a25f0a04SGreg Roach } 205c1010edaSGreg Roach 206a25f0a04SGreg Roach // Add a tool-tip showing the full name 207d53324c9SGreg Roach return '<span title="' . e($this->getGedcomName()) . '" dir="auto">' . e($short_name) . '</span>'; 208a25f0a04SGreg Roach } 209a25f0a04SGreg Roach 210a25f0a04SGreg Roach /** 211661f69c0SDavid Drury * For the Place hierarchy "list all" option 212a25f0a04SGreg Roach * 213a25f0a04SGreg Roach * @return string 214a25f0a04SGreg Roach */ 2158f53f488SRico Sonntag public function getReverseName(): string 216c1010edaSGreg Roach { 21713abd6f3SGreg Roach $tmp = []; 218a25f0a04SGreg Roach foreach (array_reverse($this->gedcom_place) as $place) { 219d53324c9SGreg Roach $tmp[] = '<span dir="auto">' . e($place) . '</span>'; 220a25f0a04SGreg Roach } 221a25f0a04SGreg Roach 222a25f0a04SGreg Roach return implode(I18N::$list_separator, $tmp); 223a25f0a04SGreg Roach } 224a25f0a04SGreg Roach 225a25f0a04SGreg Roach /** 22676692c8bSGreg Roach * Search for a place name. 22776692c8bSGreg Roach * 228a25f0a04SGreg Roach * @param string $filter 22984caa210SGreg Roach * @param Tree $tree 230c1010edaSGreg Roach * 2314080d558SGreg Roach * @return Place[] 232a25f0a04SGreg Roach */ 2338f53f488SRico Sonntag public static function findPlaces($filter, Tree $tree): array 234c1010edaSGreg Roach { 23513abd6f3SGreg Roach $places = []; 236a25f0a04SGreg Roach $rows = 237a25f0a04SGreg Roach Database::prepare( 238e5588fb0SGreg Roach "SELECT CONCAT_WS(', ', p1.p_place, p2.p_place, p3.p_place, p4.p_place, p5.p_place, p6.p_place, p7.p_place, p8.p_place, p9.p_place)" . 239a25f0a04SGreg Roach " FROM `##places` AS p1" . 240a25f0a04SGreg Roach " LEFT JOIN `##places` AS p2 ON (p1.p_parent_id = p2.p_id)" . 241a25f0a04SGreg Roach " LEFT JOIN `##places` AS p3 ON (p2.p_parent_id = p3.p_id)" . 242a25f0a04SGreg Roach " LEFT JOIN `##places` AS p4 ON (p3.p_parent_id = p4.p_id)" . 243a25f0a04SGreg Roach " LEFT JOIN `##places` AS p5 ON (p4.p_parent_id = p5.p_id)" . 244a25f0a04SGreg Roach " LEFT JOIN `##places` AS p6 ON (p5.p_parent_id = p6.p_id)" . 245a25f0a04SGreg Roach " LEFT JOIN `##places` AS p7 ON (p6.p_parent_id = p7.p_id)" . 246a25f0a04SGreg Roach " LEFT JOIN `##places` AS p8 ON (p7.p_parent_id = p8.p_id)" . 247a25f0a04SGreg Roach " LEFT JOIN `##places` AS p9 ON (p8.p_parent_id = p9.p_id)" . 24884caa210SGreg Roach " WHERE CONCAT_WS(', ', p1.p_place, p2.p_place, p3.p_place, p4.p_place, p5.p_place, p6.p_place, p7.p_place, p8.p_place, p9.p_place) LIKE CONCAT('%', :filter_1, '%') AND CONCAT_WS(', ', p1.p_place, p2.p_place, p3.p_place, p4.p_place, p5.p_place, p6.p_place, p7.p_place, p8.p_place, p9.p_place) NOT LIKE CONCAT('%,%', :filter_2, '%') AND p1.p_file = :tree_id" . 24981088023SGreg Roach " ORDER BY CONCAT_WS(', ', p1.p_place, p2.p_place, p3.p_place, p4.p_place, p5.p_place, p6.p_place, p7.p_place, p8.p_place, p9.p_place) COLLATE :collation" 25013abd6f3SGreg Roach )->execute([ 25184caa210SGreg Roach 'filter_1' => preg_quote($filter), 25284caa210SGreg Roach 'filter_2' => preg_quote($filter), 25372cf66d4SGreg Roach 'tree_id' => $tree->id(), 25481088023SGreg Roach 'collation' => I18N::collation(), 25513abd6f3SGreg Roach ])->fetchOneColumn(); 256a25f0a04SGreg Roach foreach ($rows as $row) { 25706ef8e02SGreg Roach $places[] = new self($row, $tree); 258a25f0a04SGreg Roach } 259cbc1590aSGreg Roach 260a25f0a04SGreg Roach return $places; 261a25f0a04SGreg Roach } 262a25f0a04SGreg Roach} 263