1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * webtrees: online genealogy |
||||
5 | * Copyright (C) 2025 webtrees development team |
||||
6 | * This program is free software: you can redistribute it and/or modify |
||||
7 | * it under the terms of the GNU General Public License as published by |
||||
8 | * the Free Software Foundation, either version 3 of the License, or |
||||
9 | * (at your option) any later version. |
||||
10 | * This program is distributed in the hope that it will be useful, |
||||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
13 | * GNU General Public License for more details. |
||||
14 | * You should have received a copy of the GNU General Public License |
||||
15 | * along with this program. If not, see <https://www.gnu.org/licenses/>. |
||||
16 | */ |
||||
17 | |||||
18 | declare(strict_types=1); |
||||
19 | |||||
20 | namespace Fisharebest\Webtrees; |
||||
21 | |||||
22 | use Fisharebest\Webtrees\Module\ModuleInterface; |
||||
23 | use Fisharebest\Webtrees\Module\ModuleListInterface; |
||||
24 | use Fisharebest\Webtrees\Module\PlaceHierarchyListModule; |
||||
0 ignored issues
–
show
|
|||||
25 | use Fisharebest\Webtrees\Services\ModuleService; |
||||
0 ignored issues
–
show
The type
Fisharebest\Webtrees\Services\ModuleService was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
26 | use Illuminate\Support\Collection; |
||||
27 | |||||
28 | use function e; |
||||
29 | use function is_object; |
||||
30 | use function preg_split; |
||||
31 | use function strip_tags; |
||||
32 | use function trim; |
||||
33 | |||||
34 | use const PREG_SPLIT_NO_EMPTY; |
||||
35 | |||||
36 | /** |
||||
37 | * A GEDCOM place (PLAC) object. |
||||
38 | */ |
||||
39 | class Place |
||||
40 | { |
||||
41 | // "Westminster, London, England" |
||||
42 | private string $place_name; |
||||
43 | |||||
44 | /** @var Collection<int,string> The parts of a place name, e.g. ["Westminster", "London", "England"] */ |
||||
45 | private Collection $parts; |
||||
46 | |||||
47 | private Tree $tree; |
||||
48 | |||||
49 | /** |
||||
50 | * Create a place. |
||||
51 | * |
||||
52 | * @param string $place_name |
||||
53 | * @param Tree $tree |
||||
54 | */ |
||||
55 | public function __construct(string $place_name, Tree $tree) |
||||
56 | { |
||||
57 | // Ignore any empty parts in place names such as "Village, , , Country". |
||||
58 | $place_name = trim($place_name); |
||||
59 | $this->parts = new Collection(preg_split(Gedcom::PLACE_SEPARATOR_REGEX, $place_name, -1, PREG_SPLIT_NO_EMPTY)); |
||||
0 ignored issues
–
show
preg_split(Fisharebest\W...1, PREG_SPLIT_NO_EMPTY) of type array<mixed,array>|string[] is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $items of Illuminate\Support\Collection::__construct() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
60 | |||||
61 | // Rebuild the placename in the correct format. |
||||
62 | $this->place_name = $this->parts->implode(Gedcom::PLACE_SEPARATOR); |
||||
63 | |||||
64 | $this->tree = $tree; |
||||
65 | } |
||||
66 | |||||
67 | /** |
||||
68 | * Find a place by its ID. |
||||
69 | * |
||||
70 | * @param int $id |
||||
71 | * @param Tree $tree |
||||
72 | * |
||||
73 | * @return Place |
||||
74 | */ |
||||
75 | public static function find(int $id, Tree $tree): Place |
||||
76 | { |
||||
77 | $parts = new Collection(); |
||||
78 | |||||
79 | while ($id !== 0) { |
||||
80 | $row = DB::table('places') |
||||
0 ignored issues
–
show
|
|||||
81 | ->where('p_file', '=', $tree->id()) |
||||
82 | ->where('p_id', '=', $id) |
||||
83 | ->first(); |
||||
84 | |||||
85 | if (is_object($row)) { |
||||
86 | $id = (int) $row->p_parent_id; |
||||
87 | $parts->add($row->p_place); |
||||
88 | } else { |
||||
89 | $id = 0; |
||||
90 | } |
||||
91 | } |
||||
92 | |||||
93 | $place_name = $parts->implode(Gedcom::PLACE_SEPARATOR); |
||||
94 | |||||
95 | return new Place($place_name, $tree); |
||||
96 | } |
||||
97 | |||||
98 | /** |
||||
99 | * Get the higher level place. |
||||
100 | * |
||||
101 | * @return Place |
||||
102 | */ |
||||
103 | public function parent(): Place |
||||
104 | { |
||||
105 | return new self($this->parts->slice(1)->implode(Gedcom::PLACE_SEPARATOR), $this->tree); |
||||
106 | } |
||||
107 | |||||
108 | /** |
||||
109 | * The database row that contains this place. |
||||
110 | * Note that due to database collation, both "Quebec" and "Québec" will share the same row. |
||||
111 | * |
||||
112 | * @return int |
||||
113 | */ |
||||
114 | public function id(): int |
||||
115 | { |
||||
116 | return Registry::cache()->array()->remember('place-' . $this->place_name, function (): int { |
||||
117 | // The "top-level" place won't exist in the database. |
||||
118 | if ($this->parts->isEmpty()) { |
||||
119 | return 0; |
||||
120 | } |
||||
121 | |||||
122 | $parent_place_id = $this->parent()->id(); |
||||
123 | |||||
124 | $place_id = (int) DB::table('places') |
||||
125 | ->where('p_file', '=', $this->tree->id()) |
||||
126 | ->where('p_place', '=', mb_substr($this->parts->first(), 0, 120)) |
||||
127 | ->where('p_parent_id', '=', $parent_place_id) |
||||
128 | ->value('p_id'); |
||||
129 | |||||
130 | if ($place_id === 0) { |
||||
131 | $place = $this->parts->first(); |
||||
132 | |||||
133 | DB::table('places')->insert([ |
||||
134 | 'p_file' => $this->tree->id(), |
||||
135 | 'p_place' => mb_substr($place, 0, 120), |
||||
136 | 'p_parent_id' => $parent_place_id, |
||||
137 | 'p_std_soundex' => Soundex::russell($place), |
||||
0 ignored issues
–
show
The type
Fisharebest\Webtrees\Soundex was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
138 | 'p_dm_soundex' => Soundex::daitchMokotoff($place), |
||||
139 | ]); |
||||
140 | |||||
141 | $place_id = DB::lastInsertId(); |
||||
142 | } |
||||
143 | |||||
144 | return $place_id; |
||||
145 | }); |
||||
146 | } |
||||
147 | |||||
148 | /** |
||||
149 | * @return Tree |
||||
150 | */ |
||||
151 | public function tree(): Tree |
||||
152 | { |
||||
153 | return $this->tree; |
||||
154 | } |
||||
155 | |||||
156 | /** |
||||
157 | * Extract the locality (first parts) of a place name. |
||||
158 | * |
||||
159 | * @param int $n |
||||
160 | * |
||||
161 | * @return Collection<int,string> |
||||
162 | */ |
||||
163 | public function firstParts(int $n): Collection |
||||
164 | { |
||||
165 | return $this->parts->slice(0, $n); |
||||
166 | } |
||||
167 | |||||
168 | /** |
||||
169 | * Extract the country (last parts) of a place name. |
||||
170 | * |
||||
171 | * @param int $n |
||||
172 | * |
||||
173 | * @return Collection<int,string> |
||||
174 | */ |
||||
175 | public function lastParts(int $n): Collection |
||||
176 | { |
||||
177 | return $this->parts->slice(-$n); |
||||
178 | } |
||||
179 | |||||
180 | /** |
||||
181 | * Get the lower level places. |
||||
182 | * |
||||
183 | * @return array<Place> |
||||
184 | */ |
||||
185 | public function getChildPlaces(): array |
||||
186 | { |
||||
187 | if ($this->place_name !== '') { |
||||
188 | $parent_text = Gedcom::PLACE_SEPARATOR . $this->place_name; |
||||
189 | } else { |
||||
190 | $parent_text = ''; |
||||
191 | } |
||||
192 | |||||
193 | return DB::table('places') |
||||
194 | ->where('p_file', '=', $this->tree->id()) |
||||
195 | ->where('p_parent_id', '=', $this->id()) |
||||
196 | ->pluck('p_place') |
||||
197 | ->sort(I18N::comparator()) |
||||
0 ignored issues
–
show
The type
Fisharebest\Webtrees\I18N was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
198 | ->map(fn (string $place): Place => new self($place . $parent_text, $this->tree)) |
||||
199 | ->all(); |
||||
200 | } |
||||
201 | |||||
202 | /** |
||||
203 | * Create a URL to the place-hierarchy page. |
||||
204 | * |
||||
205 | * @return string |
||||
206 | */ |
||||
207 | public function url(): string |
||||
208 | { |
||||
209 | //find a module providing the place hierarchy |
||||
210 | $module = Registry::container()->get(ModuleService::class) |
||||
211 | ->findByComponent(ModuleListInterface::class, $this->tree, Auth::user()) |
||||
0 ignored issues
–
show
|
|||||
212 | ->first(static fn (ModuleInterface $module): bool => $module instanceof PlaceHierarchyListModule); |
||||
213 | |||||
214 | if ($module instanceof PlaceHierarchyListModule) { |
||||
215 | return $module->listUrl($this->tree, [ |
||||
216 | 'place_id' => $this->id(), |
||||
217 | 'tree' => $this->tree->name(), |
||||
218 | ]); |
||||
219 | } |
||||
220 | |||||
221 | // The place-list module is disabled... |
||||
222 | return '#'; |
||||
223 | } |
||||
224 | |||||
225 | /** |
||||
226 | * Format this place for GEDCOM data. |
||||
227 | * |
||||
228 | * @return string |
||||
229 | */ |
||||
230 | public function gedcomName(): string |
||||
231 | { |
||||
232 | return $this->place_name; |
||||
233 | } |
||||
234 | |||||
235 | /** |
||||
236 | * Format this place for display on screen. |
||||
237 | */ |
||||
238 | public function placeName(): string |
||||
239 | { |
||||
240 | $place_name = $this->parts->first() ?? I18N::translate('unknown'); |
||||
241 | |||||
242 | return '<bdi>' . e($place_name) . '</bdi>'; |
||||
243 | } |
||||
244 | |||||
245 | /** |
||||
246 | * Generate the place name for display, including the full hierarchy. |
||||
247 | */ |
||||
248 | public function fullName(bool $link = false): string |
||||
249 | { |
||||
250 | if ($this->parts->isEmpty()) { |
||||
251 | return ''; |
||||
252 | } |
||||
253 | |||||
254 | $full_name = $this->parts->implode(I18N::$list_separator); |
||||
255 | |||||
256 | if ($link) { |
||||
257 | $url = $this->url(); |
||||
258 | |||||
259 | if ($url !== '#') { |
||||
260 | return '<a class="ut" href="' . e($url) . '">' . e($full_name) . '</a>'; |
||||
261 | } |
||||
262 | } |
||||
263 | |||||
264 | return '<bdi>' . e($full_name) . '</bdi>'; |
||||
265 | } |
||||
266 | |||||
267 | /** |
||||
268 | * For lists and charts, where the full name won’t fit. |
||||
269 | */ |
||||
270 | public function shortName(bool $link = false): string |
||||
271 | { |
||||
272 | $SHOW_PEDIGREE_PLACES = (int) $this->tree->getPreference('SHOW_PEDIGREE_PLACES'); |
||||
273 | |||||
274 | // Abbreviate the place name, for lists |
||||
275 | if ($this->tree->getPreference('SHOW_PEDIGREE_PLACES_SUFFIX') === '1') { |
||||
276 | $parts = $this->lastParts($SHOW_PEDIGREE_PLACES); |
||||
277 | } else { |
||||
278 | $parts = $this->firstParts($SHOW_PEDIGREE_PLACES); |
||||
279 | } |
||||
280 | |||||
281 | $short_name = $parts->implode(I18N::$list_separator); |
||||
282 | |||||
283 | if ($link) { |
||||
284 | $url = $this->url(); |
||||
285 | |||||
286 | if ($url !== '#') { |
||||
287 | $title = strip_tags($this->fullName()); |
||||
288 | |||||
289 | return '<a class="ut" href="' . e($url) . '" title="' . e($title) . '">' . e($short_name) . '</a>'; |
||||
290 | } |
||||
291 | } |
||||
292 | |||||
293 | return '<span class="ut">' . e($short_name) . '</span>'; |
||||
294 | } |
||||
295 | } |
||||
296 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths