Passed
Pull Request — master (#3675)
by
unknown
07:46
created

PlaceHierarchyListModule::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Module;
21
22
use Aura\Router\RouterContainer;
23
use Fisharebest\Webtrees\Auth;
24
use Fisharebest\Webtrees\Contracts\UserInterface;
25
use Fisharebest\Webtrees\I18N;
26
use Fisharebest\Webtrees\Place;
27
use Fisharebest\Webtrees\PlaceLocation;
28
use Fisharebest\Webtrees\Services\ModuleService;
29
use Fisharebest\Webtrees\Services\SearchService;
30
use Fisharebest\Webtrees\Services\UserService;
31
use Fisharebest\Webtrees\Site;
32
use Fisharebest\Webtrees\Statistics;
33
use Fisharebest\Webtrees\Tree;
34
use Fisharebest\Webtrees\Webtrees;
35
use Illuminate\Database\Capsule\Manager as DB;
36
use Psr\Http\Message\ResponseInterface;
37
use Psr\Http\Message\ServerRequestInterface;
38
use Psr\Http\Server\RequestHandlerInterface;
39
40
use function array_chunk;
41
use function array_pop;
42
use function array_reverse;
43
use function assert;
44
use function ceil;
45
use function count;
46
use function is_file;
47
use function redirect;
48
use function route;
49
use function view;
50
51
/**
52
 * Class IndividualListModule
53
 */
54
class PlaceHierarchyListModule extends AbstractModule implements ModuleListInterface, RequestHandlerInterface
55
{
56
    use ModuleListTrait;
57
58
    protected const ROUTE_URL = '/tree/{tree}/place-list';
59
60
    /** @var int The default access level for this module.  It can be changed in the control panel. */
61
    protected $access_level = Auth::PRIV_USER;
62
63
    /** @var SearchService */
64
    private $search_service;
65
66
    /**
67
     * PlaceHierarchy constructor.
68
     *
69
     * @param SearchService $search_service
70
     */
71
    public function __construct(SearchService $search_service)
72
    {
73
        $this->search_service = $search_service;
74
    }
75
76
    /**
77
     * Initialization.
78
     *
79
     * @return void
80
     */
81
    public function boot(): void
82
    {
83
        $router_container = app(RouterContainer::class);
84
        assert($router_container instanceof RouterContainer);
85
86
        $router_container->getMap()
87
            ->get(static::class, static::ROUTE_URL, $this);
88
    }
89
90
    /**
91
     * How should this module be identified in the control panel, etc.?
92
     *
93
     * @return string
94
     */
95
    public function title(): string
96
    {
97
        /* I18N: Name of a module/list */
98
        return I18N::translate('Place hierarchy');
99
    }
100
101
    /**
102
     * A sentence describing what this module does.
103
     *
104
     * @return string
105
     */
106
    public function description(): string
107
    {
108
        /* I18N: Description of the “Place hierarchy” module */
109
        return I18N::translate('The place hierarchy.');
110
    }
111
112
    /**
113
     * CSS class for the URL.
114
     *
115
     * @return string
116
     */
117
    public function listMenuClass(): string
118
    {
119
        return 'menu-list-plac';
120
    }
121
122
    /**
123
     * @param Tree    $tree
124
     * @param mixed[] $parameters
125
     *
126
     * @return string
127
     */
128
    public function listUrl(Tree $tree, array $parameters = []): string
129
    {
130
        $parameters['tree'] = $tree->name();
131
132
        return route(static::class, $parameters);
133
    }
134
135
    /**
136
     * @return string[]
137
     */
138
    public function listUrlAttributes(): array
139
    {
140
        return [];
141
    }
142
143
    /**
144
     * @param Tree $tree
145
     *
146
     * @return bool
147
     */
148
    public function listIsEmpty(Tree $tree): bool
149
    {
150
        return !DB::table('places')
151
            ->where('p_file', '=', $tree->id())
152
            ->exists();
153
    }
154
155
    /**
156
     * Handle URLs generated by older versions of webtrees
157
     *
158
     * @param ServerRequestInterface $request
159
     *
160
     * @return ResponseInterface
161
     */
162
    public function getListAction(ServerRequestInterface $request): ResponseInterface
163
    {
164
        return redirect($this->listUrl($request->getAttribute('tree'), $request->getQueryParams()));
165
    }
166
167
    /**
168
     * @param ServerRequestInterface $request
169
     *
170
     * @return ResponseInterface
171
     */
172
    public function handle(ServerRequestInterface $request): ResponseInterface
173
    {
174
        $tree = $request->getAttribute('tree');
175
        assert($tree instanceof Tree);
176
177
        $user = $request->getAttribute('user');
178
        assert($user instanceof UserInterface);
179
180
        Auth::checkComponentAccess($this, ModuleListInterface::class, $tree, $user);
181
182
        $action2  = $request->getQueryParams()['action2'] ?? 'hierarchy';
183
        $place_id = (int) ($request->getQueryParams()['place_id'] ?? 0);
184
        $place    = Place::find($place_id, $tree);
185
186
        // Request for a non-existent place?
187
        if ($place_id !== $place->id()) {
188
            return redirect($place->url());
189
        }
190
191
        $content = '';
192
        $showmap = Site::getPreference('map-provider') !== '';
193
        $data    = null;
194
195
        if ($showmap) {
196
            $content .= view('modules/place-hierarchy/map', [
197
                'data'     => $this->mapData($tree, $place),
198
                'provider' => [
199
                    'url'    => 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
200
                    'options' => [
201
                        'attribution' => '<a href="https://www.openstreetmap.org/copyright">&copy; OpenStreetMap</a> contributors',
202
                        'max_zoom'    => 19
203
                    ]
204
                ]
205
            ]);
206
        }
207
208
        switch ($action2) {
209
            case 'list':
210
            default:
211
                $alt_link = I18N::translate('Show place hierarchy');
212
                $alt_url  = $this->listUrl($tree, ['action2' => 'hierarchy', 'place_id' => $place_id]);
213
                $content .= view('modules/place-hierarchy/list', ['columns' => $this->getList($tree)]);
214
                break;
215
            case 'hierarchy':
216
            case 'hierarchy-e':
217
                $alt_link = I18N::translate('Show all places in a list');
218
                $alt_url  = $this->listUrl($tree, ['action2' => 'list', 'place_id' => 0]);
219
                $data     = $this->getHierarchy($place);
220
                $content .= (null === $data || $showmap) ? '' : view('place-hierarchy', $data);
221
                if (null === $data || $action2 === 'hierarchy-e') {
222
                    $content .= view('modules/place-hierarchy/events', [
223
                        'indilist' => $this->search_service->searchIndividualsInPlace($place),
224
                        'famlist'  => $this->search_service->searchFamiliesInPlace($place),
225
                        'tree'     => $place->tree(),
226
                    ]);
227
                }
228
        }
229
230
        if ($data !== null && $action2 !== 'hierarchy-e' && $place->gedcomName() !== '') {
231
            $events_link = $this->listUrl($tree, ['action2' => 'hierarchy-e', 'place_id' => $place_id]);
232
        } else {
233
            $events_link = '';
234
        }
235
236
        $breadcrumbs = $this->breadcrumbs($place);
237
238
        $geo_link = '';
239
        if (Auth::isAdmin()) {
240
            $location = new PlaceLocation($place->gedcomName());
241
            if ($location->id() !== 0 || $location->parent()->id() !== 0) { //Can't edit the world's coordinates
242
                $geo_link = route('map-data-edit', ['place_id'  => $location->id(), 'parent_id' => $location->parent()->id()]);
243
            }
244
        }
245
246
        return $this->viewResponse('modules/place-hierarchy/page', [
247
            'geo_link'    => $geo_link,
248
            'alt_link'    => $alt_link,
249
            'alt_url'     => $alt_url,
250
            'breadcrumbs' => $breadcrumbs['breadcrumbs'],
251
            'content'     => $content,
252
            'current'     => $breadcrumbs['current'],
253
            'events_link' => $events_link,
254
            'place'       => $place,
255
            'title'       => I18N::translate('Place hierarchy'),
256
            'tree'        => $tree,
257
            'world_url'   => $this->listUrl($tree)
258
        ]);
259
    }
260
261
    /**
262
     * @param Tree $tree
263
     *
264
     * @return array<array<Place>>
265
     */
266
    private function getList(Tree $tree): array
267
    {
268
        $places = $this->search_service->searchPlaces($tree, '')
269
            ->sort(static function (Place $x, Place $y): int {
270
                return $x->gedcomName() <=> $y->gedcomName();
271
            })
272
            ->all();
273
274
        $count = count($places);
275
276
        if ($places === []) {
277
            return [];
278
        }
279
280
        $columns = $count > 20 ? 3 : 2;
281
282
        return array_chunk($places, (int) ceil($count / $columns));
283
    }
284
285
286
    /**
287
     * @param Place $place
288
     *
289
     * @return array{'tree':Tree,'col_class':string,'columns':array<array<Place>>,'place':Place}|null
290
     */
291
    private function getHierarchy(Place $place): ?array
292
    {
293
        $child_places = $place->getChildPlaces();
294
        $numfound     = count($child_places);
295
296
        if ($numfound > 0) {
297
            $divisor = $numfound > 20 ? 3 : 2;
298
299
            return [
300
                'tree'      => $place->tree(),
301
                'col_class' => 'w-' . ($divisor === 2 ? '25' : '50'),
302
                'columns'   => array_chunk($child_places, (int) ceil($numfound / $divisor)),
303
                'place'     => $place,
304
            ];
305
        }
306
307
        return null;
308
    }
309
310
    /**
311
     * @param Place $place
312
     *
313
     * @return array{'breadcrumbs':array<Place>,'current':Place|null}
314
     */
315
    private function breadcrumbs(Place $place): array
316
    {
317
        $breadcrumbs = [];
318
        if ($place->gedcomName() !== '') {
319
            $breadcrumbs[] = $place;
320
            $parent_place  = $place->parent();
321
            while ($parent_place->gedcomName() !== '') {
322
                $breadcrumbs[] = $parent_place;
323
                $parent_place  = $parent_place->parent();
324
            }
325
            $breadcrumbs = array_reverse($breadcrumbs);
326
            $current     = array_pop($breadcrumbs);
327
        } else {
328
            $current = null;
329
        }
330
331
        return [
332
            'breadcrumbs' => $breadcrumbs,
333
            'current'     => $current,
334
        ];
335
    }
336
337
    /**
338
     * @param Tree  $tree
339
     * @param Place $placeObj
340
     *
341
     * @return array<mixed>
342
     */
343
    protected function mapData(Tree $tree, Place $placeObj): array
344
    {
345
        $places    = $placeObj->getChildPlaces();
346
        $features  = [];
347
        $sidebar   = '';
348
        $flag_path = Webtrees::MODULES_DIR . 'openstreetmap/';
349
        $show_link = true;
350
351
        if ($places === []) {
352
            $places[] = $placeObj;
353
            $show_link = false;
354
        }
355
356
        foreach ($places as $id => $place) {
357
            $location = new PlaceLocation($place->gedcomName());
358
359
            if ($location->icon() !== '' && is_file($flag_path . $location->icon())) {
360
                $flag = $flag_path . $location->icon();
361
            } else {
362
                $flag = '';
363
            }
364
365
            if ($location->latitude() === 0.0 && $location->longitude() === 0.0) {
366
                $sidebar_class = 'unmapped';
367
            } else {
368
                $sidebar_class = 'mapped';
369
                $features[]    = [
370
                    'type'       => 'Feature',
371
                    'id'         => $id,
372
                    'geometry'   => [
373
                        'type'        => 'Point',
374
                        'coordinates' => [$location->longitude(), $location->latitude()],
375
                    ],
376
                    'properties' => [
377
                        'tooltip' => $place->gedcomName(),
378
                        'popup'   => view('modules/place-hierarchy/popup', [
379
                            'showlink'  => $show_link,
380
                            'flag'      => $flag,
381
                            'place'     => $place,
382
                            'latitude'  => $location->latitude(),
383
                            'longitude' => $location->longitude(),
384
                        ]),
385
                    ],
386
                ];
387
            }
388
389
            $statistics = new Statistics(app(ModuleService::class), $tree, app(UserService::class));
390
391
            //Stats
392
            $placeStats = [];
393
            foreach (['INDI', 'FAM'] as $type) {
394
                $tmp               = $statistics->statsPlaces($type, '', $place->id());
395
                $placeStats[$type] = $tmp === [] ? 0 : $tmp[0]->tot;
396
            }
397
            $sidebar .= view('modules/place-hierarchy/sidebar', [
398
                'showlink'      => $show_link,
399
                'flag'          => $flag,
400
                'id'            => $id,
401
                'place'         => $place,
402
                'sidebar_class' => $sidebar_class,
403
                'stats'         => $placeStats,
404
            ]);
405
        }
406
407
        return [
408
            'bounds'  => (new PlaceLocation($placeObj->gedcomName()))->boundingRectangle(),
409
            'sidebar' => $sidebar,
410
            'markers' => [
411
                'type'     => 'FeatureCollection',
412
                'features' => $features,
413
            ]
414
        ];
415
    }
416
}
417