xref: /webtrees/app/Place.php (revision b68caec60900b4d11ce1bad44ffcce337629d086)
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