Passed
Pull Request — master (#3703)
by
unknown
07:34
created

PlaceHierarchyListModule   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 350
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 43
eloc 156
c 1
b 0
f 0
dl 0
loc 350
rs 8.96

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A description() 0 4 1
A listMenuClass() 0 3 1
A title() 0 4 1
A listUrl() 0 5 1
D handle() 0 86 17
A getList() 0 17 3
A getListAction() 0 3 1
A listUrlAttributes() 0 3 1
A getHierarchy() 0 17 4
A breadcrumbs() 0 19 3
B mapData() 0 61 7
A listIsEmpty() 0 5 1
A boot() 0 7 1

How to fix   Complexity   

Complex Class

Complex classes like PlaceHierarchyListModule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PlaceHierarchyListModule, and based on these observations, apply Extract Interface, too.

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