xref: /webtrees/app/Place.php (revision e8e7866be1a6002e498a9d6b7d1257c59d7e3484)
1<?php
2namespace Fisharebest\Webtrees;
3
4/**
5 * webtrees: online genealogy
6 * Copyright (C) 2015 webtrees development team
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19/**
20 * Class Place - Gedcom Place functionality.
21 */
22class Place {
23	const GEDCOM_SEPARATOR = ', ';
24
25	/**
26	 * @var string[] e.g. array('Westminster', 'London', 'England')
27	 */
28	private $gedcom_place;
29
30	/**
31	 * @var Tree We may have the same place name in different trees
32	 */
33	private $tree;
34
35	/**
36	 * @param string $gedcom_place
37	 * @param Tree   $tree
38	 */
39	public function __construct($gedcom_place, Tree $tree) {
40		if ($gedcom_place) {
41			$this->gedcom_place = explode(self::GEDCOM_SEPARATOR, $gedcom_place);
42		} else {
43			// Empty => "Top level"
44			$this->gedcom_place = array();
45		}
46		$this->tree = $tree;
47	}
48
49	/**
50	 * @return int
51	 */
52	public function getPlaceId() {
53		$place_id = 0;
54		foreach (array_reverse($this->gedcom_place) as $place) {
55			$place_id = Database::prepare(
56				"SELECT SQL_CACHE p_id FROM `##places` WHERE p_parent_id = :parent_id AND p_place = :place AND p_file = :tree_id"
57			)->execute(array(
58				'parent_id' => $place_id,
59				'place'     => $place,
60				'tree_id'   => $this->tree->getTreeId(),
61			))->fetchOne();
62		}
63
64		return $place_id;
65	}
66
67	/**
68	 * @return Place
69	 */
70	public function getParentPlace() {
71		return new self(implode(self::GEDCOM_SEPARATOR, array_slice($this->gedcom_place, 1)), $this->tree);
72	}
73
74	/**
75	 * @return Place[]
76	 */
77	public function getChildPlaces() {
78		$children = array();
79		if ($this->getPlaceId()) {
80			$parent_text = self::GEDCOM_SEPARATOR . $this->getGedcomName();
81		} else {
82			$parent_text = '';
83		}
84
85		$rows = Database::prepare(
86			"SELECT SQL_CACHE p_place FROM `##places`" .
87			" WHERE p_parent_id = :parent_id AND p_file = :tree_id" .
88			" ORDER BY p_place COLLATE :collation"
89		)->execute(array(
90			'parent_id' => $this->getPlaceId(),
91			'tree_id'   => $this->tree->getTreeId(),
92			'collation' => I18N::collation(),
93		))->fetchOneColumn();
94		foreach ($rows as $row) {
95			$children[] = new self($row . $parent_text, $this->tree);
96		}
97
98		return $children;
99	}
100
101	/**
102	 * @return string
103	 */
104	public function getURL() {
105		if (Auth::isSearchEngine()) {
106			return '#';
107		} else {
108			$url = 'placelist.php';
109			foreach (array_reverse($this->gedcom_place) as $n => $place) {
110				$url .= $n ? '&amp;' : '?';
111				$url .= 'parent%5B%5D=' . rawurlencode($place);
112			}
113			$url .= '&amp;ged=' . $this->tree->getNameHtml();
114
115			return $url;
116		}
117	}
118
119	/**
120	 * @return string
121	 */
122	public function getGedcomName() {
123		return implode(self::GEDCOM_SEPARATOR, $this->gedcom_place);
124	}
125
126	/**
127	 * @return string
128	 */
129	public function getPlaceName() {
130		$place = reset($this->gedcom_place);
131
132		return $place ? '<span dir="auto">' . Filter::escapeHtml($place) . '</span>' : I18N::translate('unknown');
133	}
134
135	/**
136	 * @return bool
137	 */
138	public function isEmpty() {
139		return empty($this->gedcom_place);
140	}
141
142	/**
143	 * @return string
144	 */
145	public function getFullName() {
146		if (true) {
147			// If a place hierarchy is a single entity
148			return '<span dir="auto">' . Filter::escapeHtml(implode(I18N::$list_separator, $this->gedcom_place)) . '</span>';
149		} else {
150			// If a place hierarchy is a list of distinct items
151			$tmp = array();
152			foreach ($this->gedcom_place as $place) {
153				$tmp[] = '<span dir="auto">' . Filter::escapeHtml($place) . '</span>';
154			}
155
156			return implode(I18N::$list_separator, $tmp);
157		}
158	}
159
160	/**
161	 * For lists and charts, where the full name won’t fit.
162	 *
163	 * @return string
164	 */
165	public function getShortName() {
166		$SHOW_PEDIGREE_PLACES = $this->tree->getPreference('SHOW_PEDIGREE_PLACES');
167
168		if ($SHOW_PEDIGREE_PLACES >= count($this->gedcom_place)) {
169			// A short place name - no need to abbreviate
170			return $this->getFullName();
171		} else {
172			// Abbreviate the place name, for lists
173			if ($this->tree->getPreference('SHOW_PEDIGREE_PLACES_SUFFIX')) {
174				// The *last* $SHOW_PEDIGREE_PLACES components
175				$short_name = implode(self::GEDCOM_SEPARATOR, array_slice($this->gedcom_place, -$SHOW_PEDIGREE_PLACES));
176			} else {
177				// The *first* $SHOW_PEDIGREE_PLACES components
178				$short_name = implode(self::GEDCOM_SEPARATOR, array_slice($this->gedcom_place, 0, $SHOW_PEDIGREE_PLACES));
179			}
180			// Add a tool-tip showing the full name
181			return '<span title="' . Filter::escapeHtml($this->getGedcomName()) . '" dir="auto">' . Filter::escapeHtml($short_name) . '</span>';
182		}
183	}
184
185	/**
186	 * For the "view all" option of placelist.php and find.php
187	 *
188	 * @return string
189	 */
190	public function getReverseName() {
191		$tmp = array();
192		foreach (array_reverse($this->gedcom_place) as $place) {
193			$tmp[] = '<span dir="auto">' . Filter::escapeHtml($place) . '</span>';
194		}
195
196		return implode(I18N::$list_separator, $tmp);
197	}
198
199	/**
200	 * @param Tree $tree
201	 *
202	 * @return string[]
203	 */
204	public static function allPlaces(Tree $tree) {
205		$places = array();
206		$rows   =
207			Database::prepare(
208				"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)" .
209				" FROM      `##places` AS p1" .
210				" LEFT JOIN `##places` AS p2 ON (p1.p_parent_id = p2.p_id)" .
211				" LEFT JOIN `##places` AS p3 ON (p2.p_parent_id = p3.p_id)" .
212				" LEFT JOIN `##places` AS p4 ON (p3.p_parent_id = p4.p_id)" .
213				" LEFT JOIN `##places` AS p5 ON (p4.p_parent_id = p5.p_id)" .
214				" LEFT JOIN `##places` AS p6 ON (p5.p_parent_id = p6.p_id)" .
215				" LEFT JOIN `##places` AS p7 ON (p6.p_parent_id = p7.p_id)" .
216				" LEFT JOIN `##places` AS p8 ON (p7.p_parent_id = p8.p_id)" .
217				" LEFT JOIN `##places` AS p9 ON (p8.p_parent_id = p9.p_id)" .
218				" WHERE p1.p_file = :tree_id" .
219				" 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"
220			)
221			->execute(array(
222				'tree_id' => $tree->getTreeId(),
223				'collate' => I18N::collation(),
224			))->fetchOneColumn();
225		foreach ($rows as $row) {
226			$places[] = new self($row, $tree);
227		}
228
229		return $places;
230	}
231
232	/**
233	 * @param string  $filter
234	 * @param Tree    $tree
235	 *
236	 * @return Place[]
237	 */
238	public static function findPlaces($filter, Tree $tree) {
239		$places = array();
240		$rows   =
241			Database::prepare(
242				"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)" .
243				" FROM      `##places` AS p1" .
244				" LEFT JOIN `##places` AS p2 ON (p1.p_parent_id = p2.p_id)" .
245				" LEFT JOIN `##places` AS p3 ON (p2.p_parent_id = p3.p_id)" .
246				" LEFT JOIN `##places` AS p4 ON (p3.p_parent_id = p4.p_id)" .
247				" LEFT JOIN `##places` AS p5 ON (p4.p_parent_id = p5.p_id)" .
248				" LEFT JOIN `##places` AS p6 ON (p5.p_parent_id = p6.p_id)" .
249				" LEFT JOIN `##places` AS p7 ON (p6.p_parent_id = p7.p_id)" .
250				" LEFT JOIN `##places` AS p8 ON (p7.p_parent_id = p8.p_id)" .
251				" LEFT JOIN `##places` AS p9 ON (p8.p_parent_id = p9.p_id)" .
252				" 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" .
253				" 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"
254			)->execute(array(
255				'filter_1'  => preg_quote($filter),
256				'filter_2'  => preg_quote($filter),
257				'tree_id'   => $tree->getTreeId(),
258				'collation' => I18N::collation(),
259			))->fetchOneColumn();
260		foreach ($rows as $row) {
261			$places[] = new self($row, $tree);
262		}
263
264		return $places;
265	}
266}
267