1a25f0a04SGreg Roach<?php 2a25f0a04SGreg Roach/** 3a25f0a04SGreg Roach * webtrees: online genealogy 46bdf7674SGreg Roach * Copyright (C) 2017 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 */ 21a25f0a04SGreg Roachclass Place { 22a25f0a04SGreg Roach const GEDCOM_SEPARATOR = ', '; 2384caa210SGreg Roach 2476692c8bSGreg Roach /** @var string[] e.g. array('Westminster', 'London', 'England') */ 2584caa210SGreg Roach private $gedcom_place; 2684caa210SGreg Roach 2776692c8bSGreg Roach /** @var Tree We may have the same place name in different trees. */ 2884caa210SGreg Roach private $tree; 29a25f0a04SGreg Roach 30a25f0a04SGreg Roach /** 3176692c8bSGreg Roach * Create a place. 3276692c8bSGreg Roach * 33a25f0a04SGreg Roach * @param string $gedcom_place 3484caa210SGreg Roach * @param Tree $tree 35a25f0a04SGreg Roach */ 362769ecbeSGreg Roach public function __construct($gedcom_place, Tree $tree) { 37a25f0a04SGreg Roach if ($gedcom_place) { 38a25f0a04SGreg Roach $this->gedcom_place = explode(self::GEDCOM_SEPARATOR, $gedcom_place); 39a25f0a04SGreg Roach } else { 40a25f0a04SGreg Roach // Empty => "Top level" 4113abd6f3SGreg Roach $this->gedcom_place = []; 42a25f0a04SGreg Roach } 4364b16745SGreg Roach $this->tree = $tree; 44a25f0a04SGreg Roach } 45a25f0a04SGreg Roach 46a25f0a04SGreg Roach /** 4716d0b7f7SRico Sonntag * Extract the country (last part) of a place name. 4816d0b7f7SRico Sonntag * 4916d0b7f7SRico Sonntag * @param string $place - e.g. "London, England" 5016d0b7f7SRico Sonntag * 5116d0b7f7SRico Sonntag * @return string - e.g. "England" 5216d0b7f7SRico Sonntag */ 5316d0b7f7SRico Sonntag public function lastPart() 5416d0b7f7SRico Sonntag { 5516d0b7f7SRico Sonntag return end($this->gedcom_place); 5616d0b7f7SRico Sonntag } 5716d0b7f7SRico Sonntag 5816d0b7f7SRico Sonntag /** 5976692c8bSGreg Roach * Get the identifier for a place. 6076692c8bSGreg Roach * 61cbc1590aSGreg Roach * @return int 62a25f0a04SGreg Roach */ 63a25f0a04SGreg Roach public function getPlaceId() { 64a25f0a04SGreg Roach $place_id = 0; 65a25f0a04SGreg Roach foreach (array_reverse($this->gedcom_place) as $place) { 66a25f0a04SGreg Roach $place_id = Database::prepare( 67a25f0a04SGreg Roach "SELECT SQL_CACHE p_id FROM `##places` WHERE p_parent_id = :parent_id AND p_place = :place AND p_file = :tree_id" 6813abd6f3SGreg Roach )->execute([ 69a25f0a04SGreg Roach 'parent_id' => $place_id, 70a25f0a04SGreg Roach 'place' => $place, 7164b16745SGreg Roach 'tree_id' => $this->tree->getTreeId(), 7213abd6f3SGreg Roach ])->fetchOne(); 73a25f0a04SGreg Roach } 74a25f0a04SGreg Roach 75a25f0a04SGreg Roach return $place_id; 76a25f0a04SGreg Roach } 77a25f0a04SGreg Roach 78a25f0a04SGreg Roach /** 7976692c8bSGreg Roach * Get the higher level place. 8076692c8bSGreg Roach * 81a25f0a04SGreg Roach * @return Place 82a25f0a04SGreg Roach */ 83a25f0a04SGreg Roach public function getParentPlace() { 8406ef8e02SGreg Roach return new self(implode(self::GEDCOM_SEPARATOR, array_slice($this->gedcom_place, 1)), $this->tree); 85a25f0a04SGreg Roach } 86a25f0a04SGreg Roach 87a25f0a04SGreg Roach /** 8876692c8bSGreg Roach * Get the lower level places. 8976692c8bSGreg Roach * 90a25f0a04SGreg Roach * @return Place[] 91a25f0a04SGreg Roach */ 92a25f0a04SGreg Roach public function getChildPlaces() { 9313abd6f3SGreg Roach $children = []; 94a25f0a04SGreg Roach if ($this->getPlaceId()) { 95a25f0a04SGreg Roach $parent_text = self::GEDCOM_SEPARATOR . $this->getGedcomName(); 96a25f0a04SGreg Roach } else { 97a25f0a04SGreg Roach $parent_text = ''; 98a25f0a04SGreg Roach } 99a25f0a04SGreg Roach 100a25f0a04SGreg Roach $rows = Database::prepare( 101a25f0a04SGreg Roach "SELECT SQL_CACHE p_place FROM `##places`" . 102a25f0a04SGreg Roach " WHERE p_parent_id = :parent_id AND p_file = :tree_id" . 103a25f0a04SGreg Roach " ORDER BY p_place COLLATE :collation" 10413abd6f3SGreg Roach )->execute([ 105a25f0a04SGreg Roach 'parent_id' => $this->getPlaceId(), 10664b16745SGreg Roach 'tree_id' => $this->tree->getTreeId(), 10781088023SGreg Roach 'collation' => I18N::collation(), 10813abd6f3SGreg Roach ])->fetchOneColumn(); 109a25f0a04SGreg Roach foreach ($rows as $row) { 11006ef8e02SGreg Roach $children[] = new self($row . $parent_text, $this->tree); 111a25f0a04SGreg Roach } 112a25f0a04SGreg Roach 113a25f0a04SGreg Roach return $children; 114a25f0a04SGreg Roach } 115a25f0a04SGreg Roach 116a25f0a04SGreg Roach /** 11776692c8bSGreg Roach * Create a URL to the place-hierarchy page. 11876692c8bSGreg Roach * 119a25f0a04SGreg Roach * @return string 120a25f0a04SGreg Roach */ 121a25f0a04SGreg Roach public function getURL() { 122a25f0a04SGreg Roach $url = 'placelist.php'; 123a25f0a04SGreg Roach foreach (array_reverse($this->gedcom_place) as $n => $place) { 124a25f0a04SGreg Roach $url .= $n ? '&' : '?'; 125a25f0a04SGreg Roach $url .= 'parent%5B%5D=' . rawurlencode($place); 126a25f0a04SGreg Roach } 1275bb4de4dSGreg Roach $url .= '&ged=' . $this->tree->getNameUrl(); 128a25f0a04SGreg Roach 129a25f0a04SGreg Roach return $url; 130a25f0a04SGreg Roach } 131a25f0a04SGreg Roach 132a25f0a04SGreg Roach /** 13376692c8bSGreg Roach * Format this name for GEDCOM data. 13476692c8bSGreg Roach * 135a25f0a04SGreg Roach * @return string 136a25f0a04SGreg Roach */ 137a25f0a04SGreg Roach public function getGedcomName() { 138a25f0a04SGreg Roach return implode(self::GEDCOM_SEPARATOR, $this->gedcom_place); 139a25f0a04SGreg Roach } 140a25f0a04SGreg Roach 141a25f0a04SGreg Roach /** 14276692c8bSGreg Roach * Format this place for display on screen. 14376692c8bSGreg Roach * 144a25f0a04SGreg Roach * @return string 145a25f0a04SGreg Roach */ 146a25f0a04SGreg Roach public function getPlaceName() { 147a25f0a04SGreg Roach $place = reset($this->gedcom_place); 148a25f0a04SGreg Roach 149cc5ab399SGreg Roach return $place ? '<span dir="auto">' . Html::escape($place) . '</span>' : I18N::translate('unknown'); 150a25f0a04SGreg Roach } 151a25f0a04SGreg Roach 152a25f0a04SGreg Roach /** 15376692c8bSGreg Roach * Is this a null/empty/missing/invalid place? 15476692c8bSGreg Roach * 155a25f0a04SGreg Roach * @return bool 156a25f0a04SGreg Roach */ 157a25f0a04SGreg Roach public function isEmpty() { 158a25f0a04SGreg Roach return empty($this->gedcom_place); 159a25f0a04SGreg Roach } 160a25f0a04SGreg Roach 161a25f0a04SGreg Roach /** 16276692c8bSGreg Roach * Generate the place name for display, including the full hierarchy. 16376692c8bSGreg Roach * 164a25f0a04SGreg Roach * @return string 165a25f0a04SGreg Roach */ 166a25f0a04SGreg Roach public function getFullName() { 167a25f0a04SGreg Roach if (true) { 168a25f0a04SGreg Roach // If a place hierarchy is a single entity 169cc5ab399SGreg Roach return '<span dir="auto">' . Html::escape(implode(I18N::$list_separator, $this->gedcom_place)) . '</span>'; 170a25f0a04SGreg Roach } else { 171a25f0a04SGreg Roach // If a place hierarchy is a list of distinct items 17213abd6f3SGreg Roach $tmp = []; 173a25f0a04SGreg Roach foreach ($this->gedcom_place as $place) { 174cc5ab399SGreg Roach $tmp[] = '<span dir="auto">' . Html::escape($place) . '</span>'; 175a25f0a04SGreg Roach } 176a25f0a04SGreg Roach 177a25f0a04SGreg Roach return implode(I18N::$list_separator, $tmp); 178a25f0a04SGreg Roach } 179a25f0a04SGreg Roach } 180a25f0a04SGreg Roach 181a25f0a04SGreg Roach /** 182a25f0a04SGreg Roach * For lists and charts, where the full name won’t fit. 183a25f0a04SGreg Roach * 184a25f0a04SGreg Roach * @return string 185a25f0a04SGreg Roach */ 186a25f0a04SGreg Roach public function getShortName() { 18764b16745SGreg Roach $SHOW_PEDIGREE_PLACES = $this->tree->getPreference('SHOW_PEDIGREE_PLACES'); 188a25f0a04SGreg Roach 189a25f0a04SGreg Roach if ($SHOW_PEDIGREE_PLACES >= count($this->gedcom_place)) { 190a25f0a04SGreg Roach // A short place name - no need to abbreviate 191a25f0a04SGreg Roach return $this->getFullName(); 192a25f0a04SGreg Roach } else { 193a25f0a04SGreg Roach // Abbreviate the place name, for lists 19464b16745SGreg Roach if ($this->tree->getPreference('SHOW_PEDIGREE_PLACES_SUFFIX')) { 195a25f0a04SGreg Roach // The *last* $SHOW_PEDIGREE_PLACES components 196a25f0a04SGreg Roach $short_name = implode(self::GEDCOM_SEPARATOR, array_slice($this->gedcom_place, -$SHOW_PEDIGREE_PLACES)); 197a25f0a04SGreg Roach } else { 198a25f0a04SGreg Roach // The *first* $SHOW_PEDIGREE_PLACES components 199a25f0a04SGreg Roach $short_name = implode(self::GEDCOM_SEPARATOR, array_slice($this->gedcom_place, 0, $SHOW_PEDIGREE_PLACES)); 200a25f0a04SGreg Roach } 201a25f0a04SGreg Roach // Add a tool-tip showing the full name 202cc5ab399SGreg Roach return '<span title="' . Html::escape($this->getGedcomName()) . '" dir="auto">' . Html::escape($short_name) . '</span>'; 203a25f0a04SGreg Roach } 204a25f0a04SGreg Roach } 205a25f0a04SGreg Roach 206a25f0a04SGreg Roach /** 20715d603e7SGreg Roach * For the "view all" option of placelist.phpp 208a25f0a04SGreg Roach * 209a25f0a04SGreg Roach * @return string 210a25f0a04SGreg Roach */ 211a25f0a04SGreg Roach public function getReverseName() { 21213abd6f3SGreg Roach $tmp = []; 213a25f0a04SGreg Roach foreach (array_reverse($this->gedcom_place) as $place) { 214cc5ab399SGreg Roach $tmp[] = '<span dir="auto">' . Html::escape($place) . '</span>'; 215a25f0a04SGreg Roach } 216a25f0a04SGreg Roach 217a25f0a04SGreg Roach return implode(I18N::$list_separator, $tmp); 218a25f0a04SGreg Roach } 219a25f0a04SGreg Roach 220a25f0a04SGreg Roach /** 22176692c8bSGreg Roach * Fetch all places from the database. 22276692c8bSGreg Roach * 22384caa210SGreg Roach * @param Tree $tree 224a25f0a04SGreg Roach * 225*4080d558SGreg Roach * @return Place[] 226a25f0a04SGreg Roach */ 227703a96f4SGreg Roach public static function allPlaces(Tree $tree) { 22813abd6f3SGreg Roach $places = []; 229a25f0a04SGreg Roach $rows = 230a25f0a04SGreg Roach Database::prepare( 231a25f0a04SGreg Roach "SELECT SQL_CACHE 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)" . 232a25f0a04SGreg Roach " FROM `##places` AS p1" . 233a25f0a04SGreg Roach " LEFT JOIN `##places` AS p2 ON (p1.p_parent_id = p2.p_id)" . 234a25f0a04SGreg Roach " LEFT JOIN `##places` AS p3 ON (p2.p_parent_id = p3.p_id)" . 235a25f0a04SGreg Roach " LEFT JOIN `##places` AS p4 ON (p3.p_parent_id = p4.p_id)" . 236a25f0a04SGreg Roach " LEFT JOIN `##places` AS p5 ON (p4.p_parent_id = p5.p_id)" . 237a25f0a04SGreg Roach " LEFT JOIN `##places` AS p6 ON (p5.p_parent_id = p6.p_id)" . 238a25f0a04SGreg Roach " LEFT JOIN `##places` AS p7 ON (p6.p_parent_id = p7.p_id)" . 239a25f0a04SGreg Roach " LEFT JOIN `##places` AS p8 ON (p7.p_parent_id = p8.p_id)" . 240a25f0a04SGreg Roach " LEFT JOIN `##places` AS p9 ON (p8.p_parent_id = p9.p_id)" . 24184caa210SGreg Roach " WHERE p1.p_file = :tree_id" . 242288566a3SGreg 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" 243a25f0a04SGreg Roach ) 24413abd6f3SGreg Roach ->execute([ 245288566a3SGreg Roach 'tree_id' => $tree->getTreeId(), 24681088023SGreg Roach 'collate' => I18N::collation(), 24713abd6f3SGreg Roach ])->fetchOneColumn(); 248a25f0a04SGreg Roach foreach ($rows as $row) { 24906ef8e02SGreg Roach $places[] = new self($row, $tree); 250a25f0a04SGreg Roach } 251cbc1590aSGreg Roach 252a25f0a04SGreg Roach return $places; 253a25f0a04SGreg Roach } 254a25f0a04SGreg Roach 255a25f0a04SGreg Roach /** 25676692c8bSGreg Roach * Search for a place name. 25776692c8bSGreg Roach * 258a25f0a04SGreg Roach * @param string $filter 25984caa210SGreg Roach * @param Tree $tree 260a25f0a04SGreg Roach * 261*4080d558SGreg Roach * @return Place[] 262a25f0a04SGreg Roach */ 2632769ecbeSGreg Roach public static function findPlaces($filter, Tree $tree) { 26413abd6f3SGreg Roach $places = []; 265a25f0a04SGreg Roach $rows = 266a25f0a04SGreg Roach Database::prepare( 267a25f0a04SGreg Roach "SELECT SQL_CACHE 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)" . 268a25f0a04SGreg Roach " FROM `##places` AS p1" . 269a25f0a04SGreg Roach " LEFT JOIN `##places` AS p2 ON (p1.p_parent_id = p2.p_id)" . 270a25f0a04SGreg Roach " LEFT JOIN `##places` AS p3 ON (p2.p_parent_id = p3.p_id)" . 271a25f0a04SGreg Roach " LEFT JOIN `##places` AS p4 ON (p3.p_parent_id = p4.p_id)" . 272a25f0a04SGreg Roach " LEFT JOIN `##places` AS p5 ON (p4.p_parent_id = p5.p_id)" . 273a25f0a04SGreg Roach " LEFT JOIN `##places` AS p6 ON (p5.p_parent_id = p6.p_id)" . 274a25f0a04SGreg Roach " LEFT JOIN `##places` AS p7 ON (p6.p_parent_id = p7.p_id)" . 275a25f0a04SGreg Roach " LEFT JOIN `##places` AS p8 ON (p7.p_parent_id = p8.p_id)" . 276a25f0a04SGreg Roach " LEFT JOIN `##places` AS p9 ON (p8.p_parent_id = p9.p_id)" . 27784caa210SGreg 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" . 27881088023SGreg 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" 27913abd6f3SGreg Roach )->execute([ 28084caa210SGreg Roach 'filter_1' => preg_quote($filter), 28184caa210SGreg Roach 'filter_2' => preg_quote($filter), 28281088023SGreg Roach 'tree_id' => $tree->getTreeId(), 28381088023SGreg Roach 'collation' => I18N::collation(), 28413abd6f3SGreg Roach ])->fetchOneColumn(); 285a25f0a04SGreg Roach foreach ($rows as $row) { 28606ef8e02SGreg Roach $places[] = new self($row, $tree); 287a25f0a04SGreg Roach } 288cbc1590aSGreg Roach 289a25f0a04SGreg Roach return $places; 290a25f0a04SGreg Roach } 291a25f0a04SGreg Roach} 292