1a25f0a04SGreg Roach<?php 2a25f0a04SGreg Roach/** 3a25f0a04SGreg Roach * webtrees: online genealogy 41062a142SGreg Roach * Copyright (C) 2018 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 */ 1676692c8bSGreg Roachnamespace Fisharebest\Webtrees; 17a25f0a04SGreg Roach 18a25f0a04SGreg Roach/** 1976692c8bSGreg Roach * A GEDCOM place (PLAC) object. 20a25f0a04SGreg Roach */ 21c1010edaSGreg Roachclass Place 22c1010edaSGreg Roach{ 23a25f0a04SGreg Roach const GEDCOM_SEPARATOR = ', '; 2484caa210SGreg Roach 2576692c8bSGreg Roach /** @var string[] e.g. array('Westminster', 'London', 'England') */ 2684caa210SGreg Roach private $gedcom_place; 2784caa210SGreg Roach 2876692c8bSGreg Roach /** @var Tree We may have the same place name in different trees. */ 2984caa210SGreg Roach private $tree; 30a25f0a04SGreg Roach 31a25f0a04SGreg Roach /** 3276692c8bSGreg Roach * Create a place. 3376692c8bSGreg Roach * 34a25f0a04SGreg Roach * @param string $gedcom_place 3584caa210SGreg Roach * @param Tree $tree 36a25f0a04SGreg Roach */ 37c1010edaSGreg Roach public function __construct($gedcom_place, Tree $tree) 38c1010edaSGreg Roach { 39b20ddbf9SGreg Roach if ($gedcom_place === '') { 4013abd6f3SGreg Roach $this->gedcom_place = []; 41b20ddbf9SGreg Roach } else { 42b20ddbf9SGreg Roach $this->gedcom_place = explode(self::GEDCOM_SEPARATOR, $gedcom_place); 43a25f0a04SGreg Roach } 4464b16745SGreg Roach $this->tree = $tree; 45a25f0a04SGreg Roach } 46a25f0a04SGreg Roach 47a25f0a04SGreg Roach /** 4816d0b7f7SRico Sonntag * Extract the country (last part) of a place name. 4916d0b7f7SRico Sonntag * 5016d0b7f7SRico Sonntag * @return string - e.g. "England" 5116d0b7f7SRico Sonntag */ 528f53f488SRico Sonntag public function lastPart(): string 53c1010edaSGreg Roach { 5416d0b7f7SRico Sonntag return end($this->gedcom_place); 5516d0b7f7SRico Sonntag } 5616d0b7f7SRico Sonntag 5716d0b7f7SRico Sonntag /** 5876692c8bSGreg Roach * Get the identifier for a place. 5976692c8bSGreg Roach * 60cbc1590aSGreg Roach * @return int 61a25f0a04SGreg Roach */ 628f53f488SRico Sonntag public function getPlaceId(): int 63c1010edaSGreg Roach { 64a25f0a04SGreg Roach $place_id = 0; 65db7bb364SGreg Roach 66a25f0a04SGreg Roach foreach (array_reverse($this->gedcom_place) as $place) { 67db7bb364SGreg Roach $place_id = (int) Database::prepare( 68e5588fb0SGreg Roach "SELECT p_id FROM `##places` WHERE p_parent_id = :parent_id AND p_place = :place AND p_file = :tree_id" 6913abd6f3SGreg Roach )->execute([ 70a25f0a04SGreg Roach 'parent_id' => $place_id, 71a25f0a04SGreg Roach 'place' => $place, 7264b16745SGreg Roach 'tree_id' => $this->tree->getTreeId(), 7313abd6f3SGreg Roach ])->fetchOne(); 74a25f0a04SGreg Roach } 75a25f0a04SGreg Roach 76a25f0a04SGreg Roach return $place_id; 77a25f0a04SGreg Roach } 78a25f0a04SGreg Roach 79a25f0a04SGreg Roach /** 8076692c8bSGreg Roach * Get the higher level place. 8176692c8bSGreg Roach * 82a25f0a04SGreg Roach * @return Place 83a25f0a04SGreg Roach */ 848f53f488SRico Sonntag public function getParentPlace(): Place 85c1010edaSGreg Roach { 8606ef8e02SGreg Roach return new self(implode(self::GEDCOM_SEPARATOR, array_slice($this->gedcom_place, 1)), $this->tree); 87a25f0a04SGreg Roach } 88a25f0a04SGreg Roach 89a25f0a04SGreg Roach /** 9076692c8bSGreg Roach * Get the lower level places. 9176692c8bSGreg Roach * 92a25f0a04SGreg Roach * @return Place[] 93a25f0a04SGreg Roach */ 948f53f488SRico Sonntag public function getChildPlaces(): array 95c1010edaSGreg Roach { 9613abd6f3SGreg Roach $children = []; 97a25f0a04SGreg Roach if ($this->getPlaceId()) { 98a25f0a04SGreg Roach $parent_text = self::GEDCOM_SEPARATOR . $this->getGedcomName(); 99a25f0a04SGreg Roach } else { 100a25f0a04SGreg Roach $parent_text = ''; 101a25f0a04SGreg Roach } 102a25f0a04SGreg Roach 103a25f0a04SGreg Roach $rows = Database::prepare( 104e5588fb0SGreg Roach "SELECT p_place FROM `##places`" . 105a25f0a04SGreg Roach " WHERE p_parent_id = :parent_id AND p_file = :tree_id" . 106a25f0a04SGreg Roach " ORDER BY p_place COLLATE :collation" 10713abd6f3SGreg Roach )->execute([ 108a25f0a04SGreg Roach 'parent_id' => $this->getPlaceId(), 10964b16745SGreg Roach 'tree_id' => $this->tree->getTreeId(), 11081088023SGreg Roach 'collation' => I18N::collation(), 11113abd6f3SGreg Roach ])->fetchOneColumn(); 112a25f0a04SGreg Roach foreach ($rows as $row) { 11306ef8e02SGreg Roach $children[] = new self($row . $parent_text, $this->tree); 114a25f0a04SGreg Roach } 115a25f0a04SGreg Roach 116a25f0a04SGreg Roach return $children; 117a25f0a04SGreg Roach } 118a25f0a04SGreg Roach 119a25f0a04SGreg Roach /** 12076692c8bSGreg Roach * Create a URL to the place-hierarchy page. 12176692c8bSGreg Roach * 122a25f0a04SGreg Roach * @return string 123a25f0a04SGreg Roach */ 1248f53f488SRico Sonntag public function getURL(): string 125c1010edaSGreg Roach { 1264686330aSGreg Roach return e(route('place-hierarchy', [ 127c1010edaSGreg Roach 'parent' => array_reverse($this->gedcom_place), 128c1010edaSGreg Roach 'ged' => $this->tree->getName(), 129c1010edaSGreg Roach ])); 130a25f0a04SGreg Roach } 131a25f0a04SGreg Roach 132a25f0a04SGreg Roach /** 13376692c8bSGreg Roach * Format this name for GEDCOM data. 13476692c8bSGreg Roach * 135a25f0a04SGreg Roach * @return string 136a25f0a04SGreg Roach */ 1378f53f488SRico Sonntag public function getGedcomName(): string 138c1010edaSGreg Roach { 139a25f0a04SGreg Roach return implode(self::GEDCOM_SEPARATOR, $this->gedcom_place); 140a25f0a04SGreg Roach } 141a25f0a04SGreg Roach 142a25f0a04SGreg Roach /** 14376692c8bSGreg Roach * Format this place for display on screen. 14476692c8bSGreg Roach * 145a25f0a04SGreg Roach * @return string 146a25f0a04SGreg Roach */ 1478f53f488SRico Sonntag public function getPlaceName(): string 148c1010edaSGreg Roach { 149a25f0a04SGreg Roach $place = reset($this->gedcom_place); 150a25f0a04SGreg Roach 151d53324c9SGreg Roach return $place ? '<span dir="auto">' . e($place) . '</span>' : I18N::translate('unknown'); 152a25f0a04SGreg Roach } 153a25f0a04SGreg Roach 154a25f0a04SGreg Roach /** 15576692c8bSGreg Roach * Is this a null/empty/missing/invalid place? 15676692c8bSGreg Roach * 157a25f0a04SGreg Roach * @return bool 158a25f0a04SGreg Roach */ 1598f53f488SRico Sonntag public function isEmpty(): bool 160c1010edaSGreg Roach { 161a25f0a04SGreg Roach return empty($this->gedcom_place); 162a25f0a04SGreg Roach } 163a25f0a04SGreg Roach 164a25f0a04SGreg Roach /** 16576692c8bSGreg Roach * Generate the place name for display, including the full hierarchy. 16676692c8bSGreg Roach * 167a25f0a04SGreg Roach * @return string 168a25f0a04SGreg Roach */ 169c1010edaSGreg Roach public function getFullName() 170c1010edaSGreg Roach { 171a25f0a04SGreg Roach if (true) { 172a25f0a04SGreg Roach // If a place hierarchy is a single entity 173d53324c9SGreg Roach return '<span dir="auto">' . e(implode(I18N::$list_separator, $this->gedcom_place)) . '</span>'; 174*b2ce94c6SRico Sonntag } 175*b2ce94c6SRico Sonntag 176a25f0a04SGreg Roach // If a place hierarchy is a list of distinct items 17713abd6f3SGreg Roach $tmp = []; 178a25f0a04SGreg Roach foreach ($this->gedcom_place as $place) { 179d53324c9SGreg Roach $tmp[] = '<span dir="auto">' . e($place) . '</span>'; 180a25f0a04SGreg Roach } 181a25f0a04SGreg Roach 182a25f0a04SGreg Roach return implode(I18N::$list_separator, $tmp); 183a25f0a04SGreg Roach } 184a25f0a04SGreg Roach 185a25f0a04SGreg Roach /** 186a25f0a04SGreg Roach * For lists and charts, where the full name won’t fit. 187a25f0a04SGreg Roach * 188a25f0a04SGreg Roach * @return string 189a25f0a04SGreg Roach */ 190c1010edaSGreg Roach public function getShortName() 191c1010edaSGreg Roach { 192a99c6938SGreg Roach $SHOW_PEDIGREE_PLACES = (int)$this->tree->getPreference('SHOW_PEDIGREE_PLACES'); 193a25f0a04SGreg Roach 194a25f0a04SGreg Roach if ($SHOW_PEDIGREE_PLACES >= count($this->gedcom_place)) { 195a25f0a04SGreg Roach // A short place name - no need to abbreviate 196a25f0a04SGreg Roach return $this->getFullName(); 197*b2ce94c6SRico Sonntag } 198*b2ce94c6SRico Sonntag 199a25f0a04SGreg Roach // Abbreviate the place name, for lists 20064b16745SGreg Roach if ($this->tree->getPreference('SHOW_PEDIGREE_PLACES_SUFFIX')) { 201a25f0a04SGreg Roach // The *last* $SHOW_PEDIGREE_PLACES components 202a25f0a04SGreg Roach $short_name = implode(self::GEDCOM_SEPARATOR, array_slice($this->gedcom_place, -$SHOW_PEDIGREE_PLACES)); 203a25f0a04SGreg Roach } else { 204a25f0a04SGreg Roach // The *first* $SHOW_PEDIGREE_PLACES components 205a25f0a04SGreg Roach $short_name = implode(self::GEDCOM_SEPARATOR, array_slice($this->gedcom_place, 0, $SHOW_PEDIGREE_PLACES)); 206a25f0a04SGreg Roach } 207c1010edaSGreg Roach 208a25f0a04SGreg Roach // Add a tool-tip showing the full name 209d53324c9SGreg Roach return '<span title="' . e($this->getGedcomName()) . '" dir="auto">' . e($short_name) . '</span>'; 210a25f0a04SGreg Roach } 211a25f0a04SGreg Roach 212a25f0a04SGreg Roach /** 213661f69c0SDavid Drury * For the Place hierarchy "list all" option 214a25f0a04SGreg Roach * 215a25f0a04SGreg Roach * @return string 216a25f0a04SGreg Roach */ 2178f53f488SRico Sonntag public function getReverseName(): string 218c1010edaSGreg Roach { 21913abd6f3SGreg Roach $tmp = []; 220a25f0a04SGreg Roach foreach (array_reverse($this->gedcom_place) as $place) { 221d53324c9SGreg Roach $tmp[] = '<span dir="auto">' . e($place) . '</span>'; 222a25f0a04SGreg Roach } 223a25f0a04SGreg Roach 224a25f0a04SGreg Roach return implode(I18N::$list_separator, $tmp); 225a25f0a04SGreg Roach } 226a25f0a04SGreg Roach 227a25f0a04SGreg Roach /** 22876692c8bSGreg Roach * Fetch all places from the database. 22976692c8bSGreg Roach * 23084caa210SGreg Roach * @param Tree $tree 231c1010edaSGreg Roach * 2324080d558SGreg Roach * @return Place[] 233a25f0a04SGreg Roach */ 2348f53f488SRico Sonntag public static function allPlaces(Tree $tree): array 235c1010edaSGreg Roach { 23613abd6f3SGreg Roach $places = []; 237a25f0a04SGreg Roach $rows = 238a25f0a04SGreg Roach Database::prepare( 239e5588fb0SGreg 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)" . 240a25f0a04SGreg Roach " FROM `##places` AS p1" . 241a25f0a04SGreg Roach " LEFT JOIN `##places` AS p2 ON (p1.p_parent_id = p2.p_id)" . 242a25f0a04SGreg Roach " LEFT JOIN `##places` AS p3 ON (p2.p_parent_id = p3.p_id)" . 243a25f0a04SGreg Roach " LEFT JOIN `##places` AS p4 ON (p3.p_parent_id = p4.p_id)" . 244a25f0a04SGreg Roach " LEFT JOIN `##places` AS p5 ON (p4.p_parent_id = p5.p_id)" . 245a25f0a04SGreg Roach " LEFT JOIN `##places` AS p6 ON (p5.p_parent_id = p6.p_id)" . 246a25f0a04SGreg Roach " LEFT JOIN `##places` AS p7 ON (p6.p_parent_id = p7.p_id)" . 247a25f0a04SGreg Roach " LEFT JOIN `##places` AS p8 ON (p7.p_parent_id = p8.p_id)" . 248a25f0a04SGreg Roach " LEFT JOIN `##places` AS p9 ON (p8.p_parent_id = p9.p_id)" . 24984caa210SGreg Roach " WHERE p1.p_file = :tree_id" . 250288566a3SGreg Roach " ORDER BY CONCAT_WS(', ', p9.p_place, p8.p_place, p7.p_place, p6.p_place, p5.p_place, p4.p_place, p3.p_place, p2.p_place, p1.p_place) COLLATE :collate" 251a25f0a04SGreg Roach ) 25213abd6f3SGreg Roach ->execute([ 253288566a3SGreg Roach 'tree_id' => $tree->getTreeId(), 25481088023SGreg Roach 'collate' => 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 263a25f0a04SGreg Roach /** 26476692c8bSGreg Roach * Search for a place name. 26576692c8bSGreg Roach * 266a25f0a04SGreg Roach * @param string $filter 26784caa210SGreg Roach * @param Tree $tree 268c1010edaSGreg Roach * 2694080d558SGreg Roach * @return Place[] 270a25f0a04SGreg Roach */ 2718f53f488SRico Sonntag public static function findPlaces($filter, Tree $tree): array 272c1010edaSGreg Roach { 27313abd6f3SGreg Roach $places = []; 274a25f0a04SGreg Roach $rows = 275a25f0a04SGreg Roach Database::prepare( 276e5588fb0SGreg 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)" . 277a25f0a04SGreg Roach " FROM `##places` AS p1" . 278a25f0a04SGreg Roach " LEFT JOIN `##places` AS p2 ON (p1.p_parent_id = p2.p_id)" . 279a25f0a04SGreg Roach " LEFT JOIN `##places` AS p3 ON (p2.p_parent_id = p3.p_id)" . 280a25f0a04SGreg Roach " LEFT JOIN `##places` AS p4 ON (p3.p_parent_id = p4.p_id)" . 281a25f0a04SGreg Roach " LEFT JOIN `##places` AS p5 ON (p4.p_parent_id = p5.p_id)" . 282a25f0a04SGreg Roach " LEFT JOIN `##places` AS p6 ON (p5.p_parent_id = p6.p_id)" . 283a25f0a04SGreg Roach " LEFT JOIN `##places` AS p7 ON (p6.p_parent_id = p7.p_id)" . 284a25f0a04SGreg Roach " LEFT JOIN `##places` AS p8 ON (p7.p_parent_id = p8.p_id)" . 285a25f0a04SGreg Roach " LEFT JOIN `##places` AS p9 ON (p8.p_parent_id = p9.p_id)" . 28684caa210SGreg 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" . 28781088023SGreg 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" 28813abd6f3SGreg Roach )->execute([ 28984caa210SGreg Roach 'filter_1' => preg_quote($filter), 29084caa210SGreg Roach 'filter_2' => preg_quote($filter), 29181088023SGreg Roach 'tree_id' => $tree->getTreeId(), 29281088023SGreg Roach 'collation' => I18N::collation(), 29313abd6f3SGreg Roach ])->fetchOneColumn(); 294a25f0a04SGreg Roach foreach ($rows as $row) { 29506ef8e02SGreg Roach $places[] = new self($row, $tree); 296a25f0a04SGreg Roach } 297cbc1590aSGreg Roach 298a25f0a04SGreg Roach return $places; 299a25f0a04SGreg Roach } 300a25f0a04SGreg Roach} 301